前言
笔者今天几乎完成了去年电赛图像识别的部分,如果用opencv的话确实会简单不少,我们只需要识别外矩形框以及内矩形框的8个顶点,还有中心点以及激光点的坐标发送给stm32,基本就已经完成了这个题目视觉的部分,在之前笔者进行了opencv的入门,学习一段时间后做了银行卡号识别的项目才开始真正做这道题的图像,希望我的思路可以帮助到大家。
先展示一下效果
一、识别黑色电工胶带八个顶点(外边框4个内边框4个)
我们识别这个黑色边框的步骤主要就是以下几个步骤
1.图像灰度化
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
2.Canny边缘检测
先二值化图像随后进行边缘检测
lurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, 30, 60)
3.轮廓检测
我们需要消除图像中由噪声引起的小面积边缘轮廓。
# 轮廓检测
contours, _ = cv2.findContours(edges, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# 去除面积小的轮廓
filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 50] # 可根据实际情况调整阈值
4.近似多边形
rect_contours = []
for cnt in filtered_contours:
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
if len(approx) == 4: # 近似为四边形(可能是矩形)
rect_contours.append(approx)
detected_rect_contours = rect_contours # 保存检测到的矩形轮廓
把近似成矩形的轮廓保留下来,其他的全部舍弃。
5.绘制矩形轮廓
# 绘制矩形轮廓
for i, rect_contour in enumerate(detected_rect_contours):
if i == index_to_delete or i == index_to_delete_2: # 如果是要删除的索引,跳过
continue
area = cv2.contourArea(rect_contour)
if area > 40000:
cv2.drawContours(frame, [rect_contour], -1, (255, 0, 0), 2) # 面积大于 40000 用蓝色绘制
else:
cv2.drawContours(frame, [rect_contour], -1, (0, 255, 0), 2) # 其他用绿色绘制
这里笔者把轮廓分成了两部分,一部分是外层的一部分是内层的,通过面积进行区分。
6.找到矩形顶点
# 计算并打印要关注的两个矩形的中心点坐标
center_coords = []
for i, rect_contour in enumerate(detected_rect_contours):
if i in [index_to_delete, index_to_delete_2]:
M = cv2.moments(rect_contour)
if M["m00"]!= 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
center_coords.append((cx, cy))
print(f"要关注的矩形 {i} 的中心点坐标: ({cx}, {cy})")
# 标注两个矩形的顶点
for i, rect_contour in enumerate(detected_rect_contours):
if i == index_to_delete or i == index_to_delete_2:
for point in rect_contour:
cv2.circle(frame, tuple(point[0]), 5, (255, 255, 0), -1) # 用黄色标注顶点
二、找到中心点以及红色激光点(绿色同理)
1.中心点
# 计算并标注矩形中心点
M = cv2.moments(rect_contour)
if M["m00"]!= 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1) # 用红色标注中心点
2.激光点
主要就是将图像转到hsv色彩空间,这样就可以找到红色的激光点。如果找不到阈值可以用我的找阈值代码,也会写在文章里。
# 处理激光检测的结果
if len(contours_red) > 0:
largest_area = 0
laser_center = None
for contour_red in contours_red:
area = cv2.contourArea(contour_red)
if area > largest_area:
largest_area = area
M = cv2.moments(contour_red)
if M["m00"]!= 0:
height, width = frame.shape[:2]
cy = height-(int(M["m01"] / M["m00"]))
cx = int(M["m10"] / M["m00"])
laser_center = (cx, cy)
if laser_center:
print("红色激光位置:", laser_center)
# 用小矩形框框出红色激光点位置
rect_size = 10 # 矩形框的大小
x1 = laser_center[0] - rect_size // 2
y1 = laser_center[1] - rect_size // 2
x2 = laser_center[0] + rect_size // 2
y2 = laser_center[1] + rect_size // 2
cv2.rectangle(frame, (x1, height-y1), (x2, height-y2), (0, 0, 255), 2) # 绘制矩形框
return frame
3.找阈值代码
import cv2
def mouse_click(event, x, y, flags, para):
if event == cv2.EVENT_LBUTTONDOWN: # 左边鼠标点击
print('PIX:', x, y)
print("BGR:", frame[y, x])
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
print("GRAY:", gray_frame[y, x])
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
print("HSV:", hsv_frame[y, x])
if __name__ == '__main__':
cap = cv2.VideoCapture(0) # 打开默认摄像头(通常为 0)
cv2.namedWindow("frame")
cv2.setMouseCallback("frame", mouse_click)
while True:
ret, frame = cap.read() # 读取摄像头帧
if not ret:
print("无法获取摄像头帧")
break
cv2.imshow('frame', frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
通过鼠标点击某个点就可以得出这个点的颜色信息。效果如下
三、全部代码
import cv2
import numpy as np
# 全局变量用于存储检测到的矩形轮廓
detected_rect_contours = []
is_detected = False # 标志位,初始化为未检测
index_to_delete = 1 # 要删除的第一个矩形轮廓的索引
index_to_delete_2 = 3 # 要删除的第二个矩形轮廓的索引
# 处理帧的函数
def process_frame(frame):
global detected_rect_contours, is_detected, index_to_delete, index_to_delete_2
# 每次都重新检测矩形轮廓
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, 30, 60)
# 轮廓检测
contours, _ = cv2.findContours(edges, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# 去除面积小的轮廓
filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 50] # 可根据实际情况调整阈值
rect_contours = []
for cnt in filtered_contours:
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
if len(approx) == 4: # 近似为四边形(可能是矩形)
rect_contours.append(approx)
detected_rect_contours = rect_contours # 保存检测到的矩形轮廓
# 绘制矩形轮廓
for i, rect_contour in enumerate(detected_rect_contours):
if i == index_to_delete or i == index_to_delete_2: # 如果是要删除的索引,跳过
continue
area = cv2.contourArea(rect_contour)
if area > 40000:
cv2.drawContours(frame, [rect_contour], -1, (255, 0, 0), 2) # 面积大于 40000 用蓝色绘制
else:
cv2.drawContours(frame, [rect_contour], -1, (0, 255, 0), 2) # 其他用绿色绘制
# 计算并标注矩形中心点
M = cv2.moments(rect_contour)
if M["m00"]!= 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1) # 用红色标注中心点
# 打印矩形顶点坐标
for point in rect_contour:
print(f"矩形 {i} 的顶点坐标: {point[0]}")
# 计算并打印要关注的两个矩形的中心点坐标
center_coords = []
for i, rect_contour in enumerate(detected_rect_contours):
if i in [index_to_delete, index_to_delete_2]:
M = cv2.moments(rect_contour)
if M["m00"]!= 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
center_coords.append((cx, cy))
print(f"要关注的矩形 {i} 的中心点坐标: ({cx}, {cy})")
# 标注要删除的两个矩形的顶点
for i, rect_contour in enumerate(detected_rect_contours):
if i == index_to_delete or i == index_to_delete_2:
for point in rect_contour:
cv2.circle(frame, tuple(point[0]), 5, (255, 255, 0), -1) # 用黄色标注顶点
# 检测红色激光点
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 设定红色的范围
lower_red = np.array([165, 30, 120])
upper_red = np.array([185, 150, 255])
# 提取红色部分
mask = cv2.inRange(hsv, lower_red, upper_red)
# 寻找红色激光的轮廓
contours_red, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 处理激光检测的结果
if len(contours_red) > 0:
largest_area = 0
laser_center = None
for contour_red in contours_red:
area = cv2.contourArea(contour_red)
if area > largest_area:
largest_area = area
M = cv2.moments(contour_red)
if M["m00"]!= 0:
height, width = frame.shape[:2]
cy = height-(int(M["m01"] / M["m00"]))
cx = int(M["m10"] / M["m00"])
laser_center = (cx, cy)
if laser_center:
print("红色激光位置:", laser_center)
# 用小矩形框框出红色激光点位置
rect_size = 10 # 矩形框的大小
x1 = laser_center[0] - rect_size // 2
y1 = laser_center[1] - rect_size // 2
x2 = laser_center[0] + rect_size // 2
y2 = laser_center[1] + rect_size // 2
cv2.rectangle(frame, (x1, height-y1), (x2, height-y2), (0, 0, 255), 2) # 绘制矩形框
return frame
cap = cv2.VideoCapture(0) # 0 表示默认摄像头
while True:
ret, frame = cap.read()
if not ret:
break
processed_frame = process_frame(frame)
cv2.imshow('Enhanced Edge Detection with Rectangular Contours', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
四、总结
在这道题上边opencv确实起到了对openmv等视觉模块的降维打击,把坐标点发送到stm32后即可让stm32通过舵机去控制激光点的走向,可以通过8个顶点计算出两个轮廓中线的路径以及坐标。用opencv的话一个上午就可以写出解决方案,我认为对于电赛的视觉识别方面帮助也是特别大的,可以节省很多时间。并且也有很多便宜的开发板可以运行opencv,用到的都是传统算法。希望这篇文章可以帮助到大家。