OpenCV技巧篇【1】——多目标视觉定位(以飞镖定位为例)
1、针对问题
多目标视觉定位是指通过计算机视觉技术对一张图片中的多个目标进行识别和定位的过程。本篇将以对飞镖定位为例,提出一个简单有效的多目标定位技巧,最终实现如下图所示的定位效果。
2、解决方法
2.1 颜色筛选
首先要考虑所需定位目标通常具有的最显著的特征——颜色,通过将图片从RGB空间转化到HSV色彩空间筛选出颜色对应的色彩。其中:
H(色调):0-180
S(饱和度):0-255
V(黑暗的程度):0-255
下表是HSV取值范围与对应的色彩(通常需要根据环境光线做出相应的调整,以更好地过滤出目标颜色):
色彩 | 黑 | 灰 | 白 | 红 | 橙 | 黄 | 绿 | 青 | 蓝 | 紫 |
---|---|---|---|---|---|---|---|---|---|---|
H | 0~180 | 0~180 | 0~180 | 156~10 | 11~25 | 26~34 | 35~77 | 78~99 | 100~124 | 125~155 |
S | 0~255 | 0~43 | 0~30 | 43~255 | 43~255 | 43~255 | 43~255 | 43~255 | 43~255 | 43~255 |
V | 0~46 | 46~220 | 221~255 | 46~255 | 46~255 | 46~255 | 46~255 | 46~255 | 46~255 | 46~255 |
opencv代码如下:
# 将图片转到HSV域
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 提取红色
lower_red1 = np.array([0, 43, 46]) # 红色阈值下界
higher_red1 = np.array([10, 255, 255]) # 红色阈值上界
mask_red1 = cv2.inRange(hsv, lower_red1, higher_red1)
lower_red2 = np.array([156, 43, 46]) # 红色阈值下界
higher_red2 = np.array([180, 255, 255]) # 红色阈值上界
mask_red2 = cv2.inRange(hsv, lower_red2, higher_red2)
mask_red = cv2.add(mask_red1, mask_red2) # 拼接过滤后的mask
cv2.imshow("mask_red", mask_red)
# 提取绿色
lower_green = np.array([35, 43, 46]) # 绿色阈值下界
higher_green = np.array([77, 255, 255]) # 绿色阈值上界
mask_green = cv2.inRange(hsv, lower_green, higher_green)
cv2.imshow("mask_green",mask_green)
绿色飞镖过滤效果如下所示:
2.2 多目标识别
我们得到了经过颜色过滤后的mask图片,之后需要用ROI将每个单一飞镖裁剪出来,但如何将这多个飞镖的位置分别获取出来就是一个问题,许多人会去使用遍历的方法,通过在各个方向上寻找边缘来定位,但在有多个目标存在的情况下,这种方法往往效果不佳,而这里使用的方法是这样的:
第一步 纵向求和
由于在mask图片中,飞镖区域的值为255,其他区域的值为0,因此可以采用纵向求和的方式(也可以认为是向x轴投影的分布情况),来定位出飞镖的横向坐标范围。
第二步 横向求和
考虑到有可能出现下图所示的情况,因此在纵向求和获得横向定位坐标范围后,按照此范围内裁剪mask图片,并进行横向求和(也可以认为是向y轴投影的分布情况),来定位出飞镖的纵向坐标范围。最终将得每个到单一飞镖的具体横纵坐标范围。
同时考虑到背景中存在微量不连续杂色的可能性,可以在获取范围时加入阈值判断,多目标识别具体代码如下:
# 获得X轴分布
x_list_left = []
x_list_right = []
x_distribute = mask_red.sum(axis=0)/255
# plt.plot(mask_red.sum(axis=0)/255)
# plt.plot(mask_red.sum(axis=1)/255)
# plt.show()
# plt.waitforbuttonpress()
for i in range(3,len(x_distribute)-3):if x_distribute[i]==0 and x_distribute[i+1]>0 and x_distribute[i:i+3].sum()>3:x_list_left.append(i)elif x_distribute[i-3:i].sum()>3 and x_distribute[i]>0 and x_distribute[i+1]==0:x_list_right.append(i+1)# 基于X轴,获得Y轴分布,并获得所有可能区域ranges_list = []for j in range(min([len(x_list_left),len(x_list_right)])):y_distribute = mask_red[:,x_list_left[j]:x_list_right[j]].sum(axis=1)/255# 获得Y轴分布y_list_left = []y_list_right = []for i in range(3,len(y_distribute)-3):if y_distribute[i]==0 and y_distribute[i+1]>0 and y_distribute[i:i+5].sum()>3:y_list_left.append(i)elif y_distribute[i-3:i].sum()>3 and y_distribute[i]>0 and y_distribute[i+1]==0:y_list_right.append(i+1)# 获得所有可能区域for i in range(min([len(y_list_left),len(y_list_right)])):if (y_list_right[i]-y_list_left[i]) > 30 and (x_list_right[j]-x_list_left[j]) > 30:ranges_list.append((x_list_left[j],x_list_right[j],y_list_left[i],y_list_right[i]))
第三步 求取坐标
对于如下所示的裁剪出的单一飞镖,只需算得最下层的均值所在位置,即可得到飞镖的像素坐标。
# 遍历所有飞镖
green_point = []
for i in range(len(ranges_list)):# ROI裁剪max_x = ranges_list[i][1]min_x = ranges_list[i][0]max_y = ranges_list[i][3]min_y = ranges_list[i][2]green_position_x = int(np.where(mask_green[min_x:max_x,min_y:max_y]==255)[0].mean())+min_xgreen_position_y = int(np.where(mask_green[min_x:max_x,min_y:max_y]==255)[1].mean())+min_ycv2.circle(result, green_position,10,(0,255,0),5)green_point.append(green_position)
3、完整代码
对所有红色飞镖进行定位。
import cv2
import numpy as np
import matplotlib.pyplot as pltcap = cv2.VideoCapture(1)while(1):_, frame = cap.read()cv2.imshow("result", frame)hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)# 提取红色lower_red1 = np.array([0, 160, 120]) # 红色阈值下界higher_red1 = np.array([10, 255, 255]) # 红色阈值上界mask_red1 = cv2.inRange(hsv, lower_red1, higher_red1)lower_red2 = np.array([170, 160, 120]) # 红色阈值下界higher_red2 = np.array([180, 255, 255]) # 红色阈值上界mask_red2 = cv2.inRange(hsv, lower_red2, higher_red2)mask_red = cv2.add(mask_red1, mask_red2) # 拼接过滤后的maskcv2.imshow("mask_red", mask_red)# 获得X轴分布x_distribute = mask_red.sum(axis=0)/255x_list_left = []x_list_right = []for i in range(3,len(x_distribute)-3):if x_distribute[i]==0 and x_distribute[i+1]>0 and x_distribute[i:i+3].sum()>3:x_list_left.append(i)elif x_distribute[i-3:i].sum()>3 and x_distribute[i]>0 and x_distribute[i+1]==0:x_list_right.append(i+1)# 基于X轴,获得Y轴分布,并获得所有可能区域ranges_list = []for j in range(min([len(x_list_left),len(x_list_right)])):y_distribute = mask_red[:,x_list_left[j]:x_list_right[j]].sum(axis=1)/255# 获得Y轴分布y_list_left = []y_list_right = []for i in range(3,len(y_distribute)-3):if y_distribute[i]==0 and y_distribute[i+1]>0 and y_distribute[i:i+3].sum()>3:y_list_left.append(i)elif y_distribute[i-3:i].sum()>3 and y_distribute[i]>0 and y_distribute[i+1]==0:y_list_right.append(i+1)# 获得所有可能区域for i in range(min([len(y_list_left),len(y_list_right)])):if (y_list_right[i]-y_list_left[i]) > 30 and (x_list_right[j]-x_list_left[j]) > 30:ranges_list.append((x_list_left[j],x_list_right[j],y_list_left[i],y_list_right[i]))try:red_point = []for i in range(len(ranges_list)):# ROI裁剪max_x = ranges_list[i][1]min_x = ranges_list[i][0]max_y = ranges_list[i][3]min_y = ranges_list[i][2]flag=0for j in range(max_y-1,min_y-1,-1):if flag==1:breakfor i in range(min_x,max_x):if mask_red[j,i]==255:red_position=(i,j+5)flag=1breakcv2.circle(frame, red_position,10,(0,0,255),5)red_point.append(red_position)print("红色坐标:",red_point)except:cv2.waitKey(5)continuecv2.waitKey(5)
cv2.destroyAllWindows()
cap.release()