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]))
第三步 求取坐标
对于如下所示的裁剪出的单一飞镖,只需算得最下层的均值所在位置,即可得到飞镖的像素坐标。
文章来源:https://www.toymoban.com/news/detail-561332.html
# 遍历所有飞镖
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_x
green_position_y = int(np.where(mask_green[min_x:max_x,min_y:max_y]==255)[1].mean())+min_y
cv2.circle(result, green_position,10,(0,255,0),5)
green_point.append(green_position)
3、完整代码
对所有红色飞镖进行定位。文章来源地址https://www.toymoban.com/news/detail-561332.html
import cv2
import numpy as np
import matplotlib.pyplot as plt
cap = 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) # 拼接过滤后的mask
cv2.imshow("mask_red", mask_red)
# 获得X轴分布
x_distribute = mask_red.sum(axis=0)/255
x_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=0
for j in range(max_y-1,min_y-1,-1):
if flag==1:
break
for i in range(min_x,max_x):
if mask_red[j,i]==255:
red_position=(i,j+5)
flag=1
break
cv2.circle(frame, red_position,10,(0,0,255),5)
red_point.append(red_position)
print("红色坐标:",red_point)
except:
cv2.waitKey(5)
continue
cv2.waitKey(5)
cv2.destroyAllWindows()
cap.release()
到了这里,关于OpenCV技巧篇——多目标视觉定位(以飞镖定位为例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!