OpenCV主要通过findContours()函数来查找检测物体的轮廓,返回轮廓线上所有的像素点坐标,多个轮廓的序号、层级关系。
def findContours(image: Any, mode: Any, method: Any, contours: Any = None, hierarchy: Any = None, offset: Any = None) -> None
参数解释:
image:输入的图像,单通道二值图像,可以使用compare, inRange, threshold , adaptiveThreshold, Canny等函数创建得到。
mode:cv.RETR_EXTERNAL只识别外部轮廓;cv.RETR_LIST识别所有轮廓不记录层级关系;cv.RETR_TREE识别轮廓及树形的层级关系;cv.RETR_CCOMP检测所有轮廓,但所有轮廓都只建立两个等级关系。
method:cv.CV_CHAIN_APPROX_NONE 边界上所有连续的轮廓点; cv.CHAIN_APPROX_SIMPLE仅保存轮廓的拐点信;息 cv.CHAIN_APPROX_TC89_L1一种算法 cv.CHAIN_APPROX_TC89_KCOS另一种算法
写程序演示下,原始图片如下:
首先打开图片,进行二值化处理:
import cv2 as cvimg = cv.imread("resouse/contour1.png")imgGray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 转成灰度图imgBlur = cv.GaussianBlur(imgGray, (3, 3), 1) # 高斯降噪imgCanny = cv.Canny(imgBlur, 50, 50) # 转成二值轮廓图像# # 后续代码会添加到这一位置#cv.imshow("Original", img) # 显示原始图像cv.imshow("Gray", imgGray) # 显示灰度图cv.imshow("Canny", imgCanny) # 显示二值图cv.waitKey(0)
抽取轮廓之前,我们先准备一个绘画轮廓的黑板,用来显示我们抽取的轮廓:
imgContour = img.copy()imgContour[:, :, :] = 0 # 置成黑背景(使用了numpy切片操作
正式获取轮廓:
contours, hierarchy = cv.findContours(imgCanny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
返回的hierarchy用来描述轮廓的拓扑,每个轮廓返回一个,是个四元组,分别表示[Next, Previous, First_Child, Parent] 即下一个轮廓编号,前一个轮廓编号,第一个子轮廓,父轮廓。
显示下相关信息:
for cnt,hier in zip(contours,hierarchy[0]): print("==================") area = cv.contourArea(cnt) # 轮廓面积 print("hierarchy:", hier, "\nfirst pixel: ", cnt[:1], "\npixel nu: ", len(cnt), "\narea: ", area) # 以后代码默认处于此for循环中
在我们制作的幕布上绘制轮廓:
for cnt,hier in zip(contours,hierarchy[0]): # ...other codes... cv.drawContours(imgContour, cnt, -1, (0, 0, 252), 1) # 绘制轮廓 # ...other codes...cv.imshow("Contour",imgContour)
获取轮廓的关键点,可以通过点的数量位置来判断是什么形状:
for cnt,hier in zip(contours,hierarchy[0]): # ...other codes... perimeter = cv.arcLength(cnt, True) # 轮廓周长 approx = cv.approxPolyDP(cnt, 0.01 * perimeter, True) # 近似多边形,道格拉斯皮克算法 approximate polygon Douglas-Peucker print('spot nu:', len(approx)) cv.drawContours(imgContour, approx, -1, (0, 255, 0), 6) # 绘制关键点 # ...other codes...cv.imshow("Contour",imgContour)
获取轮廓的外框:
for cnt,hier in zip(contours,hierarchy[0]): # ...other codes... x, y, w, h = cv.boundingRect(approx) # 获取点集的边界矩形 # x,y,w,h分别是矩形坐标点和宽度高度 cv.rectangle(imgContour, (x, y), (x + w, y + h), (224, 0, 0), 1) # 绘制矩形 # ...other codes...cv.imshow("Contour",imgContour)
以下是完整的代码:
import cv2 as cvimg = cv.imread("resouse/contour1.png")imgGray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)imgBlur = cv.GaussianBlur(imgGray, (3, 3), 1) # 高斯降噪imgCanny = cv.Canny(imgBlur, 50, 50)cv.imshow("Original", img)cv.imshow("Gray", imgGray)cv.imshow("Blur", imgBlur)cv.imshow("Canny", imgCanny)imgContour = img.copy()imgContour[:, :, :] = 0 # 置成黑背景print(imgContour.shape)contours, hierarchy = cv.findContours(imgCanny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE) # 发现形状#print(contours)print(hierarchy)for cnt,hier in zip(contours,hierarchy[0]): print("==================") area = cv.contourArea(cnt) # 轮廓面积 print("hierarchy:", hier, "\nfirst pixel: ", cnt[:1], "\npixel nu: ", len(cnt), "\narea: ", area) # 绘制轮廓 cv.drawContours(imgContour, cnt, -1, (0, 0, 252), 1) # 获取关键点 perimeter = cv.arcLength(cnt, True) # 轮廓周长 approx = cv.approxPolyDP(cnt, 0.02 * perimeter, True) # 近似多边形,道格拉斯皮克算法 approximate polygon Douglas-Peucker print('spot nu:', len(approx)) cv.drawContours(imgContour, approx, -1, (0, 255, 0), 6) # 绘制轮廓 # 获取轮廓边框 x, y, w, h = cv.boundingRect(approx) # 获取点集的边界矩形 cv.rectangle(imgContour, (x, y), (x + w, y + h), (224, 0, 0), 1) # 绘制矩形cv.imshow("Contour",imgContour)cv.waitKey(0)cv.destroyAllWindows()