Robomaster视觉-装甲板识别
环境:windows10、pycharm2017、、python3.64、opencv3
***先上个效果图吧,图中是加了一些其他算法的,不是单独的识别,还有一些卡尔曼滤波,svm等。我们先出基本的识别思路开始。
第一步-导入需要的库进行图像预处理
- 读取摄像头的信息后,对图片进行hsv色彩空间的转换,对于红蓝而言也可以采用颜色通道相减的方法获取蓝色、红色(两种方法各有优点)
- 进行图像形态学操作,包括开闭操作,腐蚀膨胀等等
- 得到一个较为清晰的二值图,其中镜头中红、蓝色为高亮区。
***分享一下我自己代码,其中形态学操作,hsv色彩空间转换都进行了模块化,可以直接使用。
这是第一部分的一个主程序由它调用其他下面的程序:
def read_morphology(cap): # read cap and morphological operation to get led binary image.
ret, frame = cap.read()
# frame = cv.flip(frame, 1)
# frame = cv.flip(frame, 1)
open = cv.getTrackbarPos('open', 'mor_adjust')
close = cv.getTrackbarPos('close', 'mor_adjust')
erode = cv.getTrackbarPos('erode', 'mor_adjust')
dilate = cv.getTrackbarPos('dilate', 'mor_adjust')
frame = cv.resize(frame, (WIDTH, HIGH), interpolation=cv.INTER_CUBIC)
mask = hsv_change(frame)
# dst_open = open_binary(mask, open, open)
dst_close = close_binary(mask, close, close)
dst_erode = erode_binary(dst_close, erode, erode)
dst_dilate = dilate_binary(dst_erode, dilate, dilate)
cv.circle(frame, (int(WIDTH / 2), int(HIGH / 2)), 2, (255, 0, 255), -1)
cv.imshow("erode", mask)
return dst_dilate, frame
hsv色彩空间转换,为了调参数方便我设置了滑块,方便调参调整hsv的阈值来获得一个较好的阈值效果,但是这种根据阈值来调整的会受光线影响比较严重 ,rm的场地比较黑暗,但是能也会有些影响,可以加一些滤光片或者增加摄像头的曝光等等来进行调整。
def nothing(x):
pass
def creatTrackbar(): # creat trackbar to adjust the color threshold.
# blue
# cv.createTrackbar("hmin", "color_adjust", 0, 255, nothing)
# cv.createTrackbar("hmax", "color_adjust", 250, 255, nothing)
# cv.createTrackbar("smin", "color_adjust", 0, 255, nothing)
# cv.createTrackbar("smax", "color_adjust", 143, 255, nothing)
# cv.createTrackbar("vmin", "color_adjust", 255, 255, nothing)
# cv.createTrackbar("vmax", "color_adjust", 255, 255, nothing)
# red
cv.createTrackbar("hmin", "color_adjust", 0, 255, nothing)
cv.createTrackbar("hmax", "color_adjust", 255, 255, nothing)
cv.createTrackbar("smin", "color_adjust", 3, 255, nothing)
cv.createTrackbar("smax", "color_adjust", 255, 255, nothing)
cv.createTrackbar("vmin", "color_adjust", 245, 255, nothing)
cv.createTrackbar("vmax", "color_adjust", 255, 255, nothing)
cv.createTrackbar("open", "mor_adjust", 1, 30, nothing)
cv.createTrackbar("close", "mor_adjust", 5, 30, nothing)
cv.createTrackbar("erode", "mor_adjust", 2, 30, nothing)
cv.createTrackbar("dilate", "mor_adjust", 5, 30, nothing)
def hsv_change(frame): # hsv channel separation.
hmin = cv.getTrackbarPos('hmin', 'color_adjust')
hmax = cv.getTrackbarPos('hmax', 'color_adjust')
smin = cv.getTrackbarPos('smin', 'color_adjust')
smax = cv.getTrackbarPos('smax', 'color_adjust')
vmin = cv.getTrackbarPos('vmin', 'color_adjust')
vmax = cv.getTrackbarPos('vmax', 'color_adjust')
# gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# cv.imshow("gray", gray)
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
lower_hsv = np.array([hmin, smin, vmin])
upper_hsv = np.array([hmax, smax, vmax])
mask = cv.inRange(hsv, lowerb=lower_hsv, upperb=upper_hsv)
return mask
形态学操作,模块化可直接调用使用
def open_binary(binary, x, y):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (x, y))
dst = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
return dst
def close_binary(binary, x, y):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (x, y))
dst = cv.morphologyEx(binary, cv.MORPH_CLOSE, kernel)
return dst
def erode_binary(binary, x, y):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (x, y))
dst = cv.erode(binary, kernel)
return dst
def dilate_binary(binary, x, y):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (x, y))
dst = cv.dilate(binary, kernel)
return dst
第二部分 -筛选寻找装甲板
- 由第一步得到的图像找出画面中所以的红、蓝区域,用读取区域最小外接矩形获取对应的面积,高、宽、旋转角、中心点等等信息。
- 遍历每一个矩形,将获取的可能为装甲板的区域的信息存储在python-字典中,并对不同的信息不同的键值加以区分。
- 筛选这些信息,留下可能是装甲板的信息,删去非装甲板的信息。例如,遍历对比每一对的信息的高宽比是否满足某个条件、两个矩形块之间的中心点距离是否满足一定大小。这里需要说明,是把所有的红、蓝色区域都作为一个单独的矩形区域进行判断,所有需要判断的是两两之间的关系,而不是单独一个矩形块的关系。当然也可以加入一些单独的判断,例如装甲板的灯条是个长方形的,它自己一个灯条的高宽比肯定满足某个范围,如果单独一个矩形区域都不满足灯条基本的高宽比,那这个必然不是装甲板的一个灯条。
- 通过层层筛选可以最终得到一个字典,里面包含了画面中所以可能是装甲板的信息,然后通过画图将他们画出来,再调试找到准确率最高的情况。这里需要说明,在筛选之前最好对信息进行一个排序,这样不容易造成一个装甲板的一个灯条和另一个装甲板的另一个灯条被识别成一个装甲板的情况。同时记得结束后将字典清空后再进行下一次筛选,否则可能会报错。
***下面附上这部分代码
def find_contours(binary, frame): # find contours and main screening section
_, contours, heriachy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
length = len(contours)
data_list = []
first_data = []
second_data1 = []
second_data2 = []
c = 0
d = 0
if length > 0:
# print("---founding---")
for i, contour in enumerate(contours):
data_dict = dict()
# print("countour", contour)
area = cv.contourArea(contour)
rect = cv.minAreaRect(contour)
rx, ry = rect[0]
rw = rect[1][0]
rh = rect[1][1]
z = rect[2]
coor = cv.boxPoints(rect)
x1 = coor[0][0]
y1 = coor[0][1]
x2 = coor[1][0]
y2 = coor[1][1]
x3 = coor[2][0]
y3 = coor[2][1]
x4 = coor[3][0]
y4 = coor[3][1]
# if i >= 1:
data_dict["area"] = area
data_dict["rx"] = rx
data_dict["ry"] = ry
data_dict["rh"] = rh
data_dict["rw"] = rw
data_dict["z"] = z
data_dict["x1"] = x1
data_dict["y1"] = y1
data_dict["x2"] = x2
data_dict["y2"] = y2
data_dict["x3"] = x3
data_dict["y3"] = y3
data_dict["x4"] = x4
data_dict["y4"] = y4
data_list.append(data_dict)
for i in range(len(data_list)):
data_rh = data_list[i].get("rh", 0)
data_rw = data_list[i].get("rw", 0)
data_area = data_list[i].get("area", 0)
if (float(data_rh / data_rw) >= 0.2) \
and (float(data_rh / data_rw) <= 4) \
and data_area >= 20:
first_data.append(data_list[i])
else:
pass
for i in range(len(first_data)):
c = i + 1
while c < len(first_data):
data_ryi = float(first_data[i].get("ry", 0))
data_ryc = float(first_data[c].get("ry", 0))
data_rhi = float(first_data[i].get("rh", 0))
data_rhc = float(first_data[c].get("rh", 0))
data_rxi = float(first_data[i].get("rx", 0))
data_rxc = float(first_data[c].get("rx", 0))
if (abs(data_ryi - data_ryc) <= 3 * ((data_rhi + data_rhc) / 2)) \
and (abs(data_rhi - data_rhc) <= 0.2 * max(data_rhi, data_rhc)) \
and (abs(data_rxi - data_rxc) <= (6 / 2) * ((data_rhi + data_rhc) / 2)):
second_data1.append(first_data[i])
second_data2.append(first_data[c])
c = c + 1
# for i in range(len(second_data1)):
# data_z1 = second_data1[i].get("z", 0)
# data_z2 = second_data2[i].get("z", 0)
# if abs(data_z1 - data_z2) <= 6:
# third_data1.append(second_data1[i])
# third_data2.append(second_data2[i])
if len(second_data1):
global dataList_c
dataList_c.clear()
for i in range(len(second_data1)):
rectangle_x1 = int(second_data1[i]["x1"])
rectangle_y1 = int(second_data1[i]["y1"])
rectangle_x2 = int(second_data2[i]["x3"])
rectangle_y2 = int(second_data2[i]["y3"])
if abs(rectangle_y1 - rectangle_y2) <= (6 / 2) * (abs(rectangle_x1 - rectangle_x2)):
global point1_1x, point1_1y, point1_2x, point1_2y, point1_3x, point1_3y, point1_4x, point1_4y
global point2_1x, point2_1y, point2_2x, point2_2y, point2_3x, point2_3y, point2_4x, point2_4y
point1_1x = second_data1[i]["x1"]
point1_1y = second_data1[i]["y1"]
point1_2x = second_data1[i]["x2"]
point1_2y = second_data1[i]["y2"]
point1_3x = second_data1[i]["x3"]
point1_3y = second_data1[i]["y3"]
point1_4x = second_data1[i]["x4"]
point1_4y = second_data1[i]["y4"]
point2_1x = second_data2[i]["x1"]
point2_1y = second_data1[i]["y1"]
point2_2x = second_data1[i]["x2"]
point2_2y = second_data1[i]["y2"]
point2_3x = second_data1[i]["x3"]
point2_3y = second_data1[i]["y3"]
point2_4x = second_data1[i]["x4"]
point2_4y = second_data1[i]["y4"]
if point1_1x > point2_1x:
pass
cv.rectangle(frame, (point2_2x, point2_2y), (point1_4x, point1_4y), (255, 255, 0), 2)
else:
point1_1x, point2_1x = point2_1x, point1_1x
point1_2x, point2_2x = point2_2x, point1_2x
point1_3x, point2_3x = point2_3x, point1_3x
point1_4x, point2_4x = point2_4x, point1_4x
point1_1y, point2_1y = point2_1y, point1_1y
point1_2y, point2_2y = point2_2y, point1_2y
point1_3y, point2_3y = point2_3y, point1_3y
point1_4y, point2_4y = point2_4y, point1_4y
cv.rectangle(frame, (point2_1x, point2_1y), (point1_3x, point1_3y), (255, 255, 0), 2)
cv.putText(frame, "target1:", (rectangle_x2, rectangle_y2 - 5), cv.FONT_HERSHEY_SIMPLEX,
0.5, [255, 255, 255])
center = (int((point2_2x + point1_4x) / 2), int((point2_2y + point1_4y) / 2))
cv.circle(frame, center, 2, (0, 0, 255), -1) # 画出重心
dataList_c.append(center)
high = (rectangle_y1, rectangle_y2)
dataRange.append(high)
else:
print("---not find---")
dataList_c.clear()
data_list.clear()
- 到这一步基本的识别就结束了,之后就该进行一些算法的优化以及提高识别速度等等,这里可以采用神经网络训练识别数字,svm也可,卡尔曼滤波添加提前量增加击打准确,增加线进程提高速度等等。
总结
1,图片数据读取:在While语句中从摄像头中读入数据,以帧一帧的形式加以进入后续处理。
2,预处理:对读入进来的图片预处理,包括,转化到HSV色彩空间、根据不同阈值不断调整提取出鲜明的红色/蓝色、对提取出来的包含各种噪声的二值图进行腐蚀膨胀处理,得到完整的可能含有多个的明显的灯条信息并且噪声较小或没有噪声后。即算预处理结束。
3,筛选灯条:对预处理完的灯条进行轮廓查找进入find_contours()函数,遍历cv2.findContours()opencvAPI查找出的轮廓,根据灯条高宽比、面积大小、两列灯条之间的宽度差、高度差把是灯条对的轮廓筛选出来,其中以待筛选的一对灯条的高度平均值或max(待筛选的一对灯条)作为比对标准进行筛选。画出所有灯条对,并确定中心及击打点。
4,测距及数据处理:根据PNP算法、cv2.solvePnP()API等单目测距出目标点距离摄像头的距离,并根据姿势计算获得欧拉角作为电控云台转动的数据。
5,主要框架流程:将各个函数步骤逐步封装,并在主函数的While读图语句中分步调用,并作为主线程,将筛选灯条svm算法、测距及数据处理和串口发送,作为两个分线程,各自运行互不影响提高识别速度。(也可以开多个线程,根据自己算法而定)