draw

draw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def draw():
    # 画图窗口大小设置为512*512 通常参数,可选1,3,4
    img = np.zeros((512, 512, 4), np.uint8)

    # 画直线,参数为绘图窗口,起点,终点,颜色,粗细,连通性(可选4联通或8联通)
    img = cv.line(img, (100, 0), (511, 511), (255, 0, 0), 5, 8)
    # 画矩形,参数为窗口,左上角坐标,右下角坐标,颜色,粗度
    img = cv.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
    # 画圆函数,参数为窗口,圆心,半径,颜色,粗度(如果粗度为-1标示实心),连通性
    img = cv.circle(img, (447, 63), 50, (0, 0, 255), 1, 8)
    # 画椭圆函数,参数为窗口,椭圆中心,椭圆长轴短轴,椭圆逆时针旋转角度,椭圆起绘制起始角度,椭圆绘制结束角度,颜色,粗度,连通性
    img = cv.ellipse(img, (256, 256), (100, 50), 100, 36, 360, (0, 255, 255), 1, 8)

    # 画多边形
    pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
    # 转换成三维数组,表示每一维里有一个点坐标
    pts = pts.reshape((-1, 1, 2))
    # 画多边形函数,参数为窗口,坐标,是否封闭,颜色
    img = cv.polylines(img, [pts], True, (0, 255, 255))

    # 字 参数为窗口,字符,字体位(左下角),字体类型(查询cv2.putText()函数的文档来查看字体),字体大小,常规参数,类似颜色,粗细,线条类型等等
    font = cv.FONT_HERSHEY_SIMPLEX
    cv.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)

    util.show(img)

鼠标 draw

监听鼠标事件 cv.EVENT_* cv.setMouseCallback('image', draw_circle)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def mouse_draw():
    """双击画圆"""

    def draw_circle(event, x, y, flags, param):
        if event == cv.EVENT_LBUTTONDBLCLK:
            cv.circle(img, (x, y), 100, (255, 0, 0), -1)

    def draw_circle(event, x, y, flags, param):
        global ix, iy, drawing, mode
        # print(ix, iy, drawing, mode)
        if event == cv.EVENT_LBUTTONDOWN:
            drawing = True
            ix, iy = x, y
        elif event == cv.EVENT_MOUSEMOVE:
            if drawing:
                if mode:
                    cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1)
                else:
                    cv.circle(img, (x, y), 5, (0, 0, 255), -1)
        elif event == cv.EVENT_LBUTTONUP:
            drawing = False
            if mode:
                cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1)
            else:
                cv.circle(img, (x, y), 5, (0, 0, 255), -1)

    img = np.zeros((512, 512, 3), np.uint8)
    cv.namedWindow('image')
    cv.setMouseCallback('image', draw_circle)

    while True:
        cv.imshow('image', img)
        if cv.waitKey(20) & 0xFF == 27:
            break
    cv.destroyAllWindows()

UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def bar_grb():
    def nothing(x):
        pass

    # Create a black image, a window
    img = np.zeros((300, 512, 3), np.uint8)
    cv.namedWindow('image')

    # create trackbars for color change
    cv.createTrackbar('R', 'image', 0, 255, nothing)
    cv.createTrackbar('G', 'image', 0, 255, nothing)
    cv.createTrackbar('B', 'image', 0, 255, nothing)
    # create switch for ON/OFF functionality
    switch = '0 : OFF \n1 : ON'
    cv.createTrackbar(switch, 'image', 0, 1, nothing)

    while True:
        cv.imshow('image', img)
        k = cv.waitKey(1) & 0xFF
        if k == 27:
            break
        # get current positions of four trackbars
        r = cv.getTrackbarPos('R', 'image')
        g = cv.getTrackbarPos('G', 'image')
        b = cv.getTrackbarPos('B', 'image')
        s = cv.getTrackbarPos(switch, 'image')
        if s == 0:
            img[:] = 0
        else:
            img[:] = [b, g, r]

    cv.destroyAllWindows()

plot

直方图 histogram

直方图可以对图像灰度分布有一个整体了解,x轴上是灰度值(0到255),y轴是图片中该灰度值的像素点的数目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""
# cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
"""
# hist = cv.calcHist([img], [0], None, [256], [0, 256])
#
# hist, bins = np.histogram(img.ravel(), 256, [0, 256])

# mask = np.zeros(img.shape[:2], np.uint8)
# mask[100:300, 100:400] = 255
# masked_img = cv.bitwise_and(img, img, mask=mask)

# gray
img = util.load_img('img/home.jpg',0)
# plt.hist(img.ravel(), 256, [0, 256])
# plt.show()

mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img, img, mask=mask)

hist_full = cv.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv.calcHist([img], [0], mask, [256], [0, 256])

plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0, 256])
plt.show()

# color
img = util.load_img('img/home.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
    #
    histr = cv.calcHist([img], [i], None, [256], [0, 256])
    plt.plot(histr, color=col)
    plt.xlim([0, 256])
plt.show()

直方图均衡

增加图像整体的对比度,局部对比度较低的区域获得较高的对比度,缺点它可能会增加背景噪音的对比度 ,同时减少可用信号。

1
2
3
4
img = util.load_img('img/contrast.png',0)
equ = cv.equalizeHist(img)
res = np.hstack((img, equ))  # stacking images side-by-side
util.show(res)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
img = util.load_img('img/tsukuba.png', 0)
# 直方图均衡
equ = cv.equalizeHist(img)
"""
自适应直方图均衡解决 增加背景噪音的对比度 增加局部对比度

图像被分成称为tile(8x8)的小块.每个小块进行直方图均衡, 加入对比度限制防止噪音被放大
超过限制,像素剪切并均匀分布到其他区间,均衡后,为了去除图块边框中的瑕疵,应用双线性插值。
"""
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl1 = clahe.apply(img)

res = np.hstack((img, equ,cl1))
util.show(res)

直方图反向投影

用于图像分割或查找图像中感兴趣的对象,创建的图像与我们的输入图像具有相同大小(但是单个通道)的图像,输出的图像我们感兴趣的部分像素值较高(更白)

最好使用颜色直方图,物体的颜色信息比灰度图像更容易被分割和识别。再将颜色直方图投影到tar查找目标,找到输入图像中每一个像素点的像素值在直方图中对应的概率

得到一个概率图像,最后设置适当的阈值对概率图像进行二值化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
roi = util.load_img('img/roi.png')
roi_hsv = util.hsv(roi)
tar = util.load_img('img/tar.png')
tar_hsv = util.hsv(tar)

# 计算目标直方图 颜色直方图优于灰度直方图
roihist = cv.calcHist([roi_hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
# 对象直方图进行normalize cv.calcBackProject返回概率图像
cv.normalize(roihist, roihist, 0, 255, cv.NORM_MINMAX)
dst = cv.calcBackProject([tar_hsv], [0, 1], roihist, [0, 180, 0, 256], 1)
# 与disc kernel卷积
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
cv.filter2D(dst, -1, disc, dst)

# threshold and binary AND
ret, thresh = cv.threshold(dst, 50, 255, 0)
# 使用merge变成通道图像
thresh = cv.merge((thresh, thresh, thresh))
# 蒙板
res = cv.bitwise_and(tar, thresh)

res = np.hstack((tar, thresh, res))
util.show(res)

模板匹配

较大图像中搜索和查找模板图像位置的方法

与2D卷积一样 模板图像在输入图像上滑动(类似窗口),在每一个位置对模板图像和输入图像的窗口区域进行匹配。 与直方图的反向投影类似。

输入图像大小是W×H,模板大小是w×h,输出结果的大小(W-w+1,H-h+1)。得到此结果后可以使用函数cv2.minMaxLoc()来找到其中的最小值和最大值的位置。

第一个值为矩形左上角的位置,(w,h)是模板矩形的宽度和高度。矩形就是模板区域。

匹配一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
roi = util.load_img('img/roi.png', 0)
w, h = roi.shape[::-1]
tar = util.load_img('img/tar.png', 0)
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
           'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']

for meth in methods:
    img = roi.copy()
    method = eval(meth)

    res = cv.matchTemplate(img, tar, method)
    # 只匹配一个对象
    min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)

    if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
        # 最小值会给出最佳匹配
        top_left = min_loc
    else:
        top_left = max_loc

    bottom_right = (top_left[0] + w, top_left[1] + h)
    cv.rectangle(img, top_left, bottom_right, 255, 2)

    plt.subplot(121), plt.imshow(res, cmap='gray')
    plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(img, cmap='gray')
    plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
    plt.suptitle(meth)
    plt.show()

匹配多个

1
2
3
4
5
6
7
8
9
10
11
12
13
img_rgb = util.load_img('img/mario.png')
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('img/mario_coin.png', 0)
w, h = template.shape[::-1]
res = cv.matchTemplate(img_gray, template, cv.TM_CCOEFF_NORMED)
print(res.shape)
threshold = 0.8
# 注意 行列 高宽
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
    cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)

util.show(img_rgb)