一种基于opencv的分辨圆形,三角形,矩形的思路
题目要求是分辨出一个随机颜色(红色,绿色,蓝色)的图形(矩形,圆形,三角形)。
上篇文章给大家讲了基于openmv的思路,这篇文章大致讲讲如何用opencv来做。
我事先查了一下,我这个方法不知道有多少人早就用过了。(可能是因为当时我也是疯狂查出来的,已经记忆模糊了)他们讲的比我详细多了,我就简单说说思路。
我的思路是:色块识别+轮廓提取+角点检测
1. 色块识别:
色块识别是很基础的一个操作了,也比较简单。直接看代码:
lower_red_1 = np.array([0, 80, 128]) #先找出HSV色彩空间红绿蓝三种颜色的大致范围。红色有两个是因为hsv空间中,色相h最上面和最下面都是红色。可以看下面这张图你就懂了。
upper_red_1 = np.array([6, 255, 255])
lower_red_2 = np.array([170, 110, 128])
upper_red_2 = np.array([180, 255, 255])
lower_green = np.array([35, 80, 80])
upper_green = np.array([77, 255, 255])
lower_blue = np.array([90, 110, 110])
upper_blue = np.array([124, 255, 255])
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV) #frame就是输入的图像
red_mask_1 = cv.inRange(hsv,lower_red_1,upper_red_1) #将图像二值化,在lower和upper之间的颜色变为白色,其他全为黑色
red_mask_2 = cv.inRange(hsv,lower_red_2,upper_red_2)
red_mask = cv.bitwise_or(red_mask_1, red_mask_2) #两种红色统一
green_mask = cv.inRange(hsv, lower_green, upper_green)
blue_mask = cv.inRange(hsv, lower_blue, upper_blue)
上面这一段不懂的话可以看看这篇文章,这位博主讲得很好(侵删)
red_res = cv.bitwise_or(frame, frame, mask = red_mask) #或运算,将彩色图像中红色部分选中,忽略其余颜色
green_res = cv.bitwise_and(frame, frame, mask = green_mask)
blue_res = cv.bitwise_and(frame, frame, mask = blue_mask)
red_gray = cv.cvtColor(red_res, cv.COLOR_BGR2GRAY) #转灰度图
green_gray = cv.cvtColor(green_res, cv.COLOR_BGR2GRAY)
blue_gray = cv.cvtColor(blue_res, cv.COLOR_BGR2GRAY)
final_gray = cv.bitwise_or(red_res, green_res) #将选出的红色,蓝色,绿色都集成起来
final_gray = cv.bitwise_or(final_gray, blue_res)
final_gray = cv.cvtColor(final_gray,cv.COLOR_BGR2GRAY) #得到最终的灰度图。就是下一步轮廓提取的输入
2. 轮廓提取
轮廓提取的话,opencv有自己的API,好用的很。
__, contours,hierarchy = cv.findContours(final_gray, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
#这个地方要注意一下。这个函数根据版本不同,返回的值可能有两个或者三个。如果opencv版本比较新,就只有后面两个返回值。如果比较旧(我代码是在树莓派上跑的,所以比较旧),就有三个返回值。不过我们只用到countours这个返回值就行。
for cnt in range(len(contours)): #对检测到的每个轮廓遍历
p = cv.arcLength(contours[cnt],True) #p是Perimeter周长的意思,当时偷懒了
area = cv.contourArea(contours[cnt]) #area是该轮廓的像素面积
3. 角点检测
原理我也没啥好说的,毕竟这也不是计算机视觉原理专栏,调用api就完事了。
for cnt in range(len(contours)):
p = cv.arcLength(contours[cnt],True)
area = cv.contourArea(contours[cnt])
if area > 2500:
mm = cv.moments(contours[cnt]) #计算图像轮廓中的中心矩,原理见代码后链接
if mm['m00'] != 0: #如果算出了中心距(感觉这里应该用try,except,当时写的不严谨)
cx = int(mm['m10'] / mm['m00']) #归一化计算得出轮廓中心的横纵坐标
cy = int(mm['m01'] / mm['m00'])
else:
continue
epsilon = 0.04 * cv.arcLength(contours[cnt], True) #多边形拟合的距离参数,下一个函数要用到。原理见代码后链接
approx = cv.approxPolyDP(contours[cnt], epsilon, True) #轮廓近似。将圆润曲线折线化,以此得到该图像的角点坐标。
corners = len(approx) #得到角点数量
if corners == 3: #三个角点的就是三角形
shapes['triangle'] = shapes['triangle'] + 1
shapeLenth = p/3 #得到三角形边长
elif corners == 4: #四个角点就是矩形
shapes['rectangle'] = shapes['rectangle'] + 1
shapeLenth = p/4 #得到正方形边长
else: #圆有好多角点
shapes['circles'] = shapes['circles'] + 1
pi = 3.1415926
rad = p/(2*pi) #得到圆周长
解释中心距(侵删)概率论的知识了
解释多边形拟合(侵删)
解释角点检测原理(侵删)这两篇文章都讲的很清楚,一看就明白了,知道原理 然后拿来用就行。
效果图是拿树莓派跑的:
有需要的可以自己拿去跑了试试,根据角点检测的原理,五边形,六边形,七边形等多边形都可以识别出来,但是可能会和圆搞混,调一调epsilon 这个参数就行。
有问题可以评论区交流