实验一 视觉感知:红绿灯与锥桶检测

实验目的:

  1. 掌握相机标定的基本方法,了解相机畸变的影响;
  2. 掌握传统图像处理的基本方法,使用 OpenCV 进行图像处理;

实验内容:

  1. 使用相机标定源码标定相机;
  2. 运用OpenCV处理锥桶和红绿灯信息;

实验仪器:

ROS智能车、雷达、红色蓝色锥桶、路由器、工控机。

实验原理:

  1. ROS环境下驱动USB相机

​ ROS智能车上配置的是USB串口摄像头。原厂代码不包含驱动,我们需要在ROS.org上获取usb相机驱动usb_cam功能包,功能包在/image_raw话题下发布sensor_msgs/Image结构的图像信息,数据结构如下:

1
2
3
4
5
6
7
8
Header header    

uint32 height # image height, that is, number of rows
uint32 width # image width, that is, number of columns
string encoding # Encoding of pixels -- channel meaning, ordering, size
uint8 is_bigendian # is this data bigendian?
uint32 step # Full row length in bytes
uint8[] data # actual matrix data, size is (step * rows)

​ 通过相机获取图像信息,首先需要对相机进行标定以消除畸变,否则无法正常驱动相机。相机标定的目的是为了确定相机内部参数和外部参数,以便修正图像并将图像坐标映射到世界坐标,或者从世界坐标映射到图像坐标。这个过程是计算机视觉和三维感知任务的关键步骤之一。相机标定可以通过matlab、OpenCV、ROS三种方式进行,本实验我们采用camera_calibration功能包,通过国际棋盘计算参数,实现相机标定。

  1. 图像处理算法分析

近年来,随着计算机技术不断发展和深度学习的兴起,图像处理领域也得到了广泛的关注和研究。当前主流的图像处理算法可以分为传统算法和深度学习算法两类。

传统算法主要是基于人工设计的特征提取和分类模型,包括SIFT、HOG、SURF等。这些算法在一些场景下仍然具有一定的优势,例如对于目标检测任务,传统算法通常能够更好地处理尺度变化和视角变化等问题,而且相对于深度学习算法来说,传统算法的计算速度也更快。但是,传统算法需要大量的手工调参和特征设计,而这些过程通常需要专业知识和经验,成本较高且不易复用。

深度学习算法则是基于神经网络的方法,通过大规模数据训练神经网络,实现自动特征提取和分类。AlexNet、VGG、GoogLeNet、ResNet等深度学习模型在图像处理领域具有重要的作用。与传统算法相比,深度学习算法能够更好地处理复杂的非线性特征,且无需手动设计特征,更加便于工程实现。同时,深度学习算法的泛化能力比传统算法更强,在大规模数据下表现更好。

然而,深度学习算法也存在一些问题,例如需要大量的数据和计算资源进行训练,且网络结构和参数调整较为复杂,容易出现过拟合等问题。此外,在一些场景下,深度学习算法的计算开销较大,对计算资源的要求较高。

考虑到四代i5工控机计算资源有限,且室外专项赛道元素较为单一 ,不需要对各种场景进行泛,选择传统算法可能是更合适的选择。

传统图像处理 深度学习网络
关键技术 信号处理方法,如直方图,灰度化,滤波,差值,采样等 CNN,RNN,FC,Yolov5s,LSTM,etc.
优点 特点场景效果较好 数据驱动,泛化能力好
缺点 1.泛化能力弱
2.需要人为处理图像,寻找特征
1.可解释性差
2.需要大量数据集训练
算力要求 低,CPU单线程处理 高,GPU 并行处理
  1. 图像处理技术

  2. 图像去噪

通过降低图像中的噪声来提高图像质量。常见的方法包括中值滤波、高斯滤波和锐化滤波。

中值滤波是一种常见的非线性图像滤波方法,它使用像素点邻域内的中值来取代中心像素的灰度值,从而达到去除噪声的效果。中值滤波的公式比较简单,就是将像素点邻域内的像素值进行排序,然后取中间值作为当前像素的值。
$$
I_{\text{filtered}}(x, y) = \text{median}\left(I(x+i, y+j) ,|, i = -k,\ldots,k, j = -k,\ldots,k\right)
$$
中值滤波器对于去除椒盐噪声等概率型噪声非常有效,因为它不受噪声干扰,能够更好地保留图像的边缘信息。在OpenCV中,可以使用cv2.medianBlur()函数来应用中值滤波。

1
blurred_image = cv2.medianBlur(image, ksize)

其中关键参数如下:

  • image是输入的图像
  • ksize是中值滤波核的大小,通常设置为一个大于1的奇数,表示邻域的大小

$$
H(i, j) = \frac{1}{2\pi\sigma^2} e^{-\frac{i^2+j^2}{2\sigma^2}}
$$
其中,ij是滤波器的坐标,$\sigma$是高斯函数的标准差。在OpenCV中,可以使用cv2.GaussianBlur()函数来应用高斯滤波。

1
blurred_image = cv2.GaussianBlur(image, (ksize_x, ksize_y), sigmaX, sigmaY)

其中关键参数如下:

  • image是输入的图像
  • (ksize_x, ksize_y)是高斯核的大小,通常设置为奇数,表示在x方向和y方向上的标准差
  • sigmaXsigmaY是高斯核函数在x方向和y方向上的标准差

锐化滤波器可以通过不同的算子来实现,其中一个常见的算子是拉普拉斯算子。其公式如下:

$$
\nabla^2 f = \frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y^2}
$$

在OpenCV中,可以使用 cv2.Laplacian 函数来应用拉普拉斯算子。

1
laplacian_image = cv2.Laplacian(image, ddepth, ksize)

其中关键参数如下:

  • image是输入的图像
  • ddepth是输出图像的深度,通常设置为cv2.CV_8U-1表示与输入图像相同的深度
  • ksize是拉普拉斯算子的核大小
  1. 形态学滤波

形态学滤波是基于形态的滤波方法,基本的形态操作包含膨胀、腐蚀,以及这两者的组合运算,如形态学开运算,闭运算,黑帽运算等等。

膨胀: 膨胀是一种形态学操作,用于求取局部最大值。在膨胀过程中,我们使用一个卷积核(也称为结构元素),将其与图像进行卷积操作。对于每个卷积核覆盖区域内的像素,将选择其中的最大值作为该区域内所有像素点的输出。膨胀操作可以使边缘变得更加明显,并将对象之间的间隙填充。
$$
\text{dilateAB}\ A(x,y) = \max_{(i,j) \in B}(B(i,j) + A(x+i, y+j))
$$
其中,$A$为原始图像,$B$为卷积核(也称为结构元素),$(x,y)$为当前像素坐标,$(i,j)$为卷积核中心点到当前像素点的距离。

1
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

其中关键参数如下:

  • src:原始图像

    kernel:卷积核/结构元素

    dst:输出图像(可选)

    anchor:锚点,指示卷积核中心位置(默认为(-1, -1))

    iterations:迭代次数,表示膨胀操作的次数(默认为1)

    borderType:边界扩充类型(默认为cv2.BORDER_CONSTANT),可选的值还有cv2.BORDER_REPLICATE

    borderValue:边界填充值(默认为0)

腐蚀: 腐蚀是一种形态学操作,用于求取局部最小值。在腐蚀过程中,我们同样使用一个卷积核与图像进行卷积操作。对于每个卷积核覆盖区域内的像素,将选择其中的最小值作为该区域内所有像素点的输出。腐蚀操作可以使边缘变得更加细化,并消除图像中的小的孔洞或细小的连通区域。
$$
\text{erodeAB}\ A(x,y) = \min_{(i,j) \in B}(B(i,j) + A(x+i, y+j))
$$

其中,$A$为原始图像,$B$为卷积核(也称为结构元素),$(x,y)$为当前像素坐标,$(i,j)$为卷积核中心点到当前像素点的距离。

1
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

其中关键参数如下:

  • src:原始图像
  • kernel:卷积核/结构元素
  • dst:输出图像(可选)
  • anchor:锚点,指示卷积核中心位置(默认为(-1, -1))
  • iterations:迭代次数,表示腐蚀操作的次数(默认为1)
  • borderType:边界扩充类型(默认为cv2.BORDER_CONSTANT),可选的值还有cv2.BORDER_REPLICATE
  • borderValue:边界填充值(默认为0)
  1. 边缘检测

边缘检测是数字图像处理中常用的技术,用于识别图像中物体边缘的位置。常见的边缘检测算法包括Sobel算子、Prewitt算子、Roberts算子和Canny边缘检测算法等。其中,Sobel算子和Canny算法是应用最为广泛的两种方法。

Sobel算子:是一种基于梯度的边缘检测算子,它通过计算图像的水平和竖直方向上的梯度来识别边缘。具体来说,Sobel算子使用卷积操作来对图像进行梯度计算,从而找到像素值变化最为剧烈的位置,即边缘。Sobel算子的计算简单高效,适用于许多实际场景。在OpenCV中,可以使用cv2.Sobel()函数来应用Sobel边缘检测。

1
cv2.Sobel(src, ddepth, dx, dy, ksize)

其中关键参数如下:

  • src:输入图像。
  • ddepth:输出图像的深度,通常设置为-1,表示输出图像与输入图像有相同的深度。
  • dxdy:分别表示在x和y方向上的导数的阶数。dx和dy都可以是0、1或者-1。
  • ksize:Sobel算子的孔径大小,可以取1、3、5或者7。

Canny算法:它是一种多阶段的边缘检测方法。Canny算法首先对图像进行高斯滤波以去除噪声,然后计算图像梯度的幅值和方向,接着利用非极大值抑制来细化边缘,最后通过双阈值处理来提取强边缘并抑制弱边缘,从而得到最终的边缘图像。Canny算法在实际应用中表现出较好的性能,尤其在复杂背景和噪声较多的图像中效果明显。本次试验中,我们使用 canny 计算锥桶边缘获得连接线,继而确定锥桶位置。在OpenCV中,可以使用cv2.Canny()函数来应用Canny边缘检测。

1
cv2.Canny(image, threshold1, threshold2)

其中关键参数如下:

  • image:输入图像。
  • threshold1threshold2:两个阈值,用于控制边缘检测的灵敏度。通常情况下,threshold2 会被用于边缘连接,而 threshold1 被用来控制强边缘的初始检测。推荐的 threshold2/threshold1 比值在2:1到3:1之间。

实验步骤:

  1. 驱动USB相机

在ubuntu中查看摄像头需要安装cheese:

1
sudo apt-get install cheese

在终端输入cheese查看摄像头

1
cheese

如果能如下图显示摄像头画面则表示摄像头正常

3_3
cheese摄像头画面

在ROS下获取usb_cam功能包,usb_cam功能包支持源码下载和本地下载:

1
2
sudo apt-get install ros-noetic-usb-cam  # 本地下载
git clone https://github.com/ros-drivers/usb_cam.git # 源码下载

智能车首次使用单目相机时,需要对其进行标定,首先获取camera_calibration功能包:

1
rosdep install camera_calibration

将一个9X6的国际棋盘格27mmX27mm打印到A4纸上,在终端启动如下指令:

1
rosrun camera_calibration cameracalibrator.py --size 9x6 --square 0.027 image:=/camera/image_raw camera:=/camera

将棋盘在相机前方上、下、左、右平移、翻转棋盘格,直到条形变为绿色。当calibration按钮亮起时,代表已经有足够的数据进行摄像头的标定,此时请按下calibration并等待一分钟左右,标定界面会变成灰色,单击“save”和“commit”将标定数据保存至本地,下次启动相机时会自动加载标定数据。

mono_0
标定过程,图源 camera_calibration 官方 Tutorials,单目相机情况

标定结束后,可以通过launch文件启动usb_cam:

1
roslaunch usb_cam usb_cam.launch

launch文件中加载的配置文件及其含义注释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
start_service_name: "start_capture"  # 定义重新启动暂停的流媒体的std_srvs::Empty服务的名称后缀
stop_service_name: "stop_capture" # 定义暂停相机轮询计时器的std_srvs::Empty服务的名称后缀
video_device: /dev/video0 # usb_cam设备串口名称
io_method: mmap # I/O方法
# - read - 适用于支持虚拟文件系统或块I/O的设备
# - mmap - 适用于具有直接libusb内存映射的设备
# - userptr - 适用于支持用户空间指针交换的用户空间设备
pixel_format: yuyv # 像素格式,与硬件相关,此处应为yuyv
color_format: yuv422p # 硬件编码的输入帧的芯片上颜色表示模式
# - yuv422p - YUV422 - 默认值,与大多数MJPEG硬件编码器兼容
# - yuv420p - YUV420 - H.264和H.265硬件编码器必需的
create_suspended: false # 指示节点在启动后是否立即开始流媒体,或者等到触发启动服务后再开始
full_ffmpeg_log: false # 是否允许抑制由libavcodec生成的警告消息,清除日志
camera_name: head_camera # 相机的ROS内部名称,用于生成camera_info消息
camera_frame_id: usb_cam # 用于生成坐标转换的帧ID
camera_transport_suffix: image_raw # image_transport用于生成话题名称的后缀
camera_info_url: "" # 相机校准数据的URI(可能是从相机校准中获取的YML文件)
image_width: 800 # 帧的宽度,应受到相机硬件支持
image_height:
[Something went wrong, please try again later.]

此时在终端订阅话题/image_raw 即可获得图像并进一步处理。 我们调用 rostopic 显示图像信息:

1
rostopic echo <camera_name>/raw
  1. 锥桶检测

上文我们在ROS中获取到的图像为sensor_msgs/Image格式,我们希望使用OpenCV处理图像,ROS中提供了CvBridge库,用于连接ROS格式数据与OpenCV格式数据:

image-20231010225010118

首先定义一个类,用于订阅ROS中摄像头传递回来的图像,self.bridge为CvBridge()的成员变量,用于将ROS格式信息转换为OpenCV格式信息:

1
2
3
4
5
6
7
8
9
class image_converter:
def __init__(self, fps):
# 创建cv_bridge,声明图像的发布者和订阅者
rospy.on_shutdown(self.cleanup)
self.bridge = CvBridge()
self.image_sub = rospy.Subscriber("/usb_cam/image_raw", Image, self.callback, queue_size=1)
self.fps = fps
self.count = 0
self.location = coneLocation()
3_1 3_2 3_3
三种情况的原始图片

定义callback函数作为回调函数,首先使用self.bridge.imgmsg_to_cv2(data, “bgr8”)转换图像格式为BGR8比特图像,每FPS取出一次图像,传入self.detectLocation函数用于检测锥桶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def callback(self, data):
# 使用cv_bridge将ROS的图像数据转换成OpenCV的图像格式
try:
cv_image = self.bridge.imgmsg_to_cv2(data, "bgr8")
# cv_image=cv_image[250:350, 350:470]
# cv_image=cv_image[0:800, 100:500]
img = np.array(cv_image, dtype=np.uint8)

except CvBridgeError as e:
print(e)

# 每fps次出图取一次
if(self.count % self.fps == 0):
self.count = 0
self.detectLocation(cv_image)
# 计数自增一
self.count += 1

接下来我们详细介绍self.detectLocation中检测锥桶的代码实现流程:

定义红色和蓝色锥桶的HSV颜色区间,通过inRange()函数分别将红、蓝锥桶从图像中分割出来并形成二值化图像,注意红色在HSV中有两个区间,使用inRange()二值化之后需要使用cv2.bitwise_or()融合两张图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 红色颜色区间
lower_orange1 = np.array([0, 30, 70])
lower_orange2 = np.array([15, 255, 255])
upper_orange1 = np.array([150, 30, 70])
upper_orange2 = np.array([180, 255, 255])
# 蓝色颜色区间
lower_blue = np.array([90, 40, 80])
upper_blue = np.array([135, 255, 255])
# 对HSV图像进行阈值处理,只获取亮橙色
imgThreshLow = cv2.inRange(hsv_img, lower_orange1, lower_orange2)
imgThreshHigh = cv2.inRange(hsv_img, upper_orange1, upper_orange2)
imgBlue = cv2.inRange(hsv_img, lower_blue, upper_blue)
imgRed = cv2.bitwise_or(imgThreshLow, imgThreshHigh)
image-20231110110658125
红色
image-20231110110622819
二值化后的红色、蓝色锥桶

然后进行形态学图像处理,主要使用腐蚀和膨胀平滑和处理图像,以便更容易检测图像中的锥桶。先通过腐蚀后膨胀消除小的伪影,腐蚀操作会使图像中的白色区域减小,它通过内核在图像上滑动并将内核下的像素值设置为内核中的最小值,膨胀操作会使图像中的白色区域扩大,它通过内核在图像上滑动并将内核下的像素值设置为内核中的最大值。再次进行膨胀操作,此时iterations=11表示膨胀操作将重复执行11次。这一步的目的是填补可能由于反光带而导致的橙色边缘之间的间隙,将交通锥桶还原到标准大小。最后,再次进行腐蚀操作,iterations=7表示腐蚀操作将重复执行7次。这一步的目的是对图像进行最后的平滑处理,以确保交通锥桶的形状较为均匀和规则,以便进行后续的边缘检测。

1
2
3
4
5
6
7
8
9
10
11
# 通过先腐蚀然后膨胀来消除小的伪影
threshed_img_smooth_red = cv2.erode(imgRed, kernel, iterations=3)
threshed_img_smooth_red = cv2.dilate(threshed_img_smooth_red, kernel, iterations=2)
# 通过先膨胀然后腐蚀来处理带有反光带的锥桶,以消除一个橙色边缘和另一个之间的间隙,并将交通锥桶恢复到标准大小
smoothed_img_red = cv2.dilate(threshed_img_smooth_red, kernel, iterations=11)
smoothed_img_red = cv2.erode(smoothed_img_red, kernel, iterations=7)

threshed_img_smooth_blue = cv2.erode(imgBlue, kernel, iterations=3)
threshed_img_smooth_blue = cv2.dilate(threshed_img_smooth_blue, kernel, iterations=2)
smoothed_img_blue = cv2.dilate(threshed_img_smooth_blue, kernel, iterations=11)
smoothed_img_blue = cv2.erode(smoothed_img_blue, kernel, iterations=7)
image-20231110111039863
腐蚀、膨胀后的红色锥桶
image-20231110110929359
腐蚀、膨胀后的蓝色锥桶

然后使用Canny边缘检测算法来检测图像中的边缘,并使用cv2.findContours查找图像中的轮廓,并获得这些轮廓的信息。

1
2
3
4
5
# 检测图像中的所有边缘
edges_img_red = cv2.Canny(smoothed_img_red, 80, 160)
edges_img_blue = cv2.Canny(smoothed_img_blue, 80, 160)
contours_red, hierarchy_red = cv2.findContours(edges_img_red, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours_blue, hierarchy_blue = cv2.findContours(edges_img_blue, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image-20231110111201545
边缘检测的绿色锥桶
image-20231110111024168
边缘检测的红色锥桶
image-20231110110823080
边缘检测的红色、绿色锥桶

最后,使用道格拉斯-佩克算法平滑曲线,对锥桶轮廓点进行多边形拟合,判断轮廓如果是三角形,就在周围绘制一个边界框,标明识别到锥桶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for cnt in contours_red:
boundingRect = cv2.boundingRect(cnt)
approx = cv2.approxPolyDP(cnt, 0.06 * cv2.arcLength(cnt, True), True)
# 如果轮廓是三角形,就在其周围绘制一个边界框,并给它贴上交通锥桶的标签
if len(approx) == 3:
x, y, w, h = cv2.boundingRect(approx)
rect = (x, y, w, h)
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 3)
bottomLeftCornerOfText = (x, y)
cv2.putText(image, 'red',
bottomLeftCornerOfText,
font,
fontScale,
fontColor,
lineType)
image-20231110111115872 image-20231110110906534 image-20231110110833969
识别到的锥桶
  1. 红灯检测

检测红绿灯的代码框架与上文检测锥桶类似,主要区别为两者的callback()函数,接下来我们详细介绍self.RedAndGreen()函数,以下是总体设计的流程图:

红灯
红灯检测流程图

首先我们先指定红绿灯设备中红色的HSV颜色阈值上下界,通过cv2.cvtColor函数将图片从BGR空间转换至HSV空间,然后通过cv2.inRange()函数过滤出红色部分的二值化函数

1
2
3
4
5
6
7
# 指定红色值的范围
minRed = np.array([0, 127, 128]) # 红色阈值下界
maxRed = np.array([10, 255, 255]) # 红色阈值上界
# BGR -> HSV颜色空间
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 确定目标区域
mask_red = cv2.inRange(img_hsv, minRed, maxRed) # 过滤出红色部分,获得红色的掩膜

我们无法直接对二值化后的图片进行圆形检测,主要原因有两个:

  • 由于红绿灯上檐会产生反光,我们需要尽可能消除这部分,否则会对圆形检测产生许多干扰
  • 红绿灯圆形范围边界模糊、残缺,我们需要尽可能填满这部分,让其形成一个便于检测的圆形
image-20231110001748567 image-20231110001634488

因此我们对二值化后的图像先膨胀,再腐蚀,然后再膨胀:

  • 先膨胀:补充完整红绿灯圆形边界
  • 再腐蚀:去除上檐的反光部分
  • 最后膨胀:对红绿灯边界进行平滑处理,以确保红绿灯边界的形状较为均匀和规则
1
2
3
4
5
6
# 膨胀
threshed_img_smooth_red = cv2.dilate(mask_red, kernel, iterations=2)
# 腐蚀
threshed_img_smooth_red = cv2.erode(threshed_img_smooth_red, kernel, iterations=6)
# 膨胀
threshed_img_smooth_red = cv2.dilate(threshed_img_smooth_red, kernel, iterations=2)

实现的效果图分别如下,可见可以得到一个比较好的圆形,因此后续的圆形检测能够实现比较好的效果:

image-20231110002608793
初次膨胀
image-20231110002624993
再腐蚀
image-20231110002657872
最后膨胀

然后我们对处理完毕之后的图片使用cv2.HoughCircles()函数进行圆形检测,设定圆心距20,canny阈值50,投票数20,最小半径1,最大半径400

1
2
# 使用HoughCircles函数检测圆形
circles = cv2.HoughCircles(threshed_img_smooth_red, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=20, minRadius=1, maxRadius=400)

由于可能会检测到多个圆形,我们检测到圆形后只画出最大的圆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if circles is not None:
circles = np.uint16(np.around(circles))

# 寻找半径最大的圆
for i in range(len(circles[0])):
if circles[0, i][2] > max_radius:
max_radius = circles[0, i][2]
max_circle_index = i

# 绘制半径最大的圆
if max_circle_index != -1:
center = (circles[0, max_circle_index][0], circles[0, max_circle_index][1])
radius = circles[0, max_circle_index][2]
cv2.circle(img, center, radius, (255, 0, 255), 3)
cv2.putText(img, 'Largest Circle', (center[0] - 20, center[1] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6,
(255, 255, 0), 2)

在检测到圆形的基础上,检测红色二值化后的圆形边界,如果检测到圆形边界的$宽\times 高>150\times 150$,则判断检测到了红灯并且框选出红灯。

1
2
3
4
5
6
7
8
9
10
11
12
# 查找轮廓
edges_img_red = cv2.Canny(threshed_img_smooth_red, 80, 160)
contours1, hierarchy1 = cv2.findContours(edges_img_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 轮廓检测 红灯
contour_image = img.copy()
cv2.drawContours(contour_image, contours1, -1, (0, 255, 0), 2) # 绘制所有轮廓,颜色为绿色,线宽为2
# 检测红灯
for cnt in contours1:
(x, y, w, h) = cv2.boundingRect(cnt)
if w*h < 150*150:
continue
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.putText(img, 'red_light', (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
image-20231110094557775
绿色边界为Canny边缘检测
image-20231110094629516
紫色为识别到的最大圆,红框圈出识别到的红灯

参考资料:

  1. wiki.ros.org/usb_cam
  2. ROS下采用camera_calibration进行单目相机标定_rosrun camera_calibration cameracalibrator.py
  3. cv_bridge/Tutorials - ROS Wiki
  4. camera_calibration - ROS Wiki

实验总结:

本次实验旨在通过相机标定和图像处理技术,实现对锥桶和红绿灯信息的准确提取。采用了相机标定源码和OpenCV等工具库,我们成功地完成了这一目标,取得了一系列令人满意的成果。

首先,相机标定是我们实验的重要一环。通过使用先进的相机标定源码,我们准确地调整了相机的内外参数,确保了图像的准确性和可靠性。这个步骤为后续的图像处理提供了可靠的基础,使得我们的算法能够更好地映射到实际场景。在锥桶信息的处理过程中,我们应用了中值滤波和形态学滤波。中值滤波的使用帮助我们有效地去除了图像中的噪声,提高了图像质量。形态学滤波则在处理锥桶的形态和数量时发挥了关键作用,使得我们能够精准地识别和定位锥桶。红绿灯信息的提取则涉及到边缘检测算法的应用。通过合理选择算法参数,我们成功地捕捉到交通信号的位置,确保了对交通灯状态的准确判断。这一步骤对于交通场景的智能识别具有重要的实际意义。

整个实验过程中,我们团队注重技术细节的处理,追求精益求精。通过灵活运用图像处理算法,我们建立了高效、稳健的处理流程。对工具库的熟练应用使我们能够在实际场景中更加灵活地解决问题。在未来的工作中,我们将继续优化算法,提高处理的速度和准确度。同时,我们也将探索更多的图像处理技术,以适应不同场景和环境的需求。

总体来说,这次实验为智能车的运行提供了有效的数据决策,也为我们在今后的学习生涯里学习图像处理技术奠定了坚实的基础。