简介
最近做一个ros项目,需要ros小车调用摄像头去识别迷你的限速牌上的速度,让小车根据限速的数字判断小车当前的运动状态。
遇到难点
一开始感觉难度不大,应该会有现成的库可以使用,后面我发现我想得过于简单了。首先,难点有以下几点:
一是客户要求小车的最大运行速度比较大,而限速牌数字比较小,加上限速牌只能放在小车右手边,摄像头位置在正前方,这样一来,当速度快的时候,限速牌在图像右侧一闪而过,根本来不及反映。
二是现有的成熟的库我也测试过识别效果太慢,其中包括谷歌开源的Tesseract OCR识别,以及采用的深度学习的方式也不行,静态测试准确性挺高的,但是识别速度也很慢,现有的硬件的处理能力根本无法满足实时性需求,导致小车的图像消息大部分被丢失,延迟很大,最终采取了图像模板匹配的方法,经过图像预处理后识别速度完全满足要求。
解决思路
1. 加载所有的限速牌模板图片,并将其缩放为28x28像素大小。(模板图像要提前做好的)
2. 创建ROS节点和订阅者,接收来自摄像头的图像消息。
3. 在图像中查找圆,如果发现则截取圆形区域进行数字识别。
4. 在截取的圆形区域中,先将图像缩放到28x28大小,并将其变为灰度图像。然后使用二值化处理,只保留红色圆圈中的数字,其余部分变成白色。
5. 对于每张限速牌模板图片,使用OpenCV的模板匹配功能来寻找数字,得到最佳匹配的模板,返回其代表的数字。
6. 将程序识别到的数字展示在原始图像上。
7. 通过cv2.imshow()函数,将图像实时显示在屏幕上。
最终程序
#!/usr/bin/env python
import rospy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import tensorflow as tf
import numpy as np
class SpeedLimitDetector():
def __init__(self):
# 加载模板图片
self.templates = []
# 30~120
for i in range(30, 130, 10):
filename = "template/template_{}.png".format(i)
img = cv2.imread(filename, 0)
img = cv2.resize(img, (28, 28))
self.templates.append(img)
# 创建ROS节点和订阅者
rospy.init_node('speed_limit_detector')
self.img_sub = rospy.Subscriber('/camera/image_raw', Image, self.image_callback)
self.cv_bridge = CvBridge()
def image_callback(self, img_msg):
# 将ROS图像消息转换为OpenCV图像格式
cv_img = self.cv_bridge.imgmsg_to_cv2(img_msg, desired_encoding='bgr8')
# 在图像中查找圆
gray_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray_img, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=0, maxRadius=int(min(gray_img.shape)/2))
if circles is not None:
# 找到圆后,将圆内的区域截取下来,进行数字识别
img, x, y, r = self.circle_crop(cv_img, circles[0])
digit = self.detect_digit(img)
# 显示结果
if digit is not None:
rospy.loginfo("Detected speed limit: {} km/h".format(digit))
cv2.putText(cv_img, "Speed Limit: {} km/h".format(digit), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# 显示图像
cv2.imshow('Speed Limit Detector', cv_img)
cv2.waitKey(1)
def circle_crop(self, img, circle):
x = int(circle[0][0])
y = int(circle[0][1])
r = int(circle[0][2] * 0.5)
cropped_img = img[y-r:y+r, x-r:x+r]
return cropped_img, x, y, r
def detect_digit(self, img):
# 缩小图像到28*28像素,变为灰度图,并二值化处理
img = cv2.resize(img, (28, 28))
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
binary_img = cv2.bitwise_not(binary_img)
# 使用模板匹配获取数字
max_match = None
match_idx = None
for i, template in enumerate(self.templates):
result = cv2.matchTemplate(binary_img, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
if max_match is None or max_val > max_match:
max_match = max_val
match_idx = i * 10
if max_match > 0.6:
return match_idx + 30
else:
return None
def run(self):
rospy.spin()
def __del__(self):
self.sess.close()
if __name__ == '__main__':
detector = SpeedLimitDetector()
detector.run()