在嵌入式Linux和计算机视觉开发中,V4L2(Video4Linux2)是处理视频输入输出的核心框架。这个教程将深入探讨YUV与RGB两种主流图像格式的特性差异,并详细讲解它们之间的转换原理和显示实现。作为第五篇V4L2系列教程,我们假设读者已经掌握了基础的V4L2设备操作和图像采集知识。
YUV和RGB是两种完全不同的色彩编码方式。RGB采用红绿蓝三原色混合的原理,而YUV则将亮度(Y)与色度(UV)分离。这种分离特性使YUV在视频传输和存储中占据优势,而RGB则更适合图像处理和显示。理解它们的转换机制,是开发视频处理系统的关键一步。
YUV家族包含多种子格式,主要区别在于色度采样率:
在Linux V4L2中,常见的YUV格式标识符包括:
注意:V4L2设备支持的格式可通过
v4l2-ctl --list-formats命令查看
RGB同样有多种内存排列方式:
V4L2中对应的宏定义:
标准BT.601转换公式(适用于SDTV):
c复制// YUV转RGB基本公式
R = Y + 1.402*(V-128)
G = Y - 0.344*(U-128) - 0.714*(V-128)
B = Y + 1.772*(U-128)
实际代码实现时需要处理:
使用SSE指令集优化的示例代码片段:
c复制void yuv422_to_rgb24_sse(uint8_t *yuv, uint8_t *rgb, int width, int height) {
__m128i y_mask = _mm_set1_epi16(0x00FF);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x += 8) {
// 加载16个YUV像素(8个实际像素,因422采样)
__m128i yuv_data = _mm_loadu_si128((__m128i*)(yuv + y*width*2 + x*2));
// 分离Y和UV分量
__m128i y_values = _mm_and_si128(yuv_data, y_mask);
__m128i uv_values = _mm_srli_epi16(yuv_data, 8);
// 转换计算(此处简化,实际需完整矩阵运算)
// ... SSE指令实现转换公式 ...
// 存储RGB结果
_mm_storeu_si128((__m128i*)(rgb + y*width*3 + x*3), rgb_result);
}
}
}
实测数据:在i7-1165G7上,1080P图像转换时间
- 纯C代码:~25ms
- SSE优化:~6ms
- OpenCL GPU加速:~2ms
V4L2显示输出的关键步骤:
c复制struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; // 输出RGB格式
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("设置输出格式失败");
}
避免屏幕撕裂的两种方案:
c复制struct v4l2_requestbuffers req = {0};
req.count = 2; // 双缓冲
req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
c复制struct v4l2_streamparm parm = {0};
parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
parm.parm.output.capability = V4L2_CAP_TIMEPERFRAME;
parm.parm.output.timeperframe.numerator = 1;
parm.parm.output.timeperframe.denominator = 60; // 60Hz
ioctl(fd, VIDIOC_S_PARM, &parm);
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 整体偏绿 | YUV格式识别错误(如把NV12当作YUYV) | 确认V4L2的pixelformat设置正确 |
| 颜色错位 | RGB通道顺序错误(BGR当作RGB) | 交换R和B分量处理顺序 |
| 纵向条纹 | 行对齐问题(stride不等于width*bpp) | 检查并设置正确的bytesperline参数 |
bash复制perf record -g ./your_program
perf report
bash复制streamline -e your_program
bash复制vtune -collect hotspots -- ./your_program
cpp复制cv::Mat yuv_mat(height*3/2, width, CV_8UC1, yuv_data);
cv::Mat rgb_mat;
cv::cvtColor(yuv_mat, rgb_mat, cv::COLOR_YUV2RGB_NV12);
// 处理后转回YUV
cv::Mat processed_yuv;
cv::cvtColor(processed_rgb, processed_yuv, cv::COLOR_RGB2YUV_I420);
c复制drmModeSetPlane(fd, plane_id, crtc_id, fb_id, 0,
x, y, width, height,
0, 0, width<<16, height<<16);
c复制struct v4l2_buffer buf_out = {0};
buf_out.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
buf_out.m.planes = &plane_out;
buf_out.length = 1;
ioctl(fd, VIDIOC_QBUF, &buf_out);
在实际项目中,我发现YUV-RGB转换的性能往往成为系统瓶颈。通过将转换环节下沉到FPGA实现,我们曾将4K视频的处理延迟从28ms降低到1.2ms。关键是在FPGA中实现流水线化的转换电路,同时利用DDR内存的突发传输特性。