18. 轮廓发现-cv2.findContours()、cv2.drawContours()
1. 定义
在进行对象轮廓检测时,我们主要依赖于图像边缘提取这一技术作为基础方法。因此, 选择合适的边缘检测阈值将直接影响到最终的检测结果。
该特征可被视为由连续的点(附着边界)连接而成的一条曲线,并具备相同的色调或亮度。
这一特征在形状分析、物体检测以及识别方面具有重要意义。
为了提高准确性, 通常会采用二值化图像。
在进行边缘检测之前(如Canny边缘检测), 一般会对原始图像进行二值化处理。
若需后续操作原始图像, 则应在程序开始前将原始图像存入其他变量。
与OpenCV中的边缘提取类似, 在黑色背景中寻找白色物体时等同于寻找边缘。
2. 实现函数-cv2.findContours()、cv2.drawContours()
2.1 轮廓发现-cv2.findContours()
参数如下: cv2.findContours( (Image数组 image, 轮廓数组 contours, 水平层次信息 hierarchy, 整数模式 mode, 方法名称 method, 偏移量 offset = DefaultPoint() )
参数说明:
image为单通道图像矩阵,在实际应用中通常表示为灰度图;其中二值图像更为常见;这些二值图像通常是通过Canny和拉普拉斯等边缘检测算子处理得到的。
contours为一矢量,
该矢量内部每个元素存储一个由连续Point构成的一组点集,
每一个Point集合即代表一个轮廓。
则该矢量中存有与之对应的多个Point集合数目。
hierarchy字段定义为vector
4. mode:int类型的,定义轮廓的检索模式:
CV_RETR_EXTERNAL仅识别最外围的边界线,并不考虑位于外围边界线内的内部边界线。
2):OpenCV的CV_RETR_LIST方法会遍历所有边界(内外边界),但该方法不会定义任何层次结构关系,在这种检索模式下所有的外边界和内边界都是相互独立存在的。因此,在hierarchy矩阵中与这些外边界相关联的第三和第四个元素都会被赋值为-1(后续内容会详细说明)。
CV_RETR_CCOMP用于遍历所有的边缘,并根据层级结构将这些边缘分为两种关系类型:最外层边界作为主层次区域;当内部区域包含其他细节时,则将这些细节所属的所有子区域归入主层次中
- :CV_RETR_TREE, 对所有边界进行遍历并构建层次结构树。外围层级将包含内部层级的边界元素,并且这些内部层级的边界元素还可以进一步嵌套。
5. method:int类型,定义轮廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE用于记录所有连续的轮廓点并将其包含在contours向量中;
第二种情况:CV_CHAIN_APPROX_SIMPLE仅记录轮廓折线顶点的信息,并将这些折线顶点坐标存储到contours向量中;它不会包含相邻折线段之间直线上的其他中间像素坐标
选择第三和第四项:CV_CHAIN\_APPROX\_TC89\_L1 和 CV_CHAIN\_APPROX\_TC89\_KCOS 采用了 Teh-Chinℓ 近似算法。
6. Point:offset, 所有的contour information相对于original image corresponding points的offset value等价于在每一个detection到的contour point上添加相应的offset value, 并且该offset value还可以取负数值.
2.2 轮廓显示 - cv2.drawContours()
- 参数如下: cv2.drawContours(img, contours, contourldx, color, thickness)
参数说明:1. img:绘制在图像上的轮廓 2. 所有被识别出的边缘 3. 边缘对应的索引号 4. 颜色参数 5. 线条宽度参数
3. 实现代码
import cv2 as cv
import numpy as np
def edge_demo(image):
blurred = cv.GaussianBlur(image, (3, 3), 0)
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
# grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
# grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
# edge_output = cv.Canny(grad_x, grad_y, 30, 150)
edge_output = cv.Canny(gray, 50, 150)
return edge_output
def contours_demo(image):
"""
. 轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同 的颜色或者灰度。
轮廓在形状分析和物体的检测和识别中很有用。
. 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者 Canny 边界检测
. 查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图像的话,
你应该将原始图像存储到其他变量中.
. 在 OpenCV 中,查找轮廓就像在黑色背景中超白色物体。要找的物体应该是白色而背景应该是黑色。
26. 27. """
# dst = cv.GaussianBlur(image, (3, 3), 0)
# gray = cv.cvtColor(dst, cv.COLOR_BGR2GRAY)
# ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
# cv.imshow("binary image", binary)
binary = edge_demo(image)
"""
• 函数 cv2.findContours() 有三个参数, 第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。
• 检索模式:
• CV_RETR_EXTERNAL - 只提取外层的轮廓
• CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中
• CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy:
顶层为连通域的 外围边界,次层为洞的内层边界。
• CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy
• 逼近方法 (对所有节点, 不包括使用内部逼近的 CV_RETR_RUNS). 点的存贮情况,是不是都被存贮
• CV_CHAIN_CODE - Freeman 链码的输出轮廓. 其它方法输出多边形(定点序列).
• CV_CHAIN_APPROX_NONE - 将所有点由链码形式翻译为点序列形式
• CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素 点;
• CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链逼近算法.
• CV_LINK_RUNS - 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用.
• offset:每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析.
• 返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。
轮廓(第二个返回值)是一个 Python 列表,其中存储这图像中的所有轮廓。
每一个轮廓都是一个 Numpy 数组,包含对象边界点(x,y)的坐标。
"""
contours, hierarchy= cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for i, contour in enumerate(contours):
# 函数 cv2.drawContours() 可以被用来绘制轮廓。它可以根据你提供的边界点绘制任何形状。
# 它的第一个参数是原始图像,第二个参数是轮廓,一个 Python 列表。
# 第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设 置为 -1 时绘制所有轮廓)。
# 接下来的参数是轮廓的颜色和厚度等。
cv.drawContours(image, contours, i, (0, 0, 255), 2) # 2为像素大小,-1时填充轮廓
print(i)
cv.imshow("detect contours", image)
def main():
src = cv.imread("../images/circle.png")
# cv.imshow("demo",src)
contours_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows() # 关闭所有窗口
if __name__ == '__main__':
main()
代码解读
