计算机视觉实战(七)图像金字塔与轮廓检测

我的微信公众号名称:AI研究订阅号
微信公众号ID:MultiAgent1024
公众号介绍:主要研究强化学习、计算机视觉、深度学习、机器学习等相关内容,分享学习过程中的学习笔记和心得!期待您的关注,欢迎一起学习交流进步!

图像金字塔

  图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

  图像金字塔如下图所示:

图像金字塔

  那我们为什么要做图像金字塔呢?这就是因为改变像素大小有时候并不会改变它的特征,比方说给你看1000万像素的图片,你能知道里面有个人,给你看十万像素的,你也能知道里面有个人,但是对计算机而言,处理十万像素可比处理1000万像素要容易太多了。

高斯金字塔

  向下采样方法(缩小):

1 16 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] \frac{1}{16}\left[\begin{array}{ccccc} {1} & {4} & {6} & {4} & {1} \\ {4} & {16} & {24} & {16} & {4} \\ {6} & {24} & {36} & {24} & {6} \\ {4} & {16} & {24} & {16} & {4} \\ {1} & {4} & {6} & {4} & {1} \end{array}\right]

  分为两步:将 G i G_{i} 与高斯内核卷积,将所有偶数行和列去除(这也是池化的思想)。

拉普拉斯金字塔

  那向上采样怎么做呢?流程如下所示:

[ 10 30 56 96 ] [ 10 0 30 12 0 0 0 0 0 56 0 96 0 0 0 0 0 ] \left[\begin{array}{cc} {10} & {30} \\ {56} & {96} \end{array}\right] \Rightarrow \left[\begin{array}{cccc} {10} & {0} & {30} & {\frac{12}{0}} \\ {0} & {0} & {0} & {0} \\ {56} & {0} & {96} & {0} \\ {0} & {0} & {0} & {0} \end{array}\right]

  1. 将图像在每个方向扩大为原来的两倍,新增的行和列以0填充。
  2. 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值。

  读入图像

img=cv2.imread("aa.png")
cv_show(img,'img')
print (img.shape) 

  上采样

up=cv2.pyrUp(img)
cv_show(up,'up')
print (up.shape)

  下采样

down=cv2.pyrDown(img)
cv_show(down,'down')
print (down.shape) 

  这里就不给完整代码了,网上随便down一个图像应该都会可以的。

  对一张图片先进行上采样,再进行下采样得到的结果与原图是不一样的,因为上采样拿0填充再平均,下采样又损失了一些信息。

拉普拉斯金字塔

  代码实现:

down=cv2.pyrDown(img) # 下采样
down_up=cv2.pyrUp(down) # 上采样
l_1=img-down_up # 查看变换前后之间的差别
cv_show(l_1,'l_1')

图像轮廓

  • 边缘与轮廓的不同:

  边缘是零零散散的,但是轮廓是一个整体。

  • OpenCv轮廓检测函数
cv2.findContours(img,mode,method) # OpenCv轮廓检测函数

参数

  • mode:轮廓检索模式
  • RETR_EXTERNAL :只检索最外面的轮廓;
  • RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
  • RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
  • RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
  • method:轮廓逼近方法 。 # 通常情况下使用这个。
  • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
  • CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。

图像轮廓示意图

  • 为了更高的准确率,使用二值图像。
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 大于127取255,小于取0。
cv_show(thresh,'thresh')
  • 做完简单的处理之后,我们才能够用检测函数来检测轮廓,这样才能更好地检测出来。
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
  1. 第一个输入是二值处理完的图像。
  2. 第二个输入选择轮廓检测的模式。
  3. 第三个输入也是选择轮廓模式。
    第一个返回值是轮廓二值信息,第二个返回值是轮廓二值信息,第三个是一个层级信息。
  • 绘制轮廓
# 传入绘制图像,轮廓,轮廓索引,所画线条颜色模式,线条厚度
# 注意需要copy,要不原图会变。。。
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv_show(res,'res')
  • 轮廓特征
cnt = contours[0] #取第几个轮廓
  • 面积
cv2.contourArea(cnt)
  • 周长,True表示闭合的
cv2.arcLength(cnt,True)

  当轮廓有毛刺的时候,我们希望能够做轮廓近似,将毛刺去掉,大体思想是将曲线用直线代替,但是有个长度的阈值需要自己设定。

轮廓近似

轮廓近似

img = cv2.imread('contours2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #灰度处理
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) #二值处理
# 找轮廓
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 第一个轮廓
cnt = contours[0]
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv_show(res,'res')

  对于轮廓我们还可以做一些额外的操作,比如外接矩形,外接圆,外界椭圆等等。

  • 边界矩形
img = cv2.imread('contours.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv_show(img,'img')

area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print ('轮廓面积与边界矩形比',extent)

外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt) 
center = (int(x),int(y)) 
radius = int(radius) 
img = cv2.circle(img,center,radius,(0,255,0),2)
cv_show(img,'img')
  • 模板匹配

  模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有六种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AXB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)。

# 模板匹配
img = cv2.imread('lean.jpg',0)
template = cv2.imread('face.jpg',0)
h, w = template.shape[:2]
  • TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
  • TM_CCORR:计算相关性,计算出来的值越大,越相关
  • TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
  • TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
  • TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
  • TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关

  公式:https://docs.opencv.org/3.3.1/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d

res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
res.shape

res = cv2.mathTemplate(img, template, 1)

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

for meth in methods:
    img2 = img.copy()

    # 匹配方法的真值
    method = eval(meth)
    print (method)
    res = cv2.matchTemplate(img, template, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

    # 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)

    # 画矩形
    cv2.rectangle(img2, top_left, bottom_right, 255, 2)

    plt.subplot(121), plt.imshow(res, cmap='gray')
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
    plt.subplot(122), plt.imshow(img2, cmap='gray')
    plt.xticks([]), plt.yticks([])
    plt.suptitle(meth)
    plt.show()
  • 匹配多个对象
img_rgb = cv2.imread('mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.jpg', 0)
h, w = template.shape[:2]

res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于%80的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):  # *号表示可选参数
    bottom_right = (pt[0] + w, pt[1] + h)
    cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)

cv2.imshow('img_rgb', img_rgb)
cv2.waitKey(0)

  代码项目参考:https://mp.weixin.qq.com/s/Oo-NMrNw7OKnpgWR6UpYcQ

发布了141 篇原创文章 · 获赞 114 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/weixin_39059031/article/details/103439681
今日推荐