摘要: 将项目实践中比较常用的自定义函数集合汇总, 主要是Python函数, 便于快速算法验证


import matplotlib.pyplot as plt
import cv2 as cv

视频帧及bbox读取

import cv2 as cv

frame_sequence = "base"
fix_mode = True
skip_frames = 592
fps_disp = 25


def onMouse(event, x, y, flags, param):
    disp = param["img"].copy()
    roi = param["roi"]
    if event == cv.EVENT_MOUSEMOVE:
        roi[0] = x - roi[2] // 2
        roi[1] = y - roi[3] // 2
        pt1 = tuple(roi[:2])
        pt2 = tuple([int(x + y) for x, y in zip(pt1, roi[2:4])])
        cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
        cv.imshow("frame_sequence", disp)
        cv.waitKey(1)


def main():
    # 读取视频帧序列
    cap = cv.VideoCapture(frame_sequence + "/%4d.jpg")

    # 读取 groundtruth
    with open(frame_sequence + "/groundtruth_rect.txt", "r") as f:
        roi_gt_list = f.readlines()

    # 循环显示视频帧
    length = len(roi_gt_list)
    for i in range(length):
        _, frame = cap.read()
        if frame is None:
            break
        if i < skip_frames:
            continue
        # 转换 roi,原格式 "11.5,22,33.5,44\n"
        roi_gt = roi_gt_list[i].strip("\n").split(",")
        roi_gt = [round(float(x)) for x in roi_gt]
        # 显示视频帧及标注框
        disp = frame.copy()
        pt1 = tuple(roi_gt[:2])
        pt2 = tuple([int(x + y) for x, y in zip(pt1, roi_gt[2:4])])
        cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
        font = cv.FONT_HERSHEY_SIMPLEX
        cv.putText(disp, "frame: " + str(i), (20, 60), font, 1, (0, 255, 0), 2, cv.LINE_AA)
        cv.imshow("frame_sequence", disp)
        cv.waitKey(round(1000 / fps_disp))
        # 选择修复
        labeled = False
        run = True
        roi = roi_gt.copy()
        while fix_mode:
            key = cv.waitKey(0)
            if key == 27:  # ESC 退出
                run = False
                break
            elif key == 32:  # 空格下一帧
                break
            elif key in [ord('q'), ord('Q')]:  # Q键 修复标注
                cv.imshow("frame_sequence", frame)
                params = {"img": frame, "roi": roi}
                cv.setMouseCallback("frame_sequence", onMouse, params)
                cv.waitKey(0)
                labeled = True
                break
            else:  # 禁止其他键
                continue
        # 写入标注并显示
        if labeled:
            roi_str = str(roi[0]) + ',' + \
                      str(roi[1]) + ',' + \
                      str(roi[2]) + ',' + \
                      str(roi[3]) + "\n"
            roi_gt_list[i] = roi_str
            disp = frame.copy()
            pt1 = tuple(roi[:2])
            pt2 = tuple([int(x + y) for x, y in zip(pt1, roi[2:4])])
            cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
            cv.imshow("labeled", disp)
            cv.waitKey(0)
            cv.destroyAllWindows()
        # 退出
        if not run:
            cv.destroyAllWindows()
            break

    # 写入文件
    with open(frame_sequence + "/groundtruth_rect.txt", "w") as f:
        f.writelines(roi_gt_list)

    cv.waitKey(0)
    cv.destroyAllWindows()


if __name__ == '__main__':
    main()

plt 单幅图片显示

  • 常用在jupyter中
  • 自动显示BGR彩色图和灰度图. 注意是opencv::imread读入的BGR彩色图
  • 可设置缩放比例. scale=0.5,即横向纵向都缩小为1/2
def imshow(img, scale=None, name=None, axis=False):
    assert img is not None, "image is not exit"
    if scale:
        assert 0 <= scale <= 1, "0 <= scale <= 1 "
        height, width = img.shape[:2]
        plt.figure(figsize=(width*scale/70, height*scale/70), dpi=70)
    else:
        plt.figure()
    isColor = True if img.ndim == 3 else False
    if isColor:
        plt.imshow(img[:,:,::-1])
    else:
        plt.imshow(img, cmap="gray")
    ax = "image" if axis else "off"
    plt.title(name)
    plt.axis(ax)
    plt.show()

imshow(src, 0.5)

常见色彩空间的分量显示

def imColorSpace(src):
    assert src.ndim == 3, "Input image must be 3 channels"
    imgray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    hsv = cv.cvtColor(src, cv.COLOR_BGR2HSV)
    luv = cv.cvtColor(src, cv.COLOR_BGR2LUV)
    lab = cv.cvtColor(src, cv.COLOR_BGR2LAB)
    plt.figure(figsize=(15, 25), dpi=72)
    plt.subplot(5,3,1), plt.imshow(src[:,:,::-1]), plt.axis("off"), plt.title("Source")
    plt.subplot(5,3,2), plt.imshow(imgray,cmap="gray"), plt.axis("off"), plt.title("Gray")
    plt.subplot(5,3,4), plt.imshow(src[:,:,0],cmap="gray"), plt.axis("off"), plt.title("BGR-B")
    plt.subplot(5,3,5), plt.imshow(src[:,:,1],cmap="gray"), plt.axis("off"), plt.title("BGR-G")
    plt.subplot(5,3,6), plt.imshow(src[:,:,2],cmap="gray"), plt.axis("off"), plt.title("BGR-R")
    plt.subplot(5,3,7), plt.imshow(hsv[:,:,0],cmap="gray"), plt.axis("off"), plt.title("HSV-H")
    plt.subplot(5,3,8), plt.imshow(hsv[:,:,1],cmap="gray"), plt.axis("off"), plt.title("HSV-S")
    plt.subplot(5,3,9), plt.imshow(hsv[:,:,2],cmap="gray"), plt.axis("off"), plt.title("HSV-V")
    plt.subplot(5,3,10), plt.imshow(luv[:,:,0],cmap="gray"), plt.axis("off"), plt.title("LUV-L")
    plt.subplot(5,3,11), plt.imshow(luv[:,:,1],cmap="gray"), plt.axis("off"), plt.title("LUV-U")
    plt.subplot(5,3,12), plt.imshow(luv[:,:,2],cmap="gray"), plt.axis("off"), plt.title("LUV-V")
    plt.subplot(5,3,13), plt.imshow(lab[:,:,0],cmap="gray"), plt.axis("off"), plt.title("LAB-L")
    plt.subplot(5,3,14), plt.imshow(lab[:,:,1],cmap="gray"), plt.axis("off"), plt.title("LAB-A")
    plt.subplot(5,3,15), plt.imshow(lab[:,:,2],cmap="gray"), plt.axis("off"), plt.title("LAB-B")
    plt.tight_layout()
    plt.show()

二值图像的联通域显示

def imConnectedComponent(imbin):
    assert imbin.ndim == 2, "Input image must be 2 channels"
    drawing = np.zeros([imbin.shape[0], imbin.shape[1], 3], dtype="uint8")
    ccNum, labels, stats, centroids = cv.connectedComponentsWithStats(imbin)
    colors = [np.array([0,0,0])] + [np.random.rand(3)*256 for i in range(ccNum-1)]
    for i in range(ccNum):
        x1, y1, width, height, count = stats[i]
        print("Connected Component %2d: (%4d,%4d), width=%4d, height=%4d, centroids=(%d, %d)" 
        % (i, x1, y1, width, height, centroids[i,0], centroids[i,1]))
        # 涂色, label 0 黑色背景
        drawing[labels==i] = colors[i]
    plt.figure(figsize=(12,6))
    plt.subplot(1,2,1), plt.imshow(imbin,cmap="gray"), plt.axis("off"), plt.title("Source")
    plt.subplot(1,2,2), plt.imshow(drawing[:,:,::-1]), plt.axis("off"), plt.title("Connected Component")
    plt.show()

显示统计直方图

def imhist(img, sharey=False):
    assert img is not None, "image is not exit"
    isColor = True if img.ndim == 3 else False
    if isColor:
        fig, (ax1,ax2,ax3) = plt.subplots(1,3,sharey=sharey,figsize=(12,3))
        ax1.hist(img[:,:,0].ravel(), bins=256, range=(0,256), color="b"),ax1.set_xticks(range(0,256,50))
        ax2.hist(img[:,:,1].ravel(), bins=256, range=(0,256), color="g"),ax2.set_xticks(range(0,256,50))
        ax3.hist(img[:,:,2].ravel(), bins=256, range=(0,256), color="r"),ax3.set_xticks(range(0,256,50))
        ax2.set_xlabel("level"), ax1.set_ylabel("frequency")
        plt.tight_layout()
    else:
        plt.figure(figsize=(15,3))
        plt.hist(img.ravel(), bins=256, range=(0,256))
        plt.title("Statistical Histogram")
        plt.xlabel("level"), plt.ylabel("frequency")
        plt.xticks(range(0,256,20))
    plt.show()

imhist(hsv)
imhist(hsv, False)
imhist(hsv[:, :, 2])

颜色过滤生成二值图

  • 适用三通道/单通道图像
def imColorFilter(img, lower, upper):
    channels= 3 if img.ndim == 3 else 1
    assert len(lower) == len(upper) == channels
    lower = np.array(lower)
    upper = np.array(upper)
    imcolor = cv.inRange(img,lower,upper)
    return imcolor

imcolor= imColorFilter(src, [180,150,100], [230,200,130])

Hough 圆检测

void cv::HoughCircles(  InputArray  image,
                        OutputArray circles,
                        int 	    method,
                        double 	    dp,
                        double 	    minDist,
                        double 	    param1 = 100,
                        double 	    param2 = 100,
                        int 	    minRadius = 0,
                        int 	    maxRadius = 0
                     )

Python:
circles = cv.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])

参数:
- image         8位,单通道灰度化的输入图像
- circles       Output vector of found circles.Each vector is encoded as 3 or 4 element floating - point vector(x, y, radius) or (x, y, radius, votes) .
- method        Detection method.Currently, the only implemented method is HOUGH_GRADIENT (v4.1.2- dp	        累加器分辨率与图像分辨率的反比,For example, if dp = 1, the accumulator has the same resolution as the input image.If dp = 2, the accumulator has half as big widthand height.
- minDist       Minimum distance between the centers of the detected circles.If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one.If it is too large, some circles may be missed.
- param1        the higher threshold of the Canny edge detector(the lower one is twice smaller).
- param2        圆心累加器阈值. 值越小,可能会检测到更多的假圆,优先返回累加器值较大的圆。
- minRadius     Minimum circle radius.
- maxRadius     Maximum circle radius.If <= 0, uses the maximum image dimension.If < 0, returns centers without finding the radius.

示例代码:

using namespace cv;

int main(int argc, char** argv)
{
    Mat img, gray;
    img = imread(argv[1], 1);
    cvtColor(img, gray, COLOR_BGR2GRAY);
    // smooth it, otherwise a lot of false circles may be detected
    GaussianBlur(gray, gray, Size(9, 9), 2, 2);
    
    vector<Vec3f> circles;
    double dp{ 2 }, minDist{ gray.rows / 4 }, CannyHighThres{ 200 }, accumulatorThres{ 100 };
    int minRadius{ 0 }, maxRadius{ 0 };
    HoughCircles(gray, circles, cv::HOUGH_GRADIENT, dp, minDist, CannyHighThres, accumulatorThres);
    for (size_t i = 0; i < circles.size(); i++)
    {
        Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        // draw the circle center
        circle(img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
        // draw the circle outline
        circle(img, center, radius, Scalar(0, 0, 255), 3, 8, 0);
    }
    
    namedWindow("circles", 1);
    imshow("circles", img);
    waitKey(0);
    
    return 0;
}

Hough 线检测

Hough线检测与透视变换

寻找轮廓

def imContour(imbin, drawing=None):
    assert imbin.ndim == 2, "Input must be single-channel binary image"
    contours, _ = cv.findContours(imbin, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    print(f'共寻找到 {len(contours)} 条轮廓\n')
    if drawing is None:
        dst = np.zeros([imbin.shape[0], imbin.shape[1]], dtype="uint8")
    else:
        dst = drawing.copy()
    cv.drawContours(dst, contours, -1, 255, 3)
    return dst

dst = imContour(imcolor, src)
dst = imContour(imcolor)

计算直线交点

  • 考虑某条直线斜率不存在
  • 考虑两直线平行, 交点不存在
  • 不考虑交点是否位于线段内
def getCrossPoint(lineA, lineB):
    assert len(lineA) == len(lineB) == 4, "len(line) != 4"
    k1 = (lineA[3]-lineA[1]) / (lineA[2]-lineA[0]+1e-4)
    k2 = (lineB[3]-lineB[1]) / (lineB[2]-lineB[0]+1e-4)
    b1 = lineA[1] - k1*lineA[0]
    b2 = lineB[1] - k2*lineB[0]
    assert abs(k1-k2) > 0.1, f"Parallel lines have no cross point.{k1:.2f},{k2:.2f}"
    if k1 > 1e4:
        x0 = lineA[0]
        y0 = k2*x0+b2
    elif k2 > 1e4:
        x0 = lineB[0]
        y0 = k1*x0+b1
    else:
        x0 = -(b1-b2)/(k1-k2)
        y0 = k1*x0+b1
    return int(x0), int(y0)

添加Gaussian噪声

  • 参数: 均值 μ \mu μ, 方差 σ 2 \sigma^2 σ2
  • python 广播机制保证了输入可以是彩色图或者灰度图
def addGaussianNoise(img, mean=0, var=0.01):
    scale = 255.0 if img.max()>1.0 else 1.0
    img = np.array(img/scale, dtype=float)
    noise = np.random.normal(mean, var**0.5, img.shape)
    dst = img + noise
    dst[dst<0] = 0
    dst[dst>1] = 1
    dst *= 255
    return dst.astype("uint8")

添加椒盐噪声

  • 参数: 椒盐噪声强度(密度, 即占比); 椒盐比例
  • python 广播机制保证了输入可以是彩色图或者灰度图
  • 计算噪声强度: 1 - (img==dst).sum()/(img==img).sum()
def addSPNoise(img, density=0.05, proportion=0.5):
    assert 0<density<1, "Noise density on range (0,1)"
    assert 0<proportion<1, "Proportion of salt noise on range (0,1)"
    dst = img.copy()
    prob_salt = density*proportion
    prob_pepper = density*(1-proportion)
    thres = 1-prob_pepper
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            rand = np.random.rand()
            if rand < prob_salt:
                dst[i][j] = 255
            elif rand > thres:
                dst[i][j] = 0
            else:
                pass
    return dst