OpenCV - day07 Image_Outlook

337 阅读5分钟

图像轮廓

一、cv2.findContours(img, mode, method)

mode: 轮廓检测的模式

  • RETR_EXTERNAL:只检测最外面的轮廓
  • RETR_LIST:检测所有轮廓,并将其保存到一条链表
  • RETR_CCOMP:检测所有轮廓,并将它们组织为两层
    1. 顶层是各部分的外部边界
    2. 次层是空洞的边界
  • RETR_TREE(最常用):检测所有的轮廓,并重构整个嵌套轮廓的整个层次

method:轮廓逼近方法

  • CHIAN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
  • CHIAN_APPROX_SIMPLE:压缩水平的、垂直和斜的部分,namely,函数只保留他们终点的部分

为了提高轮廓检测的准确率,使用二值图像 检测步骤:

  1. 读入图像
  2. 转变为灰度图(可以和第一步融合)
  3. 设置阈值,转化为二值图像
  4. 轮廓检测

二、和边缘的区别:

边缘不一定连续,但是轮廓一定连续

压缩轮廓.PNG

import cv2
import matplotlib
import matplotlib.pyplot as plt #取别名(用于绘图展示)
import numpy as np #取别名,下面是notepad专用,立即显示图像
%matplotlib inline 
def cv_show(name, img): #定义函数用于显示图片,此处name为窗口名,img为cv2调用imread方法的返回值
    cv2.imshow(name,img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
def plt_show(images, titles):
    for i in range(len(images)):
        plt.subplot(351 + i)
        plt.imshow(cv2.cvtColor(images[i], 0))
        plt.title(titles[i])
def winds(images):
    res = np.hstack(images)
    cv_show('Compare', res)

三、轮廓处理

1. 进行二值处理

tg = cv2.imread('tgcf.png')# 彩图

gray = cv2.cvtColor(tg, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) #阈值,要先将图形转化为灰度图
cv_show('Bi', thresh)

plt_show([gray, thresh],['Origin','Binary'])

winds([gray, thresh])

output_7_0.png

对比图:

二值对比.PNG

2. 检测轮廓

## 传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
# 注意需要备份一下,否则原图会进行改变
cv2.imwrite('tgBi.png',thresh)

tgBi = cv2.imread('tgBi.png',-1)
cv_show('Test', tgBi)
## 旧版本的findContours返回三个值,而新版本只返回两个值:contours, hierarchy
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

contours是轮廓集合,hierarchy是层级

np.array(hierarchy).shape
(1, 1829, 4)

3. 绘制轮廓

  • 注意:

    thresh是单通道的灰度图像,而你的颜色设置(255, 0, 0)是三通道的,不可能在单通道图像上画三通道的轮廓线,因此 drawContours 中第一个参数图像需是三通道的才行

draw_tg = tg.copy() # 不用copy方法就是传递了地址,两个变量指向同一数组
# 绘制图像应该在原图像上绘制,二值图像是用来轮廓检测的


# 第一个参数就是作为画布,第二个是轮廓集合,第三个是所有轮廓索引(轮廓范围),第四个颜色模式,第五个线条厚度
# 每个别检测出来的对象都有内外两个轮廓
res = cv2.drawContours(draw_tg, contours, -1, (255, 0, 0), 1) 
cv_show('Contours', res)
# cv_show('Contours', draw_tg) #确实将传入的图像作为画布了

四、错误总结,不能在灰度图上绘制彩线

  • RGB三个通道,灰度图只有一个通道
  • 若是想要在灰度图上显示彩线,则可以将三个灰度图叠加
img = cv2.imread('tgcf.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #灰度图

_,thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) #二值图

#检测轮廓需要二值图
cons, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

# 尝试在二值图上绘制彩色轮廓
res_Bi = cv2.drawContours(thresh.copy(), cons, -1, (0, 0, 255), 2)
cv_show('Test', res_Bi) ##变黑了,只会读取彩线的强度

## 三张二值图的叠加

res_RGB = cv2.drawContours(cv2.merge((thresh.copy(),thresh.copy(),thresh.copy())), cons, -1, (0, 0, 255), 2)
cv_show('Test', res_RGB) #三张二值图叠加后,能作为彩色轮廓的画布,正常显示

plt_show([res_Bi, res_RGB],['BINARY','superposition'])
winds([cv2.merge((thresh.copy(),thresh.copy(),thresh.copy())), res_RGB])

output_18_0.png

对比图:

二值轮廓.PNG

五、轮廓特征和近似

1. 重新走一遍流程



img = cv2.imread('Outlook.png',-1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

plt_show([img, gray],['Origin', 'Gray'])

output_21_0.png

_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

cv_show('Thresh', thresh)
# 检测轮廓所用参数:cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE,牢记在心
cons, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

# 画轮廓必须在彩图上面,并且要备份
# 参数依次为:画布、轮廓集、轮廓序号、轮廓颜色、轮廓线条厚度
res = cv2.drawContours(img.copy(), cons, -1, (0, 0, 255), 1)
winds([img, res])

多轮廓.PNG

# 查看每个对象的内外轮廓,从右下角开始,水平向左移动
res = cv2.drawContours(img.copy(), cons, 0, (0, 0, 255), 2)

cv_show('Test', res)
## 查看轮廓特征
## 因为轮廓一定闭合,所以其有长度

con_0 = cons[11]
cv2.arcLength(con_0, True) # 以像素为单位,True表示闭合曲线,False表示间断
227.48022890090942
## 面积
cv2.contourArea(con_0)
1508.0

2. 轮廓近似

  • 为了让轮廓更加“流畅”,可以采取轮廓近似
  • 下图中间便是近似,以直代“曲”

轮廓近似.PNG

  • 核心思想就是设定一个阈值,然后在此阈值范围内,以直代曲
res0 = cv2.drawContours(img.copy(), cons, 11, (0, 0, 255), 2) #原轮廓

epsilon = 0.05 * cv2.arcLength(con_0, True) # epsilon越小,则越接近原轮廓,否则便越偏离
approx = cv2.approxPolyDP(con_0,epsilon, True)

res_005 = cv2.drawContours(img.copy(), [approx], -1, (0, 0, 255), 2)


epsilon = 0.10 * cv2.arcLength(con_0, True)
approx = cv2.approxPolyDP(con_0, epsilon, True)

res_010 = cv2.drawContours(img.copy(), [approx], -1, (0, 0, 255), 2)

winds([res0, res_005,res_010])
  • 对比图如下,第一张为原图,epsilon的权值依次为:0.05、0.10

轮廓近似三图对比.PNG

五、边界矩形

# 最后周一遍流程
img = cv2.imread('Outlook.png', -1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

_,thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 检测
cons, hier = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# 绘制
res0 = cv2.drawContours(img.copy(), cons, -1, (0, 0, 255), 1)
# cv_show('Test', res0)
## 绘制边界矩形
con_0 = cons[0]

# 绘制图像边界框可能会将图片截除,因为轮廓有内外之分
x, y, w, h = cv2.boundingRect(con_0) #先探测
r0 = cv2.rectangle(img.copy(), (x, y), (x + w, y + h), (0, 255, 0) , 2) # 会改变原图..

con_1 = cons[1]
x, y , w ,h = cv2.boundingRect(con_1)
r1 = cv2.rectangle(img.copy(),(x, y), (x + w, y + h),(0, 255, 0), 2)


winds([r0, r1])

对比图:

内外轮廓边界矩形.PNG

area = cv2.contourArea(con_0)

x, y ,w, h = cv2.boundingRect(con_0)
rect_area = w * h

extent = float(area) / rect_area # 轮廓面积和边界矩形面积之比
extent
0.5353821907013396

六、外接圆

(x, y), radius = cv2.minEnclosingCircle(con_0)
center = (int(x), int(y))
radius = int(radius)
res = cv2.circle(img.copy(), center, radius, (0, 255, 0), 2)
cv_show('Test', res)

效果图:

外接圆.PNG

area = 3.14 * radius * radius
extent = cv2.contourArea(con_0) / area
extent # 效果不如矩形框
0.42921925560720564