苦学Opencv的第十二天:图形检测

Python OpenCV从入门到精通学习日记: 图形检测

前言

图形检测是计算机视觉的一项重要功能。通过图像检测,我们可以分析出图像中可能存在的形状,然后我们可以对这些形状进行描绘,例如我们可以搜索并绘制图像的边缘,定位图像的位置,判断图像中是否存在直线或者圆形。虽然图形检测涉及到了非常深奥的数学算法,但是在Opencv中都已经将这些算法封装成了简单的方法,我们可以直接调用来实现检测功能。

图形检测
图像的轮廓
轮廓拟合
凸包
Canny边缘检测
霍夫变换

1 图像的轮廓

轮廓是指图像中图形或者物体的外边缘线条。简单的几何图形轮廓是由平滑的线构成的,容易识别,但是有些不规则的图形的轮廓可能由许多个点构成的,识别起来比较困难。

OpenCV提供的findContours()方法可以通过计算图像梯度来判断图像的边缘,然后将边缘的点封装成数组返回。

contours, hierarchy = cv2.findContours(image, mode,methode)

参数说明:
	image:被检测的图像,必须是8位单通道二值图像。如果原始图像是彩色图像,必须转为灰度图像,并经过二值化处理。
	mode:轮廓的检索模式,具体值如表1.1所示。
	methode:检测轮廓时使用的方法,具体值如表1.2所示。
返回值说明:
	contours:检测出的所有轮廓,list类型,每一个元素都是某个轮廓的像素坐标数组。
	hierarchy:轮廓之间的层次关系。

表格1.1 轮廓的检索模式参数值

参数值含义
CV2.RETR_EXTERNAL只检测外轮廓
CV2.RETR_LIST检测所有轮廓,但不建立层次关系
CV2.RETR_CCOMP检测所有轮廓,并建立两级层次关系
CV2.RETR_TREE检测所有轮廓,并建立树状结构的层次关系

表格1.2 检测轮廓时使用的方法

参数值含义
CV2.CHAIN_APPROX_NONE储存轮廓上的所有点
CV2.CHAIN_APPROX_SIMPLE只保存水平、垂直或对角线轮廓的端点
CV2.CHAIN_APPROX_TC89LITen-Chinl近似算法中的一种
CV2.CHAIN_APPROXTC89KCOSTen-Chinl近似算法中的一种

通过findContours()方法找到图像轮廓后,为了方便开发人员观测,最好能把轮廓画出来,于是OpenCV提供了drawContours()方法用来绘制这些轮廓。

image = cv2.drawContours(image, contours, contourIdx,color, thickness, lineTypee, hierarchy, maxLevel, offse)

参数说明:
	image:被绘制轮廓的原始图像,可以是多通道图像。
	contours:findContours()方法得出的轮廓列表。
	contourIdx:绘制轮廓的索引,如果为-1则绘制所有轮廓。
	color:绘制颜色,使用BGR格式。
	thickness:可选参数,画笔的粗细程度,如果该值为-1则绘制实心轮廓。
	lineTypee:可选参数,绘制轮廓的线型。
	hierarchy:可选参数,findContours()方法得出的层次关系。
	maxLevel:可选参数,绘制轮廓的层次深度,最深绘制第maxLevel层。
	offse:可选参数,偏移量,可以改变绘制结果的位置。
返回值说明:
	image:同参数中的image,执行后原始图中就包含绘制的轮廓了,可以不使用此返回值保存结果。

在这里举个例子,这是一个简单的图像:
在这里插入图片描述
我们运行如下代码:

import cv2
img = cv2.imread("shape1.png")  # 读取原图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 彩色图像转为变成单通道灰度图像
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)  # 灰度图像转为二值图像
# 检测图像中出现的所有轮廓,记录轮廓的每一个点
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 绘制所有轮廓,宽度为5,颜色为红色
cv2.drawContours(img, contours, -1, (0, 0, 255), 5)
cv2.imshow("img", img)  # 显示绘制结果
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

运行结果如下:
在这里插入图片描述

我们可以发现图像中的图像的轮廓都被选中并且用红色线条描绘了出来。

但是如果我们使用cv2.RETR_EXTERNAL作为参数,那么只会绘制外轮廓,这里给出关键代码:

contours, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

效果如下:
在这里插入图片描述

我们发现程序不再绘制内轮廓线了。

如果只是不想要绘制某个轮廓线而并非不想获得,那么我们可以通过调整drawContours()的第三个参数来指定绘制哪个索引的轮廓。索引的顺序由轮廓的检索模式决定。例如cv2.RETR_CCOMP模式下绘制索引为0的轮廓的关键代码如下:

contours, hierarchy = cv2.findContours(binary,cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img, contours, 0, (0, 0, 255), 5)

我们可以通过调整cv2.drawContours(img, contours, 0, (0, 0, 255), 5)的0来绘制不同索引的轮廓。

在这里我们以绘制花朵轮廓为例:

import cv2
img = cv2.imread("flower.png")  # 读取原图
cv2.imshow("img", img)  # 显示原图
img = cv2.medianBlur(img, 5)  # 使用中值滤波去除噪点
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 原图从彩图变成单通道灰度图像
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)  # 灰度图像转化为二值图像
cv2.imshow("binary", binary)  # 显示二值化图像
# 获取二值化图像中的轮廓极轮廓层次数据
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)  # 在原图中绘制轮廓
cv2.imshow("contours", img)  # 显示绘有轮廓的图像
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

运行结果如下:
在这里插入图片描述

2 轮廓拟合

拟合是指将平面上的一系列点,用一条光滑的曲线连接起来。轮廓的拟合就是将凹凸不平的轮廓用平整的几何图形体现出来。接下来学习如何按照轮廓绘制矩形包围框和圆形包围框。

  • 2.1 矩形包围框
    矩形包围框是指图像轮廓的最小矩形边界。OpenCV提供的boundingRect()方法可以自动计算轮廓最小矩形边界的坐标、宽和高。
retval = cv2.boundingRect (array)

参数说明:
	array:轮廓数组。
返回值说明:
	retval:元组类型,包含4个整数值,分别是最小矩形包围框的:左上角顶点的横坐标、左上角顶点的纵坐标、矩形的宽和高。所以也可以写成x, y, w, h = cv2.boundingRect(array)的形式。

示例:为一个爆炸图像绘制矩形包围框。

import cv2
img = cv2.imread("shape2.png")  # 读取原图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 从彩色图像变成单通道灰度图像
# 将灰度图像进行二值化阈值处理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 获取二值化图像中的轮廓极轮廓层次数据
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])  # 获取第一个轮廓的最小矩形边框,记录坐标和宽高
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)  # 绘制红色矩形
cv2.imshow("img", img)  # 显示绘制结果
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

运行结果如下:
在这里插入图片描述

  • 2.2 圆形包围框
    圆形包围框与矩形包围框一样,是图像轮廓的最小圆形边界。OpenCV提供的minEnclosingCircle ()方法可以自动计算轮廓最小圆形边界的圆心和半径。
center, radius = cv2.minEnclosingCircle(points)

参数说明:
	points:轮廓数组。
返回值说明:
	center:元组类型,包含2个浮点值,是最小圆形包围框圆心的横坐标和纵坐标。
	radius:浮点类型,最小圆形包围框的半径。

示例:为爆炸图像绘制圆形包围框,绘制过程中要注意:圆心坐标和圆半径都是浮点数,在绘制之前要将浮点数转换成整数。

import cv2
img = cv2.imread("shape2.png")  # 读取原图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 从彩色图像变成单通道灰度图像
# 将灰度图像进行二值化阈值处理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 获取二值化图像中的轮廓极轮廓层次数据
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
center, radius = cv2.minEnclosingCircle(contours[0])  # 获取最小圆形边框的圆心点和半径
x = int(round(center[0]))  # 圆心点横坐标转为近似整数
y = int(round(center[1]))  # 圆心点纵坐标转为近似整数
cv2.circle(img, (x, y), int(radius), (0, 0, 255), 2)  # 绘制圆形
cv2.imshow("img", img)  # 显示绘制结果
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

结果如下:
在这里插入图片描述

3 凸包

之前学习了矩形包围框和圆形包围框,这2种包围框虽然已经逼近了图形的边缘,但这种包围框为了保持几何形状,与图形的真实轮廓贴合度较差。如果能找出图形最外层的端点,将这些端点连接起来,就可以围出一个包围图形的最小包围框,这种包围框叫凸包。凸包是最逼近轮廓的多边形,凸包的每一处都是凸出来的,也就是任意3个点组成的内角均小于180°。

OpenCV提供的convexHull()方法可以自动找出轮廓的凸包。

hull = cv2.convexHull(points, clockwise, returnPoints)

参数说明:
	points:轮廓数组。
	clockwise:可选参数,布尔类型。当该值为True时,凸包中的点按顺时针排列,为False时按逆时针排列。
	returnPoints:可选参数,布尔类型。当该值为True时返回点坐标,为False时返回点索引。默认值为True。
返回值说明:
	hull:凸包的点阵数组。

示例:为爆炸图像绘制凸包。

import cv2

img = cv2.imread("shape2.png")  # 读取原始图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图像
ret, binary = cv2.threshold(gray, 127, 225, cv2.THRESH_BINARY)  # 二值化阈值处理
# 检测图像中出现的所有轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0])  # 获取轮廓的凸包
cv2.polylines(img, [hull], True, (0, 0, 255), 2)  # 绘制凸包
cv2.imshow("img", img)  # 显示图像
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

结果如下:
在这里插入图片描述

4 Canny边缘检测

Canny边缘检测算法是John F. Canny于1986年开发的一个多级边缘检测算法,该算法根据像素的梯度变化寻找图像边缘,最终可以绘制十分精细的二值边缘图像。

OpenCV将Canny边缘检测算法封装在Canny()方法中。

edges = cv2.Canny(image, threshold1, threshold2,apertureSize, L2gradient)

参数说明:
	image:检测的原始图像。
	threshold1:计算过程中使用的第一个阈值,可以是最小阈值,也可以是最大阈值,通常用来设置最小阈值。
	threshold2:计算过程中使用的第二个阈值,通常用来设置最大阈值。
	apertureSize:可选参数,Sobel算子的孔径大小。
	L2gradient:可选参数,计算图像梯度的标识,默认值为False。值为True时采用更精准的算法进行计算。
返回值说明:
	edges:计算后得出的边缘图像,是一个二值灰度图像。

在开发过程中可以通过调整最小阈值和最大阈值控制边缘检测的精细程度。当2个阈值都较小时,检测出较多的细节;当2个阈值都较大时,忽略较多的细节。

示例:利用Canny()方法检测花朵图像,分别使用10和50、100和200、400和600作为最低阈值和最高阈值检测3次。

import cv2
img = cv2.imread("flower.png")  # 读取原图
r1 = cv2.Canny(img, 10, 50);  # 使用不同的阈值进行边缘检测
r2 = cv2.Canny(img, 100, 200);
r3 = cv2.Canny(img, 400, 600);

cv2.imshow("img", img)  # 显示原图
cv2.imshow("r1", r1)  # 显示边缘检测结果
cv2.imshow("r2", r2)
cv2.imshow("r3", r3)
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

结果如下:
在这里插入图片描述

🌟从结果可以看出:阈值越小,检测出的边缘越多;阈值越大,检测出的边缘越少,只能检测出一些较明显的边缘。

5 霍夫变换

霍夫变换是一种特征检测,通过算法识别图像的特征,从而判断图像中的特殊形状,例如直线和圆。我们将学习如何检测图像中的直线和圆。

  • 5.1 直线检测
    霍夫直线变换是通过霍夫坐标系的直线与笛卡儿坐标系的点之间的映射关系来判断图像中的点是否构成直线。OpenCV将此算法封装成两个方法,分别是cv2.HoughLines()cv2.HoughLinesP(),前者用于检测无限延长的直线,后者用于检测线段。我们看看比较常用的HoughLinesP()方法。HoughLinesP()方法名称最后有一个大写的P,该方法只能检测二值灰度图像,也就是只有两种像素值的黑白图像。该方法最后把找出的所有线段的两个端点坐标保存成一个数组。
lines = cv2.HoughLinesP(image, rho, theta, threshold,minLineLength, maxLineGap)

参数说明:
	image:检测的原始图像。
	rho:检测直线使用的半径步长,值为1时,表示检测所有可能的半径步长。
	theta:搜索直线的角度,值为π/180°时,表示检测所有角度。
	threshold:阈值,该值越小,检测出的直线就越多。
	minLineLength:线段的最小长度,小于该长度的线段不记录到结果中。
	maxLineGap:线段之间的最小距离。
返回值说明:
	lines:一个数组,元素为所有检测出的线段,每条线段是一个数组,代表线段两个端点的横、纵坐标,格式为[[[x1, y1,x2, y2], [x1, y1, x2, y2]]]。

🌟使用该方法前应该为原始图像进行降噪处理,否则会影响检测结果。

示例:

import cv2
import numpy as np

img = cv2.imread("pen.jpg")  # 读取原图
o = img.copy()  # 复制原图
o = cv2.medianBlur(o, 5)  # 使用中值滤波进行降噪
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)  # 从彩色图像变成单通道灰度图像
binary = cv2.Canny(o, 50, 150)  # 绘制边缘图像
# 检测直线,精度为1,全角度,阈值为15,线段最短100,最小间隔为18
lines = cv2.HoughLinesP(binary, 1, np.pi / 180, 15, minLineLength=100, maxLineGap=18)
for line in lines:  # 遍历所有直线
    x1, y1, x2, y2 = line[0]  # 读取直线两个端点的坐标
    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)  # 在原始图像上绘制直线
cv2.imshow("canny", binary)  # 显示二值化边缘图案
cv2.imshow("img", img)  # 显示绘制结果
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

运行结果如下:
在这里插入图片描述

  • 5.2 圆环检测
    霍夫圆环变换的原理与霍夫直线变换类似。OpenCV提供的HoughCircles()方法用于检测图像中的圆环,该方法在检测过程中进行两轮筛选:第一轮筛选找出可能是圆的圆心坐标,第二轮筛选计算这些圆心坐标可能对应的半径长度。该方法最后将圆心坐标和半径封装成一个浮点型数组
circles = cv2.HoughCircles(image, method, dp, minDist,param1, param2, minRadius, maxRadius)

参数说明:
	image:检测的原始图像。
	method:检测方法,OpenCV 4.0.0及以前版本仅提供了cv2.HOUGH_GRADIENT作为唯一可用方法。
	dp:累加器分辨率与原始图像分辨率之比的倒数。值为1时,累加器与原始图像具有相同的分辨率;值为2时,累加器的分辨率为原始图像的1/2。通常使用1作为参数。
	minDist:圆心之间的最小距离。
	param1:可选参数,Canny边缘检测使用的最大阈值。
	param2:可选参数,检测圆环结果的投票数。第一轮筛选时投票数超过该值的圆环才会进入第二轮筛选。值越大,检测出的圆环越少,但越精准。
	minRadius:可选参数,圆环的最小半径。
	maxRadius:可选参数,圆环的最大半径。
返回值说明:
	circles:一个数组,元素为所有检测出的圆环,每个圆环也是一个数组,内容为圆心的横、纵坐标和半径长度,格式为:[[[x1 ,y1, r1], [x2 ,y2, r2]]]。

示例:

import cv2
import numpy as np

img = cv2.imread("coin.jpg")  # 读取原图
o = img.copy()  # 复制原图
o = cv2.medianBlur(o, 5)  # 使用中值滤波进行降噪
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)  # 从彩色图像变成单通道灰度图像
# 检测圆环,圆心最小间距为70,Canny最大阈值为100,投票数超过25。最小半径为10,最大半径为50
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 70, param1=100, param2=25, minRadius=10, maxRadius=50)
circles = np.uint(np.around(circles))  # 将数组元素四舍五入成整数
for c in circles[0]:  # 遍历圆环结果
    x, y, r = c  # 圆心横坐标、纵坐标和圆半径
    cv2.circle(img, (x, y), r, (0, 0, 255), 3)  # 绘制圆环
    cv2.circle(img, (x, y), 2, (0, 0, 255), 3)  # 绘制圆心
cv2.imshow("img", img)  # 显示绘制结果
cv2.waitKey()  # 按下任何键盘按键后
cv2.destroyAllWindows()  # 释放所有窗体

运行结果如下:
在这里插入图片描述

6 小结

今天学了如何获得图形的轮廓和如何绘制出来,Canny()绘制出的轮廓真的很清楚,但值得注意的是:Canny()方法虽然能够检测出图像的边缘,但这个边缘是不连续的。

明天继续学习视频处理。

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值