一、需求分析
- 使用摄像头进行图像拍照
- 识别摄像头里面的箭头轮廓
- 识别箭头的方向,不需要识别具体的角度,只需要识别到上、下、左、右四个方向,如在-45°~45°之间方向的箭头都可以归为向右;45° ~ 135°之间的箭头可以识别为向上;以此类推(当然也可以精细化到固定角度,只是本项目中不需要用到)。
- 如下是箭头识别的图像样例。
二、解决思路
分析如上样图特征,考虑如下几部进行箭头方向识别:
- 1、首先抽取出箭头的轮库特征,如上图中有三个图形的轮廓,包括最外面的正方形轮廓。中间的圆形的轮廓,以及我们需要的最里面箭头的轮廓。
- 2、判断轮廓是否为箭头,具体判断方法:
- 判断轮廓的端点比轮廓的凸包多两个点即为箭头轮廓,如下图所示,其中箭头轮廓的端点用红色圈出,凸包用黄色标出。可以很明显的看出箭头的这个特征。
- 判断轮廓的端点比轮廓的凸包多两个点即为箭头轮廓,如下图所示,其中箭头轮廓的端点用红色圈出,凸包用黄色标出。可以很明显的看出箭头的这个特征。
- 3、判断箭头方向的步骤如下
- 检测出箭头顶点和内凹点的坐标(内凹点只需要检测出两个中间的一个即可)
- 将原点移动到内欧点上。获取新的顶点的坐标(x1,y1)
- 将新的顶点坐标旋转45度,得到新的坐标(x2,y2)
- 判断新的坐标(x2,y2)在四个象限的位置,如下:
- 第一象限:箭头向右
- 第二象限:箭头向上
- 第三象限:箭头向左
- 第四象限:箭头向下
三、代码样例
- 1、获取图像轮廓,其中contours中包含了识别到的所有图像轮廓。针对上面这个图片,有三个轮廓。最外面的方形、中间的原型和里面的箭头轮廓。
import cv2
import numpy as np
import math
img =cv2.imread("./2.png")
contours, hierarchy = cv2.findContours(preprocess(img), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)#获取图像轮廓
其中preprocess()函数定义如下:
def preprocess(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)#进行高斯滤波
ret,img_thr = cv2.threshold(img_blur,70,255,cv2.THRESH_BINARY)#二值化,使得图片更加清晰没有中间模糊的像素点
cv2.imshow("2",img_thr)
img_canny = cv2.Canny(img_thr, 50, 50)#边缘检测
kernel = np.ones((3, 3),np.uint8)
img_dilate = cv2.dilate(img_canny, kernel, iterations =2)#边缘膨胀膨胀
img_erode = cv2.erode(img_dilate, kernel, iterations =1)#边缘腐蚀腐蚀
return img_erode
- 2、获取端点和凸包
peri = cv2.arcLength(cnt, True)#获取周长
approx = cv2.approxPolyDP(cnt, 0.025 * peri, True)#获取端点,会返回轮廓的多边形端点值
hull = cv2.convexHull(approx, returnPoints=False)#获取凸包,返回凸包角点的索引,注意本项目是针对端点多边形获取凸包,所以如果是箭头,则箭头里面的两个端点会被忽略
- 3、判断是否为箭头形状,判断条件为:凸包端点数量在3到6个之间(箭头下面那个两个端点由于是摄像头远近,可能会被识别为一个端点)且凸包的端点数量+2与轮廓的端点数量相等(凸包不会识别箭头内凹的两个端点)
sides = len(hull)
if 6 > sides > 3 and sides + 2 == len(approx):#判断箭头的依据是图像的端点个数比凸包少2个点即可。
- 4、寻找箭头的顶点,使用如下函数
arrow_tip = find_tip(approx[:,0,:], hull.squeeze())
其中函数find_tip()的定义如下:
#寻找箭头顶点和底点用于计算方向
def find_tip(points, convex_hull):
length = len(points)
indices = np.setdiff1d(range(length), convex_hull)#寻找箭头内凹的两个点的索引
for i in range(2):
j = indices[i] + 2
if j > length - 1:
j = length -j
p = j + 2
if p > length - 1:
p = length -p
if np.all(points[j] == points[indices[i-1]-2]):
return tuple([points[j],points[p]])
- 5、判断箭头方向
if arrow_tip:
arrow_dir = np.array(arrow_tip[0]) - np.array(arrow_tip[1])
arrow_rotate= rotate(math.pi/4,arrow_dir)
if arrow_rotate[0] > 0:
if arrow_rotate[1] > 0:
print("箭头向右")
else:
print("箭头向上")
else:
if arrow_rotate[1] > 0:
print("箭头向下")
else:
print("箭头向左")
其中rotate函数定义如下
'''将箭头顶点坐标旋转45度,通过旋转坐标,可以通过判断在坐标轴的哪个象限就可以知道箭头方向了'''
def rotate(angle,xy):
rotatex = math.cos(angle)*xy[0] -math.sin(angle)*xy[1]
rotatey = math.cos(angle)*xy[0] + math.sin(angle)*xy[1]
return(tuple([rotatex,rotatey]))
四、附加程序
如果要使用摄像头,则需要将代码中
img =cv2.imread("./2.png")
替换为:
capture = cv2.VideoCapture(700)
ret,img = capture.read()
注意上面代码中700是摄像头的ID号,根据自己的摄像头环境而定,笔记本的内置摄像头是0,如果不知道自己的摄像头是什么ID,可以使用如下脚本进行探测:
ID = 0
while(1):
cap = cv2.VideoCapture(ID)
# get a frame
ret, frame = cap.read()
if ret == False:
ID += 1
print("false")
else:
print(ID)