OpenCV---009(目标检测--轮廓检测)
轮廓检测
图像轮廓 指对图像中的对象的边界 是图像目标的外部特征
轮廓的发现和绘制
图像的轮廓不仅仅能够提供物体的边缘 还可以提供物体边缘间的层次关系 及 拓扑关系
我们可以将图像轮廓看为 带有 结构关系的边缘检测 这种结构关系可以表面图像中的连通域或者某些区域的关系
定义轮廓由外到内 级别越来越低
为了表示轮廓间的关系
一般使用4个参数来表示不同的个层级间关系、
分别为 同层的下一个轮廓索引 同层的上一个轮廓索引 下一层的第一个子轮廓索引 上一层的父轮廓索引
不存在的用-1表示
比如最外层的轮廓 [-1 -1 1 -1]
opencv提供了可以在二值图像中检测图像的所有轮廓并生成不同轮廓关系的函数
contours,hierarchy = cv.findContours(img,mode,method,offset)
mode 轮廓检测模式的标志
0: 只检测最外层轮廓
1: 提取所有轮廓 但是不建立等级关系
2: 提取所有轮廓 并组织为双层结构 顶层为外围 次层内层边界
3: 提取所有的轮廓 并建立网状轮廓结构
method 轮廓逼近方法的标志
1: 获得每个轮廓的每个像素 相邻两点的距离不超过1
2: 压缩水平 垂直方向 只保留该方向的终点坐标 如矩形仅保留四个点
contouts 轮廓的像素坐标
用于存放检测到的轮廓 以ndarry的方式存放在list中 每个ndarry存放的是坐标
hierarchy 轮廓结构关系描述向量
放在一个 1 * N * 4 的对象中
offset 每个轮廓点移动的可选偏移量 主要用在从ROI图像找到轮廓并基于整个图像分析轮廓的场景
提取轮廓后为了能直观的得到轮廓 所以 opencv提供了函数
img = cv.drawContours(img, contours,contourIdx,color,thickness,lineType)
contourIdx 绘制轮廓数目
注意: 这里返回的img 是 img本身
点击查看代码
import cv2 as cv
import sys
def func(x):
pass
if __name__ == '__main__':
cv.namedWindow('res')
cv.createTrackbar('thre', 'res', 50, 255, func)
img = cv.imread('img_7.png')
if img is None:
sys.exit()
img_old = img.copy()
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.GaussianBlur(img, (9, 9), 10, 20)
cv.imshow('img', img)
while True:
img_change = img.copy()
thr = cv.getTrackbarPos('thre', 'res')
_, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
img_change = cv.morphologyEx(img_change, 2, (3, 3))
cv.imshow('res', img_change)
contours, hierarchy = cv.findContours(img_change, 3, 2)
img_draw = cv.drawContours(img_old, contours, -1, (255, 0, 0), 1, 8)
print(contours)
print(hierarchy)
cv.imshow('draw', img_draw)
if cv.waitKey(1) == ord('q'):
break
cv.destroyAllWindows()


轮廓检测也可以检测边缘的轮廓
轮廓面积
轮廓面积是重要的统计特性之一 通过轮廓面积大小 可以进一步分析每个轮廓隐含的信息
如通过 轮廓面积区分物体大小 识别不同的物体 轮廓面积是指每个轮廓中的所有像素围成的区域面积
单位为像素
opencv提供了计算面积函数
retval = cv.contourAread(contours,oriented)
contours 是一个轮廓 上面得到的是一个list 用索引取值后在带入!!
oriented: 面积是否有方向性
轮廓顶点顺时针给出 和 逆时针给出 时 计算的面积值互为相反数
默认为 False
轮廓长度
轮廓长度也是轮廓重要的统计特征之一 虽然1轮廓长度无法直接反应轮廓的大小 和 形状 但是可以与轮廓面积集合
得到关于轮廓区域的更多信息
retval = cv.arcLength(curve,closed)
curve: 轮廓
closed: 轮廓 是否闭合
如计算一个三角形的轮廓 若闭合 就是 计算周长 不闭合 则计算 两两点间的长度
轮廓外接多边形
由于噪声和光照的影响 轮廓会出现不规则的形状 不规则的轮廓形状不利于对图像内容进行分析。
所以需要将物体的轮廓拟合为规则的几何图像
求取轮廓最大外接矩阵的函数
retval = cv.boundingRect(arry)
arry 表示灰度图或者点集
retval: 一个元组 里面有四个参数
分别是 左上角 x y 宽 高
最小外界矩阵:
retval = cv.minAreaRect(points)
retval: (x,y) (w,h) angle
点击查看代码
import cv2 as cv
import sys
def func(x):
pass
def get_area(contour):
area = cv.contourArea(contour)
return area
def get_length(contour):
length = cv.arcLength(contour, True)
return length
def get_box(contour):
retval = cv.boundingRect(contour)
return retval
def draw_box(retval, img):
x, y, w, h = retval
img = cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
return img
if __name__ == '__main__':
cv.namedWindow('res')
cv.createTrackbar('thre', 'res', 50, 255, func)
img = cv.imread('img_7.png')
if img is None:
sys.exit()
img_old = img.copy()
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.GaussianBlur(img, (9, 9), 10, 20)
cv.imshow('img', img)
while True:
img_change = img.copy()
thr = cv.getTrackbarPos('thre', 'res')
_, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
img_change = cv.morphologyEx(img_change, 2, (3, 3))
cv.imshow('res', img_change)
img_change = cv.Canny(img_change, 50, 100, apertureSize=3)
cv.imshow('canny', img_change)
contours, hierarchy = cv.findContours(img_change, 3, 2)
img_draw = img_old.copy()
for i in contours:
recval = get_box(i)
img_draw = draw_box(recval, img_draw)
cv.imshow('draw',img_draw)
# img_draw = cv.drawContours(img_old.copy(), contours, -1, (255, 0, 0), 1, 8)
# cv.imshow('draw', img_draw)
if cv.waitKey(1) == ord('q'):
break
cv.destroyAllWindows()
效果如下:

有时候矩形会产生较大的误差 所以用多边形围成的面积会更接近真实的轮廓面积
轮廓拟合 将 一组轮廓拟合为一个多边形的顶点
approxCurve = cv.approxPolyDP(curve,epsilon,closed)
approxCurve: 以多边形顶点的形式给出
curve: 输入轮廓
epsilon: 精度
closed: 是否闭合
效果如下

可以根据多边形的边数来判断大体形状
轮廓到点的距离
点到轮廓的距离对于计算机在图像中的位置 两轮廓的距离 以及 确定图像上某一点是否在轮廓内部有重要的作用
retval = cv.pointPolygonTest(contour,pt,measureDist)
contour: 轮廓
pt: 需计算的像素点
measureDist: 统计的距离是否有方向性
该函数可以计算像素到轮廓的最短距离 并且以float的形式返回
第一个参数 保存在 ndarry中
第三个参数 当为False时,仅判断像素与轮廓间的位置关系
-1 在轮廓外部 1 在轮廓内部 0 在轮廓边缘
若为True: 则有方向性 正数在轮廓外部 负数在内部
凸包检测
有时候图像过于复杂 即使使用多边形进行拟合处理 后 还是过于复杂 形状较复杂的图像可以使用凸包检测来近似表示
凸包是图形学中常见的概念 将二维平面上点集最外层的点连接起来构成的凸多边形称为凸包。虽然凸包检测
也是对轮廓的多边形逼近 但是逼近结果一定是凸多边形
opencv给出函数:
hull = cv.convexHull(points,hull,clockwise,returnPoints)
points: 输入轮廓坐标
hull: 输出凸包的顶点
返回一个list 或者ndarry
clockwise: 方向标志
returnPoints: 为True时 输出的是凸包顶点坐标,放在ndarry中 为False时 输出的是索引 放在list
当范围list时 用索引在轮廓点集内提取出来顶点 但是 不能直接用 cv.drawContours画出
因为他接受的是一个list list内每一个元素都单独是一个轮廓 所以会画出很多的点
效果如下:

点击查看代码
import cv2 as cv
import sys
import numpy as np
def func(x):
pass
def get_area(contour):
area = cv.contourArea(contour)
return area
def get_length(contour):
length = cv.arcLength(contour, True)
return length
def get_box(contour):
retval = cv.boundingRect(contour)
return retval
def draw_box(retval, img):
x, y, w, h = retval
img = cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
return img
def draw_shape(contour, img):
appr = cv.approxPolyDP(contour, 1, True)
# print(sum(appr))
center = np.int0((sum(appr)[0] // len(appr)))
center = (center[0], center[1])
img = cv.drawContours(img, [appr], -1, (0, 255, 0), 2, 8)
img = cv.circle(img, center, 3, (0, 0, 255), -1)
return img
def out_bag(contour, img):
hull = cv.convexHull(contour, returnPoints=True)
img = cv.drawContours(img, [hull], -1, (255, 0, 0), 2, 8)
return img
if __name__ == '__main__':
cv.namedWindow('res')
cv.createTrackbar('thre', 'res', 50, 255, func)
img = cv.imread('img_7.png')
if img is None:
sys.exit()
img_old = img.copy()
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.GaussianBlur(img, (9, 9), 10, 20)
cv.imshow('img', img)
point = (100, 100)
while True:
img_change = img.copy()
thr = cv.getTrackbarPos('thre', 'res')
_, img_change = cv.threshold(img_change, thr, 255, cv.THRESH_BINARY_INV)
img_change = cv.morphologyEx(img_change, 2, (3, 3))
cv.imshow('res', img_change)
img_change = cv.Canny(img_change, 50, 100, apertureSize=3)
cv.imshow('canny', img_change)
contours, hierarchy = cv.findContours(img_change, 3, 2)
img_draw = img_old.copy()
# for i in contours:
# img_draw = draw_shape(i, img_draw)
for i in contours:
img_draw = out_bag(i, img_draw)
for i in range(len(contours)):
dis = cv.pointPolygonTest(contours[i], point, measureDist=True)
if dis > 0:
print('点距离第{}个轮廓{},在轮廓外'.format(i, dis))
if dis < 0:
print('点距离第{}个轮廓{},在轮廓内'.format(i, dis))
if dis == 0:
print('点距离第{}个轮廓{},在轮廓上'.format(i, dis))
# for i in contours:
# recval = get_box(i)
# img_draw = draw_box(recval, img_draw)
cv.imshow('draw', img_draw)
# img_draw = cv.drawContours(img_old.copy(), contours, -1, (255, 0, 0), 1, 8)
# cv.imshow('draw', img_draw)
if cv.waitKey(1) == ord('q'):
break
cv.destroyAllWindows()

浙公网安备 33010602011771号