ROS高效进阶第四章 -- 机器视觉处理之图像格式,usb_cam,摄像头标定,opencv和cv_bridge引入

avatar
作者
筋斗云
阅读量:4

机器视觉处理之图像格式,usb_cam,摄像头标定,opencv和cv_bridge引入

1 资料

从本文开始,我们用四篇文章学习ROS机器视觉处理,本文先学习一些外围的知识,为后面的人脸识别,目标跟踪和yolov5目标检测做准备。
我的笔记本是Thinkpad T14 i7 + Nvidia MX450,系统是ubuntu20.04,ros是noetic。由于很多驱动与硬件强相关,请读者注意这点。
本文的参考资料有:
(1)《ROS机器人开发实践》胡春旭 第7章的前三节
(2)图像编码与 H264 基础知识
(3)ubuntu20.04如何识别到连接的摄像头?
(4)ROS学习 – 摄像头的标定
(5)相机内参标定究竟标了什么?相机内参外参保姆级教程
(6)ROS高效入门第一章 – ROS历史与现状 2.8节
(7)W3Cschool - OpenCV教程
(8)ChatGPT4.0(很多背景知识,问他比较快,但是要小心它忽悠你!)

2 正文

2.1 颜色编码格式,图像格式和视频压缩格式

(1)RGB和BGR:这是两种常见的颜色编码格式,分别代表了红、绿、蓝三原色。不同之处在于,RGB按照红、绿、蓝的顺序存储颜色信息,而BGR按照蓝、绿、红的顺序存储。

rgb8图像格式:常用于显示系统,如电视和计算机屏幕。 	RGB值以8 bits表示每种颜色,总共可以表示256×256×256=16777216种颜色。 	例如: (255,0,0) 表示红色,(0,255,0) 表示绿色,(0,0,255) 表示蓝色。 bgr8图像格式:由一些特定的硬件制造商采用, 	软件方面最著名的就是opencv,其默认使用BGR的颜色格式来处理图像。 	与RGB不同, (0,0,255) 在BGR中表示红色,(0,255,0) 仍然表示绿色,(255,0,0) 表示蓝色。 

在自动驾驶里,使用rgb8图像格式的图像,一般称为原图,是数据量最大的格式,没有任何压缩。
(2)YUV:这是另一种颜色编码方法,与RGB模型不同的是,它将图像信息分解为亮度(Y)和色度(U和V)两部分。这种方式更接近于人类对颜色的感知方式。

Y:代表亮度信息,也就是灰阶值。 U:从色度信号中减去Y得到的蓝色信号的差异值。 V:从色度信号中减去Y得到的红色信号的差异值。 

YUV颜色编码主要用在电视系统以及视频编解码标准中,在这些系统中,Y通道信息可以单独使用,这样黑白电视机也能接收和显示信号。而彩色信息则通过U和V两个通道传输,只有彩色电视机才能处理。这样设计兼容了黑白电视和彩色电视。
YUV色彩空间相比RGB色彩空间,更加符合人眼对亮度和色彩的敏感度,在视频压缩时,可以按照人眼的敏感度对YUV数据进行压缩,以达到更高的压缩比。
由于历史和技术的原因,YUV的标准存在多种,例如YUV 4:4:4、YUV 4:2:2和YUV 4:2:0等,这些主要是针对U和V通道的采样方式不同定义的。采样不同,对应的压缩比也不同。
(3)图像压缩格式:

jpeg:Joint Photographic Experts Group,是一种常见的用于静态图像的损失性压缩格式, 	它特别适合于全彩色和灰度图片,被广泛使用。 	通常情况下,JPEG可以提供10:1到20:1的有损压缩比,根据图像质量自由调整。 png: Portable Network Graphics,PNG是一种无损压缩格式,主要使用了DEFLATE算法。 	由于这是无损压缩,所以解压缩图像可以完全恢复原始数据。 	被广泛应用于需要高质量图像的场景,如网页设计、艺术作品等。 bmp:Bitmap,BMP是Windows系统中常用的一种无压缩的位图图像格式,通常会创造出较大的文件。 

位图(Bitmap)是一种常见的计算机图形,最小单位是像素,每个像素都包含一定量的信息,如颜色和亮度等。位图图像的一个主要特点就是,在放大查看时,可以看到图像的像素化现象,也就是我们常说的"马赛克"。BMP、JPEG、GIF、PNG等都是常见的位图格式。
(4)H264和H265:这是两个视频压缩格式,也是两种视频编解码标准。以1280*720的摄像头为例,如果是rgb8格式的原图,一帧图像的大小是:
在这里插入图片描述
如果是一小时的视频,那将是非常大的数据量,对网络传输,数据存储,都是很大的压力。而H264通过种种帧间操作,可以达到10:1到50:1的压缩比,甚至更高。H265更进一步,压缩比更高,用来解决4K或8K视频的传输。更具体的原理见:图像编码与 H264 基础知识
在自动驾驶领域,图像数据也使用h264格式,主要用于数采和回放,控制数据量。

2.2 usb_cam

(1)linux针对摄像头硬件有一套Video for Linux内核驱动框架,对应提供的有命令行工具 v4l2-ctl (Video for Linux 2),可以查看摄像头硬件信息:

ls /dev/video0  //一般video0是笔记本自带摄像头设备文件 v4l2-ctl -d /dev/video0 --all 

这里截取了部分关键信息,下面的usb_cam的launch文件将用到:
在这里插入图片描述
(2)usb_cam是ros里usb camera的软件包,一般称为ros摄像头驱动,但这是一个应用程序,其调用v4l2并通过ros topic发出图像数据。搞机器视觉,第一步就是要有图。
安装并启动usb_cam,查看图像:

sudo apt-get install ros-noetic-usb-cam  roslaunch usb_cam usb_cam-test.launch rqt_image_view 

usb_cam-test.launch:

<launch>   <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >     //指定设备文件名,默认是/dev/video0     <param name="video_device" value="/dev/video0" />     // 宽和高分辨率	     <param name="image_width" value="640" />     <param name="image_height" value="480" />     // 像素编码,可选值:mjpeg,yuyv,uyvy     <param name="pixel_format" value="yuyv" />     <param name="color_format" value="yuv422p" />     // camera坐标系名     <param name="camera_frame_id" value="usb_cam" />     // IO通道,可选值:mmap,read,userptr,大数据量信息一般用mmap     <param name="io_method" value="mmap"/>   </node>   <node name="image_view" pkg="image_view" type="image_view" respawn="false" output="screen">   	// 指定发出的topic名:/usb_cam/image_raw     <remap from="image" to="/usb_cam/image_raw"/>     <param name="autosize" value="true" />   </node> </launch> 

在这里插入图片描述
(3)/usb_cam/image_raw的数据结构体:

rostopic info /usb_cam/image_raw rosmsg show  sensor_msgs/Image 
//消息头,每个topic都有 std_msgs/Header header	   uint32 seq   time stamp   // 坐标系名   string frame_id // 高和宽分辨率 uint32 height uint32 width // 无压缩的图像编码格式,包括rgb8,YUV444 string encoding // 图像数据的大小端存储模式 uint8 is_bigendian // 一行图像数据的字节数量,作为步长参数 uint32 step // 存储图像数据的柔性数组,大小是step*height uint8[] data 

/usb_cam/image_raw内容展示:
在这里插入图片描述
(4)/usb_cam/image_raw/compressed的数据结构体:

rostopic info /usb_cam/image_raw/compressed rosmsg show sensor_msgs/CompressedImage 
std_msgs/Header header   uint32 seq   time stamp   string frame_id // 压缩的图像编码格式,jpeg,png string format uint8[] data 

/usb_cam/image_raw/compressed内容展示:
在这里插入图片描述
(5)踩坑记录
当我拉起来usb_cam时,死活不出图,显示框有,但就是黑的,找了半天资料也没解决。
后来发现,Thinkpad T14的摄像头是有个物理开关的,需要手动打开,不然摄像头就被遮挡了!

2.3 摄像头标定

2.3.1 标定引入

(1)Calibration:翻译过来就是校准和标定。
(2)摄像头标定:Camera Calibration是计算机视觉中的一种关键技术,其目的是确定摄像头的内部参数(Intrinsic Parameters)和外部参数(Extrinsic Parameters)。

内部参数:包括焦距、主点坐标以及镜头畸变等因素。 	这些参数与相机本身的硬件有关,如镜头和图像传感器等,一般由厂家提供。 外部参数:摄像头相对于环境的位置和方向。 	例如,它可能描述了一个固定摄像头相对于周围环境的姿态或者安装位置。 	以汽车为例,外参包括各个摄像头之间的关系,摄像头与radar,摄像头与lidar的关系。 

(3)汽车各种传感器的之间的相对位置和朝向,用3自由度的旋转矩阵和3自由度的平移向量表示,这些外参由整车厂自己标。
一般整车下线之后,进入特定的房间,使用静态标靶、定位器的等高精度设备,完成Camera、radar、Lidar等传感器的标定,称之为产线标定,也叫做下线标定。

2.3.2 笔记本摄像头内参标定

这里我们使用标定常用的标靶图形,完成笔记本摄像头的内参标定。usb_cam可以使用内参标定,避免图像畸变。
(1)安装标定功能包(ubuntu20.04+noetic)

sudo apt-get install ros-noetic-camera-calibration 

(2)创建 robot_vision 软件包,并标定相关文件

cd ~/catkin_ws/src catkin_create_pkg robot_vision cv_bridge image_transport sensor_msgs std_msgs geometry_msgs message_generation roscpp rospy  cd robot_vision  mkdir doc launch touch launch/cameta_calibration.launch 

图片链接:标定靶图片
在这里插入图片描述

cameta_calibration.launch:

<launch>   // 使用usb_cam包,发出/usb_cam/image_raw图像数据   <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >     <param name="video_device" value="/dev/video0" />     <param name="image_width" value="640" />     <param name="image_height" value="480" />     <param name="pixel_format" value="yuyv" />     <param name="camera_frame_id" value="usb_cam" />     <param name="io_method" value="mmap"/>   </node>   // 使用标定功能包,完成标定。   // 参数中,8x6表示横向8个内部角点,竖向有6个   // square 是每个棋盘格的边长   // /usb_cam/image_raw是监听的图像topic   <node       pkg="camera_calibration"       type="cameracalibrator.py"       name="camera_calibration"       output="screen"       args="--size 8x6 --square 0.024 image:=/usb_cam/image_raw camera:=/usb_cam"   /> </launch> 

(3)编译并运行

cd ~/catkin_ws/ catkin_make --source src/robot_vision  source devel/setup.bash roslaunch robot_vision cameta_calibration.launch 

在这里插入图片描述
不断晃动,直到COMMIT按键亮起,然后点击,即可生成标定文件,本人的路径为:/home/mm/.ros/camera_info/head_camera.yaml。具体的生成原理和文件内容,这里暂不深究,可以参考链接:相机内参标定究竟标了什么?相机内参外参保姆级教程

2.4 opencv和cv_bridge引入

(1)opencv和cv_bridge的简介可以看本人之前的博客:ROS高效入门第一章 – ROS历史与现状 2.8节
安装opencv(ubuntu20.04+noetic):

sudo apt-get install ros-noetic-vision-opencv libopencv-dev python3-opencv 

(2)opencv和cv_bridge的简单架构图如下:
在这里插入图片描述
根据这个图,在ros里,处理图像的流程一般是:

    # 第一步:使用cv_bridge将ROS的图像数据转换成OpenCV的图像格式 	cv_image = cv_bridge.imgmsg_to_cv2(msg, "bgr8")  	# 第二步:使用opencv进行图像处理 	。。。 	 	# 第三步,再将opencv格式额数据转换成ros image格式的数据 	ros_image = cv_bridge.cv2_to_imgmsg(cv_image, "bgr8") 

本文不深入讲解opencv,推荐一个资料:W3Cschool - OpenCV教程

(3)在 上节的robot_vision包里,我们新增一个cv_bridge的小样例,主要功能是在捕捉到的图像上打个蓝色的圆标。

cd ~/catkin_ws/src/robot_vision  mkdir scripts touch scripts/cv_bridge_test.py launch/cv_bridge_test.launch 

cv_bridge_test.py:

#! /usr/bin/env python3 # -*- coding: utf-8 -*- import rospy import cv2 from functools import partial from cv_bridge import CvBridge, CvBridgeError from sensor_msgs.msg import Image  def image_cb(msg, cv_bridge, image_pub):     # 使用cv_bridge将ROS的图像数据转换成OpenCV的图像格式     try:         cv_image = cv_bridge.imgmsg_to_cv2(msg, "bgr8")     except CvBridgeError as e:         print(e)      # 在opencv的显示窗口中绘制一个圆,作为标记     # cv_image.shape返回一个元组,包含图像的行数(高度),列数(宽度)和通道数(通常是3个通道:BGR)     (rows, cols, channels) = cv_image.shape     # 当图像的宽度和高度都大于60时,才执行画圆标动作     if cols > 60 and rows > 60:     	# 在计算机图像处理中,图像的原点(0,0)通常定义为图像的左上角。(60,60)是圆心的坐标。     	# 30是圆的半径。     	# (255,0,0)定义了圆的颜色。在OpenCV中,默认的颜色空间是BGR,所以这其实是绘制了一个蓝色的圆。     	# -1表示填充圆。如果这个值是正数,则代表绘制的圆的线宽;如果是负数,则代表填充该圆。         cv2.circle(cv_image, (60,60), 30, (255,0,0), -1)      # 使用Opencv的接口,显示Opencv格式的图像     cv2.imshow("ycao: opencv image window", cv_image)     cv2.waitKey(3)      # 再将opencv格式额数据转换成ros image格式的数据发布     try:         image_pub.publish(cv_bridge.cv2_to_imgmsg(cv_image, "bgr8"))     except CvBridgeError as e:         print(e)  def main():     rospy.init_node("cv_bridge_test")     rospy.loginfo("starting cv_bridge_test node")      bridge = CvBridge()     image_pub = rospy.Publisher("/cv_bridge_image", Image, queue_size=1)     bind_image_cb = partial(image_cb, cv_bridge=bridge, image_pub=image_pub) 	// 订阅/usb_cam/image_raw,然后再回调函数里处理图像,并发布出来     rospy.Subscriber("/usb_cam/image_raw", Image, bind_image_cb)     rospy.spin()     cv2.destroyAllWindows() if __name__ == "__main__":     main() 

cv_bridge_test.launch

<launch>   <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >     <param name="video_device" value="/dev/video0" />     <param name="image_width" value="640" />     <param name="image_height" value="480" />     <param name="pixel_format" value="yuyv" />     <param name="camera_frame_id" value="usb_cam" />     <param name="io_method" value="mmap"/>   </node>   <node       pkg="robot_vision"       type="cv_bridge_test.py"       name="cv_bridge_test"       output="screen"   />   <node       pkg="rqt_image_view"       type="rqt_image_view"       name="rqt_image_view"       output="screen"   /> </launch> 

(4)编译并运行

cd ~/catkin_ws/ catkin_make --source src/robot_vision  source devel/setup.bash roslaunch robot_vision cv_bridge_test.launch 

在这里插入图片描述

3 总结

本文主要系统介绍了机器视觉处理的外围知识,引入了opencv和cv_bridge,后面几篇文章,我们将用它们继续丰富 robot_vision 软件包。
本文的样例托管在本人的github上:robot_vision

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!