嵌入式Linux摄像头采集与图像处理
一句话总览:从插上摄像头到存出 JPG 图片,本文用流程图和可视化图表串联 V4L2 驱动框架、YUV 颜色格式、RGB 转换公式、libjpeg 压缩库的完整知识链,每个概念都有生活类比帮助理解。
专业术语读音速查
技术术语在交流中常常需要读出来,下表列出了国内嵌入式工程师最通用的读法------照着念就能跟同行无障碍交流。
| 术语 | 中文读法 | 说明 |
|---|---|---|
| V4L2 | V-4-L-2,或 v- fò- tù |
Video for Linux Two,逐个读字母 |
| YUV | Y-U-V,或 wài- yōu- wéi |
颜色空间,逐个读字母 |
| YUYV | Y-U-Y-V,或 wài- yōu- wài- wéi |
像素格式,逐个读字母 |
| RGB | R-G-B,或 àr- jī- bī |
颜色空间,逐个读字母 |
| JPEG | jié- pài- gé |
/ˈdʒeɪpeɡ/,中文音译"杰派格" |
| libjpeg | lì- bù- jié- pài- gé |
library + JPEG 的连读 |
| ioctl | I/O control,或 ài- ōu- kòng |
Input/Output Control 缩写 |
| mmap | M-map,或 m- mài- pǔ |
memory map 缩写 |
| select | sè- lái- kè- tè |
/sɪˈlekt/,中文音译"瑟莱克特" |
| Planar | pǔ- léi- nà |
/ˈpleɪnɑːr/,平面存储方式 |
| Packed | pài- kè- tè |
/pækt/,打包存储方式 |
| DCT | D-C-T,或 dé- xī- tì |
离散余弦变换,读字母 |
| Huffman | hā- fū- màn |
/ˈhʌfmən/,霍夫曼编码 |
| FourCC | Four-C-C,或 fò- kè- kè |
Four Character Code |
| v4l2-ctl | V4L2 control,或 v- fò- tù- kòng |
V4L2 控制工具 |
| VIDIOC | wéi- dí- ō- kè |
V4L2 的 ioctl 命令前缀 |
| MIPI | mǐ- pǐ |
手机摄像头接口标准 |
| CSI | C-S-I,或 xī- s- ài |
Camera Serial Interface |
| HDMI | H-D-M-I,或 hà- d- m- ài |
高清多媒体接口 |
| BT.601 | BT-601,或 bī- tì- liù- líng- yī |
标清转换标准,读 B-T-601 |
| BT.709 | BT-709,或 bī- tì- qī- líng- jiǔ |
高清转换标准,读 B-T-709 |
| BT.2020 | BT-2020,或 bī- tì- èr- líng- èr- líng |
超高清转换标准,读 B-T-2020 |
| clamp | kè- lán- pǔ |
钳位操作 |
| ISP | I-S-P,或 ài- s- pì |
Image Signal Processor |
| UVC | U-V-C,或 yōu- wéi- xī |
USB Video Class |
| EXIF | yì- xī- fú |
Exchangeable Image File Format |
| YCbCr | Y-Cb-Cr,或 wài- xī- bī- xī- àr |
数字颜色空间 |
| NV12 | NV-12,或 n- wéi- shí- èr |
Android 视频格式 |
| Bayer | bèi- yě |
/ˈbeɪər/,Bayer 阵列发明者 |
| CMOS | C-MOS,或 xī- māo- sī |
互补金属氧化物半导体 |
| CCD | C-C-D,或 xī- xī- dì |
电荷耦合器件 |
小建议:开会或面试时直接读字母(如 V4L2 念"V-4-L-2",YUV 念"Y-U-V")是最保险的,所有人都听得懂。
目录
- [第一章 摄像头基础与 V4L2 驱动框架](#第一章 摄像头基础与 V4L2 驱动框架)
- [1.1 摄像头硬件基础](#1.1 摄像头硬件基础)
- [1.2 V4L2 驱动框架](#1.2 V4L2 驱动框架)
- [1.3 V4L2 拍照 10 步流程](#1.3 V4L2 拍照 10 步流程)
- [第二章 图像格式与转换](#第二章 图像格式与转换)
- [2.1 YUV 格式详解](#2.1 YUV 格式详解)
- [2.2 YUV 数据转 RGB](#2.2 YUV 数据转 RGB)
- [2.3 JPEG 压缩原理与 libjpeg](#2.3 JPEG 压缩原理与 libjpeg)
- [第三章 完整代码工程](#第三章 完整代码工程)
- [3.1 系统架构总览](#3.1 系统架构总览)
- [3.2 代码实现](#3.2 代码实现)
- 附录
- [A. 常见报错与解决](#A. 常见报错与解决)
- [B. 调试命令速查](#B. 调试命令速查)
- [C. 关键概念速查表](#C. 关键概念速查表)
- [D. 知识体系脑图](#D. 知识体系脑图)
第一章 摄像头基础与 V4L2 驱动框架
1.1 摄像头硬件基础
一句话总结
摄像头本质上是一个"光电转换器"------外界光线通过镜头投射到传感器上,传感器把光信号转成电信号,再经过模数转换变成数字信号,最终交给 CPU 处理。
类比:摄像头就像人的眼睛------角膜(镜头)把光线聚焦到视网膜(传感器)上,视神经(数据总线)把信号传给大脑(CPU)。
1.1.1 摄像头工作原理
#mermaid-svg-jcUWBBUTdPCqNwL7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jcUWBBUTdPCqNwL7 .error-icon{fill:#552222;}#mermaid-svg-jcUWBBUTdPCqNwL7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jcUWBBUTdPCqNwL7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .marker.cross{stroke:#333333;}#mermaid-svg-jcUWBBUTdPCqNwL7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jcUWBBUTdPCqNwL7 p{margin:0;}#mermaid-svg-jcUWBBUTdPCqNwL7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster-label text{fill:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster-label span{color:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster-label span p{background-color:transparent;}#mermaid-svg-jcUWBBUTdPCqNwL7 .label text,#mermaid-svg-jcUWBBUTdPCqNwL7 span{fill:#333;color:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .node rect,#mermaid-svg-jcUWBBUTdPCqNwL7 .node circle,#mermaid-svg-jcUWBBUTdPCqNwL7 .node ellipse,#mermaid-svg-jcUWBBUTdPCqNwL7 .node polygon,#mermaid-svg-jcUWBBUTdPCqNwL7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .rough-node .label text,#mermaid-svg-jcUWBBUTdPCqNwL7 .node .label text,#mermaid-svg-jcUWBBUTdPCqNwL7 .image-shape .label,#mermaid-svg-jcUWBBUTdPCqNwL7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-jcUWBBUTdPCqNwL7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .rough-node .label,#mermaid-svg-jcUWBBUTdPCqNwL7 .node .label,#mermaid-svg-jcUWBBUTdPCqNwL7 .image-shape .label,#mermaid-svg-jcUWBBUTdPCqNwL7 .icon-shape .label{text-align:center;}#mermaid-svg-jcUWBBUTdPCqNwL7 .node.clickable{cursor:pointer;}#mermaid-svg-jcUWBBUTdPCqNwL7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .arrowheadPath{fill:#333333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jcUWBBUTdPCqNwL7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jcUWBBUTdPCqNwL7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jcUWBBUTdPCqNwL7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster text{fill:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 .cluster span{color:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jcUWBBUTdPCqNwL7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jcUWBBUTdPCqNwL7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-jcUWBBUTdPCqNwL7 .icon-shape,#mermaid-svg-jcUWBBUTdPCqNwL7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jcUWBBUTdPCqNwL7 .icon-shape p,#mermaid-svg-jcUWBBUTdPCqNwL7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jcUWBBUTdPCqNwL7 .icon-shape .label rect,#mermaid-svg-jcUWBBUTdPCqNwL7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jcUWBBUTdPCqNwL7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jcUWBBUTdPCqNwL7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jcUWBBUTdPCqNwL7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输出部分
传感器部分
光学部分
外界光线
镜头组
聚焦光线
CMOS/CCD 传感器
光信号→电信号
ADC 模数转换
模拟信号→数字信号
ISP 图像处理器
色彩校正/降噪
输出数据流
YUYV / MJPEG
1.1.1a CMOS 与 CCD 传感器对比
摄像头传感器主要分为 CCD 和 CMOS 两种,当前嵌入式设备几乎全部使用 CMOS 传感器。
| 对比维度 | CCD(电荷耦合器件) | CMOS(互补金属氧化物半导体) |
|---|---|---|
| 工作原理 | 电荷逐行转移到输出端统一放大 | 每个像素独立放大,行列寻址读出 |
| 功耗 | 高(需要多路电压) | 低(单电压供电,省电 80%) |
| 速度 | 慢(串行读出) | 快(并行读出,支持高速) |
| 噪声 | 低(统一放大,一致性高) | 较高(各像素放大差异大) |
| 集成度 | 低(需要外部驱动电路) | 高(可集成 ISP、ADC 在同一芯片) |
| 成本 | 高(制造工艺复杂) | 低(标准半导体工艺) |
| 典型应用 | 高端工业相机、卫星遥感 | 手机摄像头、嵌入式设备、监控 |
| 市场现状 | 逐渐被 CMOS 取代 | 占比超过 95% |
一句话总结:CCD 画质好但贵且耗电,像专业单反;CMOS 成本低、集成度高、省电,像手机摄像头。嵌入式设备几乎清一色用 CMOS。
1.1.1b ISP(图像信号处理器)流水线
摄像头传感器输出的原始数据(Bayer RAW)不能直接使用,必须经过 ISP 处理才能变成我们看到的彩色图像。ISP 就像"照片的暗房"------把原始底片变成精美的照片。
#mermaid-svg-iTIkIrNPOkEIVrgq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iTIkIrNPOkEIVrgq .error-icon{fill:#552222;}#mermaid-svg-iTIkIrNPOkEIVrgq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iTIkIrNPOkEIVrgq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iTIkIrNPOkEIVrgq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iTIkIrNPOkEIVrgq .marker.cross{stroke:#333333;}#mermaid-svg-iTIkIrNPOkEIVrgq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iTIkIrNPOkEIVrgq p{margin:0;}#mermaid-svg-iTIkIrNPOkEIVrgq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster-label text{fill:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster-label span{color:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster-label span p{background-color:transparent;}#mermaid-svg-iTIkIrNPOkEIVrgq .label text,#mermaid-svg-iTIkIrNPOkEIVrgq span{fill:#333;color:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq .node rect,#mermaid-svg-iTIkIrNPOkEIVrgq .node circle,#mermaid-svg-iTIkIrNPOkEIVrgq .node ellipse,#mermaid-svg-iTIkIrNPOkEIVrgq .node polygon,#mermaid-svg-iTIkIrNPOkEIVrgq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iTIkIrNPOkEIVrgq .rough-node .label text,#mermaid-svg-iTIkIrNPOkEIVrgq .node .label text,#mermaid-svg-iTIkIrNPOkEIVrgq .image-shape .label,#mermaid-svg-iTIkIrNPOkEIVrgq .icon-shape .label{text-anchor:middle;}#mermaid-svg-iTIkIrNPOkEIVrgq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iTIkIrNPOkEIVrgq .rough-node .label,#mermaid-svg-iTIkIrNPOkEIVrgq .node .label,#mermaid-svg-iTIkIrNPOkEIVrgq .image-shape .label,#mermaid-svg-iTIkIrNPOkEIVrgq .icon-shape .label{text-align:center;}#mermaid-svg-iTIkIrNPOkEIVrgq .node.clickable{cursor:pointer;}#mermaid-svg-iTIkIrNPOkEIVrgq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iTIkIrNPOkEIVrgq .arrowheadPath{fill:#333333;}#mermaid-svg-iTIkIrNPOkEIVrgq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iTIkIrNPOkEIVrgq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iTIkIrNPOkEIVrgq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iTIkIrNPOkEIVrgq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iTIkIrNPOkEIVrgq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iTIkIrNPOkEIVrgq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster text{fill:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq .cluster span{color:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iTIkIrNPOkEIVrgq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iTIkIrNPOkEIVrgq rect.text{fill:none;stroke-width:0;}#mermaid-svg-iTIkIrNPOkEIVrgq .icon-shape,#mermaid-svg-iTIkIrNPOkEIVrgq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iTIkIrNPOkEIVrgq .icon-shape p,#mermaid-svg-iTIkIrNPOkEIVrgq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iTIkIrNPOkEIVrgq .icon-shape .label rect,#mermaid-svg-iTIkIrNPOkEIVrgq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iTIkIrNPOkEIVrgq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iTIkIrNPOkEIVrgq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iTIkIrNPOkEIVrgq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输出
ISP 处理流水线
传感器输出
Bayer RAW 数据
每个像素只有一种颜色
去马赛克
插值补全 R/G/B
白平衡校正
适应不同色温
色彩校正矩阵
修正传感器色偏
Gamma 校正
非线性亮度映射
降噪处理
去除传感器噪声
边缘增强
提升清晰度
YUV/RGB 输出
640×480 彩色
学术背景:Bayer 格式是 1976 年由 Kodak 的 Bryce Bayer 发明的,它让每个像素只采集 R/G/B 中的一种颜色(通常 GR/BG 交替排列),再通过插值算法还原全彩。这是单传感器数码相机的核心技术。
1.1.2 摄像头关键参数速查
| 参数 | 说明 | 类比 | 典型值 |
|---|---|---|---|
| 分辨率 | 像素网格的密度,越高越清晰 | 网格纸的格子数 | 640×480 / 1920×1080 |
| 帧率 FPS | 每秒采集的画面数,越高越流畅 | 翻页动画的速度 | 30fps / 60fps |
| 像素格式 | 每个像素如何编码 | 用哪种语言描述颜色 | YUYV / MJPEG / RGB3 |
| 传感器尺寸 | 感光面积大小,越大进光越多 | 水桶的口径 | 1/4" / 1/2.3" |
| 接口类型 | 与主板的连接方式 | 水管的口径 | USB / CSI / HDMI |
1.1.2a 分辨率标准速查
| 标准名称 | 分辨率 | 宽高比 | 总像素数 | 典型应用 |
|---|---|---|---|---|
| QQVGA | 160×120 | 4:3 | 19,200 | 低端监控、玩具 |
| QVGA | 320×240 | 4:3 | 76,800 | 老式手机拍照 |
| VGA | 640×480 | 4:3 | 307,200 | 本文示例、USB 摄像头 |
| SVGA | 800×600 | 4:3 | 480,000 | 老式显示器 |
| HD(720p) | 1280×720 | 16:9 | 921,600 | 入门高清视频 |
| FHD(1080p) | 1920×1080 | 16:9 | 2,073,600 | 主流高清视频 |
| QHD(2K) | 2560×1440 | 16:9 | 3,686,400 | 高端手机 |
| UHD(4K) | 3840×2160 | 16:9 | 8,294,400 | 超高清视频 |
注意:嵌入式摄像头最常用的分辨率是 VGA(640×480)和 HD(1280×720),因为它们在画质和带宽之间取得了最佳平衡。
1.1.3 参数之间的制约关系
#mermaid-svg-I3gZL2cOe53q8X9T{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-I3gZL2cOe53q8X9T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-I3gZL2cOe53q8X9T .error-icon{fill:#552222;}#mermaid-svg-I3gZL2cOe53q8X9T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-I3gZL2cOe53q8X9T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-I3gZL2cOe53q8X9T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-I3gZL2cOe53q8X9T .marker.cross{stroke:#333333;}#mermaid-svg-I3gZL2cOe53q8X9T svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-I3gZL2cOe53q8X9T p{margin:0;}#mermaid-svg-I3gZL2cOe53q8X9T .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-I3gZL2cOe53q8X9T .cluster-label text{fill:#333;}#mermaid-svg-I3gZL2cOe53q8X9T .cluster-label span{color:#333;}#mermaid-svg-I3gZL2cOe53q8X9T .cluster-label span p{background-color:transparent;}#mermaid-svg-I3gZL2cOe53q8X9T .label text,#mermaid-svg-I3gZL2cOe53q8X9T span{fill:#333;color:#333;}#mermaid-svg-I3gZL2cOe53q8X9T .node rect,#mermaid-svg-I3gZL2cOe53q8X9T .node circle,#mermaid-svg-I3gZL2cOe53q8X9T .node ellipse,#mermaid-svg-I3gZL2cOe53q8X9T .node polygon,#mermaid-svg-I3gZL2cOe53q8X9T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-I3gZL2cOe53q8X9T .rough-node .label text,#mermaid-svg-I3gZL2cOe53q8X9T .node .label text,#mermaid-svg-I3gZL2cOe53q8X9T .image-shape .label,#mermaid-svg-I3gZL2cOe53q8X9T .icon-shape .label{text-anchor:middle;}#mermaid-svg-I3gZL2cOe53q8X9T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-I3gZL2cOe53q8X9T .rough-node .label,#mermaid-svg-I3gZL2cOe53q8X9T .node .label,#mermaid-svg-I3gZL2cOe53q8X9T .image-shape .label,#mermaid-svg-I3gZL2cOe53q8X9T .icon-shape .label{text-align:center;}#mermaid-svg-I3gZL2cOe53q8X9T .node.clickable{cursor:pointer;}#mermaid-svg-I3gZL2cOe53q8X9T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-I3gZL2cOe53q8X9T .arrowheadPath{fill:#333333;}#mermaid-svg-I3gZL2cOe53q8X9T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-I3gZL2cOe53q8X9T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-I3gZL2cOe53q8X9T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I3gZL2cOe53q8X9T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-I3gZL2cOe53q8X9T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I3gZL2cOe53q8X9T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-I3gZL2cOe53q8X9T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-I3gZL2cOe53q8X9T .cluster text{fill:#333;}#mermaid-svg-I3gZL2cOe53q8X9T .cluster span{color:#333;}#mermaid-svg-I3gZL2cOe53q8X9T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-I3gZL2cOe53q8X9T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-I3gZL2cOe53q8X9T rect.text{fill:none;stroke-width:0;}#mermaid-svg-I3gZL2cOe53q8X9T .icon-shape,#mermaid-svg-I3gZL2cOe53q8X9T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I3gZL2cOe53q8X9T .icon-shape p,#mermaid-svg-I3gZL2cOe53q8X9T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-I3gZL2cOe53q8X9T .icon-shape .label rect,#mermaid-svg-I3gZL2cOe53q8X9T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I3gZL2cOe53q8X9T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-I3gZL2cOe53q8X9T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-I3gZL2cOe53q8X9T :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 硬件限制
计算结果
可调参数
分辨率
Width × Height
帧率 FPS
像素格式
决定每像素字节数
数据带宽
= 分辨率 × 帧率 × 每像素字节数
USB 带宽上限
约 480 Mbps
CPU 处理能力
一句话总结:分辨率越高画面越细腻,帧率越高动作越流畅,但两者都会让数据量暴增------嵌入式设备常常需要在画质和速度之间做取舍,就像手机拍照时选"高清"还是"流畅"模式。
1.1.3a 带宽计算示例
理论计算可以帮助我们在选型阶段就判断硬件是否够用,避免"买了才发现跑不动"的尴尬。
场景:640×480 @ 30fps,YUYV 格式
每像素字节数 = 2(YUYV 是 4:2:2,每像素 2 字节)
每帧数据量 = 640 × 480 × 2 = 614,400 字节 ≈ 600 KB
每秒数据量 = 600 KB × 30 = 18,000 KB/s ≈ 17.6 MB/s
USB 2.0 理论带宽 = 480 Mbps ≈ 60 MB/s
实际可用带宽 ≈ 60 × 60% = 36 MB/s(USB 协议开销约 40%)
结论:640×480 @ 30fps 仅占 USB 2.0 实际带宽的约 50%,完全可行。但如果升级到 1080p @ 60fps,带宽需求会飙升到 237 MB/s,远超 USB 2.0 的能力,必须用 USB 3.0 或 MIPI CSI。
| 场景 | 分辨率 | 帧率 | 格式 | 带宽需求 | USB 2.0 | USB 3.0 |
|---|---|---|---|---|---|---|
| 低端监控 | 320×240 | 15fps | YUYV | 2.2 MB/s | ✅ 轻松 | ✅ 轻松 |
| 本文示例 | 640×480 | 30fps | YUYV | 17.6 MB/s | ✅ 可行 | ✅ 轻松 |
| 高清视频 | 1280×720 | 30fps | MJPEG | ~10 MB/s* | ✅ 可行 | ✅ 轻松 |
| 全高清 | 1920×1080 | 30fps | YUYV | 118 MB/s | ❌ 超载 | ✅ 可行 |
| 超高清 | 3840×2160 | 30fps | YUYV | 474 MB/s | ❌ 超载 | ❌ 超载 |
*MJPEG 是压缩格式,实际带宽取决于画面复杂度,通常为原始 YUYV 的 1/5~1/10。
1.1.4 常见摄像头接口对比
| 接口类型 | 传输速度 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| USB | 480 Mbps (USB 2.0) | 电脑外接摄像头 | 即插即用,通用性强 | 线长受限 |
| CSI | 1 Gbps+ | 手机/开发板内置 | 高速低延迟,体积小 | 需要专用接口 |
| HDMI | 10 Gbps+ | 高清采集卡 | 远距离传输,画质无损 | 需要额外编码 |
类比:USB 就像快递三轮车------灵活方便但速度有限;CSI 像专用轨道车------速度快但只能走固定路线;HDMI 像高速公路------速度快距离远但收费高。
1.2 V4L2 驱动框架
一句话总结
V4L2(Video for Linux Two)是 Linux 内核自带的视频设备 API,它让开发者用同一套函数操作不同品牌的摄像头------就像用同一个遥控器控制不同牌子的电视。
类比:V4L2 框架就像"手机操作系统"------App 通过系统 API 调用摄像头,不用管底层是索尼传感器还是三星传感器。换摄像头就像换手机后盖,App 不用改。
1.2.1 V4L2 分层架构
#mermaid-svg-M2VV4DandgFfLhYJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M2VV4DandgFfLhYJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M2VV4DandgFfLhYJ .error-icon{fill:#552222;}#mermaid-svg-M2VV4DandgFfLhYJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M2VV4DandgFfLhYJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M2VV4DandgFfLhYJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M2VV4DandgFfLhYJ .marker.cross{stroke:#333333;}#mermaid-svg-M2VV4DandgFfLhYJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M2VV4DandgFfLhYJ p{margin:0;}#mermaid-svg-M2VV4DandgFfLhYJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-M2VV4DandgFfLhYJ .cluster-label text{fill:#333;}#mermaid-svg-M2VV4DandgFfLhYJ .cluster-label span{color:#333;}#mermaid-svg-M2VV4DandgFfLhYJ .cluster-label span p{background-color:transparent;}#mermaid-svg-M2VV4DandgFfLhYJ .label text,#mermaid-svg-M2VV4DandgFfLhYJ span{fill:#333;color:#333;}#mermaid-svg-M2VV4DandgFfLhYJ .node rect,#mermaid-svg-M2VV4DandgFfLhYJ .node circle,#mermaid-svg-M2VV4DandgFfLhYJ .node ellipse,#mermaid-svg-M2VV4DandgFfLhYJ .node polygon,#mermaid-svg-M2VV4DandgFfLhYJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M2VV4DandgFfLhYJ .rough-node .label text,#mermaid-svg-M2VV4DandgFfLhYJ .node .label text,#mermaid-svg-M2VV4DandgFfLhYJ .image-shape .label,#mermaid-svg-M2VV4DandgFfLhYJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-M2VV4DandgFfLhYJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M2VV4DandgFfLhYJ .rough-node .label,#mermaid-svg-M2VV4DandgFfLhYJ .node .label,#mermaid-svg-M2VV4DandgFfLhYJ .image-shape .label,#mermaid-svg-M2VV4DandgFfLhYJ .icon-shape .label{text-align:center;}#mermaid-svg-M2VV4DandgFfLhYJ .node.clickable{cursor:pointer;}#mermaid-svg-M2VV4DandgFfLhYJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M2VV4DandgFfLhYJ .arrowheadPath{fill:#333333;}#mermaid-svg-M2VV4DandgFfLhYJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M2VV4DandgFfLhYJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M2VV4DandgFfLhYJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M2VV4DandgFfLhYJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M2VV4DandgFfLhYJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M2VV4DandgFfLhYJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M2VV4DandgFfLhYJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M2VV4DandgFfLhYJ .cluster text{fill:#333;}#mermaid-svg-M2VV4DandgFfLhYJ .cluster span{color:#333;}#mermaid-svg-M2VV4DandgFfLhYJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M2VV4DandgFfLhYJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M2VV4DandgFfLhYJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-M2VV4DandgFfLhYJ .icon-shape,#mermaid-svg-M2VV4DandgFfLhYJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M2VV4DandgFfLhYJ .icon-shape p,#mermaid-svg-M2VV4DandgFfLhYJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M2VV4DandgFfLhYJ .icon-shape .label rect,#mermaid-svg-M2VV4DandgFfLhYJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M2VV4DandgFfLhYJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M2VV4DandgFfLhYJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M2VV4DandgFfLhYJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 硬件层
内核空间
用户空间
open / ioctl / mmap
驱动回调
USB 传输
I2C / MIPI
PCIe
应用程序
你的 C 代码
V4L2 核心框架
video_device / v4l2_device
摄像头驱动
如 uvcvideo
USB 摄像头
CSI 摄像头
HDMI 采集卡
1.2.1a UVC 协议:USB 摄像头如何即插即用
扩展知识 :UVC(USB Video Class)是 USB 标准化组织定义的视频设备协议标准。符合 UVC 标准的摄像头插入电脑后,操作系统自动加载通用驱动(Linux 下的
uvcvideo),无需安装厂商专用驱动。
UVC 协议的核心能力:
- 自动协商:主机查询摄像头支持的格式列表,选择最优配置
- 标准控制:亮度、对比度、饱和度等参数通过统一接口调节
- 等时传输:视频数据通过 USB 等时端点传输,保证带宽但不保证可靠性
- 帧同步:通过帧头(Frame Header)标记每帧的起始和结束
#mermaid-svg-JNQJbyNb4cYPDD9Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JNQJbyNb4cYPDD9Y .error-icon{fill:#552222;}#mermaid-svg-JNQJbyNb4cYPDD9Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JNQJbyNb4cYPDD9Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .marker.cross{stroke:#333333;}#mermaid-svg-JNQJbyNb4cYPDD9Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JNQJbyNb4cYPDD9Y p{margin:0;}#mermaid-svg-JNQJbyNb4cYPDD9Y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster-label text{fill:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster-label span{color:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster-label span p{background-color:transparent;}#mermaid-svg-JNQJbyNb4cYPDD9Y .label text,#mermaid-svg-JNQJbyNb4cYPDD9Y span{fill:#333;color:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .node rect,#mermaid-svg-JNQJbyNb4cYPDD9Y .node circle,#mermaid-svg-JNQJbyNb4cYPDD9Y .node ellipse,#mermaid-svg-JNQJbyNb4cYPDD9Y .node polygon,#mermaid-svg-JNQJbyNb4cYPDD9Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .rough-node .label text,#mermaid-svg-JNQJbyNb4cYPDD9Y .node .label text,#mermaid-svg-JNQJbyNb4cYPDD9Y .image-shape .label,#mermaid-svg-JNQJbyNb4cYPDD9Y .icon-shape .label{text-anchor:middle;}#mermaid-svg-JNQJbyNb4cYPDD9Y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .rough-node .label,#mermaid-svg-JNQJbyNb4cYPDD9Y .node .label,#mermaid-svg-JNQJbyNb4cYPDD9Y .image-shape .label,#mermaid-svg-JNQJbyNb4cYPDD9Y .icon-shape .label{text-align:center;}#mermaid-svg-JNQJbyNb4cYPDD9Y .node.clickable{cursor:pointer;}#mermaid-svg-JNQJbyNb4cYPDD9Y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .arrowheadPath{fill:#333333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JNQJbyNb4cYPDD9Y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JNQJbyNb4cYPDD9Y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JNQJbyNb4cYPDD9Y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster text{fill:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y .cluster span{color:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JNQJbyNb4cYPDD9Y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JNQJbyNb4cYPDD9Y rect.text{fill:none;stroke-width:0;}#mermaid-svg-JNQJbyNb4cYPDD9Y .icon-shape,#mermaid-svg-JNQJbyNb4cYPDD9Y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JNQJbyNb4cYPDD9Y .icon-shape p,#mermaid-svg-JNQJbyNb4cYPDD9Y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JNQJbyNb4cYPDD9Y .icon-shape .label rect,#mermaid-svg-JNQJbyNb4cYPDD9Y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JNQJbyNb4cYPDD9Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JNQJbyNb4cYPDD9Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JNQJbyNb4cYPDD9Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用层
驱动层
物理层
USB 物理连接
uvcvideo.ko
UVC 通用驱动
V4L2 核心框架
应用程序
通过 V4L2 API 访问
注意:非 UVC 摄像头(如某些工业相机、MIPI 摄像头)需要厂商提供专用 V4L2 驱动,不能即插即用。这也是为什么嵌入式开发板上用 CSI 摄像头时需要额外配置设备树和驱动。
| 操作 | 函数 | 类比 | 说明 |
|---|---|---|---|
| 打开设备 | open() |
打开水龙头阀门 | 获取文件描述符 fd |
| 查询能力 | ioctl(VIDIOC_QUERYCAP) |
看水龙头说明书 | 确认设备支持什么功能 |
| 枚举格式 | ioctl(VIDIOC_ENUM_FMT) |
看能接哪几种水管 | 列出支持的像素格式 |
| 设置格式 | ioctl(VIDIOC_S_FMT) |
选好水管尺寸接上去 | 设置宽高、像素格式 |
| 申请缓冲区 | ioctl(VIDIOC_REQBUFS) |
在出水口放桶 | 申请内核缓冲区 |
| 映射内存 | mmap() |
在桶上开观察窗 | 用户空间直接访问缓冲区 |
| 入队 | ioctl(VIDIOC_QBUF) |
把空桶放流水线 | 缓冲区交给驱动填数据 |
| 开流 | ioctl(VIDIOC_STREAMON) |
打开水龙头 | 启动视频采集 |
| 等待 | select() / poll() |
盯着桶看水满了没 | 等待数据就绪 |
| 取帧 | ioctl(VIDIOC_DQBUF) |
把满桶拿下来 | 取出已填满的缓冲区 |
1.2.3 V4L2 核心结构体关系
#mermaid-svg-zxBw6GTvNbEpb5tF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zxBw6GTvNbEpb5tF .error-icon{fill:#552222;}#mermaid-svg-zxBw6GTvNbEpb5tF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zxBw6GTvNbEpb5tF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zxBw6GTvNbEpb5tF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zxBw6GTvNbEpb5tF .marker.cross{stroke:#333333;}#mermaid-svg-zxBw6GTvNbEpb5tF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zxBw6GTvNbEpb5tF p{margin:0;}#mermaid-svg-zxBw6GTvNbEpb5tF g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-zxBw6GTvNbEpb5tF g.classGroup text .title{font-weight:bolder;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster-label text{fill:#333;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster-label span{color:#333;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster-label span p{background-color:transparent;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster text{fill:#333;}#mermaid-svg-zxBw6GTvNbEpb5tF .cluster span{color:#333;}#mermaid-svg-zxBw6GTvNbEpb5tF .nodeLabel,#mermaid-svg-zxBw6GTvNbEpb5tF .edgeLabel{color:#131300;}#mermaid-svg-zxBw6GTvNbEpb5tF .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-zxBw6GTvNbEpb5tF .label text{fill:#131300;}#mermaid-svg-zxBw6GTvNbEpb5tF .labelBkg{background:#ECECFF;}#mermaid-svg-zxBw6GTvNbEpb5tF .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-zxBw6GTvNbEpb5tF .classTitle{font-weight:bolder;}#mermaid-svg-zxBw6GTvNbEpb5tF .node rect,#mermaid-svg-zxBw6GTvNbEpb5tF .node circle,#mermaid-svg-zxBw6GTvNbEpb5tF .node ellipse,#mermaid-svg-zxBw6GTvNbEpb5tF .node polygon,#mermaid-svg-zxBw6GTvNbEpb5tF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zxBw6GTvNbEpb5tF .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF g.clickable{cursor:pointer;}#mermaid-svg-zxBw6GTvNbEpb5tF g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-zxBw6GTvNbEpb5tF g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-zxBw6GTvNbEpb5tF .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-zxBw6GTvNbEpb5tF .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-zxBw6GTvNbEpb5tF .dashed-line{stroke-dasharray:3;}#mermaid-svg-zxBw6GTvNbEpb5tF .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-zxBw6GTvNbEpb5tF #compositionStart,#mermaid-svg-zxBw6GTvNbEpb5tF .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #compositionEnd,#mermaid-svg-zxBw6GTvNbEpb5tF .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #dependencyStart,#mermaid-svg-zxBw6GTvNbEpb5tF .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #dependencyStart,#mermaid-svg-zxBw6GTvNbEpb5tF .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #extensionStart,#mermaid-svg-zxBw6GTvNbEpb5tF .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #extensionEnd,#mermaid-svg-zxBw6GTvNbEpb5tF .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #aggregationStart,#mermaid-svg-zxBw6GTvNbEpb5tF .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #aggregationEnd,#mermaid-svg-zxBw6GTvNbEpb5tF .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #lollipopStart,#mermaid-svg-zxBw6GTvNbEpb5tF .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF #lollipopEnd,#mermaid-svg-zxBw6GTvNbEpb5tF .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-zxBw6GTvNbEpb5tF .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-zxBw6GTvNbEpb5tF .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zxBw6GTvNbEpb5tF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zxBw6GTvNbEpb5tF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zxBw6GTvNbEpb5tF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} fmt.pix
产生
映射到用户空间
v4l2_capability
+char driver16
+char card32
+__u32 capabilities
+查询设备能力
v4l2_fmtdesc
+__u32 index
+__u32 type
+__u32 pixelformat
+char description32
+枚举支持的格式
v4l2_format
+__u32 type
+struct v4l2_pix_format pix
+设置/获取当前格式
v4l2_pix_format
+__u32 width
+__u32 height
+__u32 pixelformat
+__u32 bytesperline
+__u32 sizeimage
v4l2_requestbuffers
+__u32 count
+__u32 type
+__u32 memory
+申请内核缓冲区
v4l2_buffer
+__u32 index
+__u32 type
+__u32 memory
+__u32 length
+查询/入队/出队缓冲区
mmap
一句话总结 :V4L2 的每个 ioctl 操作都配一个结构体------QUERYCAP 用
v4l2_capability,S_FMT 用v4l2_format,REQBUFS 用v4l2_requestbuffers,QBUF/DQBUF 用v4l2_buffer------填表办事,各司其职。
1.2.4 ioctl 通信时序
摄像头硬件 V4L2 内核驱动 应用程序 摄像头硬件 V4L2 内核驱动 应用程序 #mermaid-svg-OciTVPoWFhXzxjvM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OciTVPoWFhXzxjvM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OciTVPoWFhXzxjvM .error-icon{fill:#552222;}#mermaid-svg-OciTVPoWFhXzxjvM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OciTVPoWFhXzxjvM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OciTVPoWFhXzxjvM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OciTVPoWFhXzxjvM .marker.cross{stroke:#333333;}#mermaid-svg-OciTVPoWFhXzxjvM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OciTVPoWFhXzxjvM p{margin:0;}#mermaid-svg-OciTVPoWFhXzxjvM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OciTVPoWFhXzxjvM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-OciTVPoWFhXzxjvM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OciTVPoWFhXzxjvM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-OciTVPoWFhXzxjvM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-OciTVPoWFhXzxjvM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-OciTVPoWFhXzxjvM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-OciTVPoWFhXzxjvM .sequenceNumber{fill:white;}#mermaid-svg-OciTVPoWFhXzxjvM #sequencenumber{fill:#333;}#mermaid-svg-OciTVPoWFhXzxjvM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-OciTVPoWFhXzxjvM .messageText{fill:#333;stroke:none;}#mermaid-svg-OciTVPoWFhXzxjvM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OciTVPoWFhXzxjvM .labelText,#mermaid-svg-OciTVPoWFhXzxjvM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-OciTVPoWFhXzxjvM .loopText,#mermaid-svg-OciTVPoWFhXzxjvM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-OciTVPoWFhXzxjvM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OciTVPoWFhXzxjvM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-OciTVPoWFhXzxjvM .noteText,#mermaid-svg-OciTVPoWFhXzxjvM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-OciTVPoWFhXzxjvM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OciTVPoWFhXzxjvM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OciTVPoWFhXzxjvM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OciTVPoWFhXzxjvM .actorPopupMenu{position:absolute;}#mermaid-svg-OciTVPoWFhXzxjvM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-OciTVPoWFhXzxjvM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OciTVPoWFhXzxjvM .actor-man circle,#mermaid-svg-OciTVPoWFhXzxjvM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-OciTVPoWFhXzxjvM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} open("/dev/video0") fd = 3 ioctl(VIDIOC_QUERYCAP) 查询设备能力 返回能力位图 cap = {driver, card, capabilities} ioctl(VIDIOC_ENUM_FMT, index=0) fmt = {YUYV, "YUYV 4:2:2"} ioctl(VIDIOC_ENUM_FMT, index=1) fmt = {MJPEG, "Motion-JPEG"} ioctl(VIDIOC_ENUM_FMT, index=2) 返回 -1 (没有更多格式) ioctl(VIDIOC_S_FMT, {640,480,YUYV}) 配置传感器 确认配置 format = {实际设置的值}
类比:ioctl 是 V4L2 的"万能遥控器按钮"------按不同的按钮(VIDIOC_xxx),传不同的参数(结构体),驱动就会执行对应的操作。
1.2.5 V4L2 与普通文件读写的对比
| 特性 | 普通文件读写 | V4L2 视频采集 |
|---|---|---|
| 打开方式 | open() + read() / write() |
open() + ioctl() 系列命令 |
| 数据流向 | 内核→用户(拷贝) | 内核→用户(mmap 零拷贝) |
| 缓冲机制 | 单缓冲区 | 多缓冲区环形队列 |
| 等待方式 | 阻塞式 read() |
select() / poll() 非阻塞等待 |
| 设备类型 | 文件系统 | 字符设备 /dev/video* |
| 配置灵活性 | 无 | 可配置分辨率、帧率、格式等 |
1.3 V4L2 拍照 10 步流程
一句话总结
用 V4L2 拍照就是一套"10 步流程":打开设备 → 查能力 → 查格式 → 设格式 → 申请缓冲区 → 映射内存 → 入队 → 开流 → 等待数据 → 取帧。
1.3.1 完整流程图
#mermaid-svg-yrQ8L25Vet1cVKtc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yrQ8L25Vet1cVKtc .error-icon{fill:#552222;}#mermaid-svg-yrQ8L25Vet1cVKtc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yrQ8L25Vet1cVKtc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yrQ8L25Vet1cVKtc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yrQ8L25Vet1cVKtc .marker.cross{stroke:#333333;}#mermaid-svg-yrQ8L25Vet1cVKtc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yrQ8L25Vet1cVKtc p{margin:0;}#mermaid-svg-yrQ8L25Vet1cVKtc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster-label text{fill:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster-label span{color:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster-label span p{background-color:transparent;}#mermaid-svg-yrQ8L25Vet1cVKtc .label text,#mermaid-svg-yrQ8L25Vet1cVKtc span{fill:#333;color:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc .node rect,#mermaid-svg-yrQ8L25Vet1cVKtc .node circle,#mermaid-svg-yrQ8L25Vet1cVKtc .node ellipse,#mermaid-svg-yrQ8L25Vet1cVKtc .node polygon,#mermaid-svg-yrQ8L25Vet1cVKtc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yrQ8L25Vet1cVKtc .rough-node .label text,#mermaid-svg-yrQ8L25Vet1cVKtc .node .label text,#mermaid-svg-yrQ8L25Vet1cVKtc .image-shape .label,#mermaid-svg-yrQ8L25Vet1cVKtc .icon-shape .label{text-anchor:middle;}#mermaid-svg-yrQ8L25Vet1cVKtc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yrQ8L25Vet1cVKtc .rough-node .label,#mermaid-svg-yrQ8L25Vet1cVKtc .node .label,#mermaid-svg-yrQ8L25Vet1cVKtc .image-shape .label,#mermaid-svg-yrQ8L25Vet1cVKtc .icon-shape .label{text-align:center;}#mermaid-svg-yrQ8L25Vet1cVKtc .node.clickable{cursor:pointer;}#mermaid-svg-yrQ8L25Vet1cVKtc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yrQ8L25Vet1cVKtc .arrowheadPath{fill:#333333;}#mermaid-svg-yrQ8L25Vet1cVKtc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yrQ8L25Vet1cVKtc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yrQ8L25Vet1cVKtc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yrQ8L25Vet1cVKtc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yrQ8L25Vet1cVKtc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yrQ8L25Vet1cVKtc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster text{fill:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc .cluster span{color:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yrQ8L25Vet1cVKtc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yrQ8L25Vet1cVKtc rect.text{fill:none;stroke-width:0;}#mermaid-svg-yrQ8L25Vet1cVKtc .icon-shape,#mermaid-svg-yrQ8L25Vet1cVKtc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yrQ8L25Vet1cVKtc .icon-shape p,#mermaid-svg-yrQ8L25Vet1cVKtc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yrQ8L25Vet1cVKtc .icon-shape .label rect,#mermaid-svg-yrQ8L25Vet1cVKtc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yrQ8L25Vet1cVKtc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yrQ8L25Vet1cVKtc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yrQ8L25Vet1cVKtc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} open 设备
VIDIOC_QUERYCAP 查能力
VIDIOC_ENUM_FMT 枚举格式
VIDIOC_S_FMT 设置格式
VIDIOC_REQBUFS 申请缓冲区
VIDIOC_QUERYBUF + mmap 映射
VIDIOC_QBUF 入队
VIDIOC_STREAMON 开流
select 等待数据
VIDIOC_DQBUF 取帧
处理数据 YUV→RGB→JPG
VIDIOC_QBUF 重新入队
1.3.2 mmap 零拷贝原理
类比:没有 mmap 就像你去快递站取包裹,先要把包裹从货架搬到柜台(内核→用户拷贝),再从柜台拿回家。有了 mmap 就像快递站直接在你家墙上开了一扇门,你伸手就能从货架上拿。
#mermaid-svg-7LlEXDTcedjWBIwa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7LlEXDTcedjWBIwa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7LlEXDTcedjWBIwa .error-icon{fill:#552222;}#mermaid-svg-7LlEXDTcedjWBIwa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7LlEXDTcedjWBIwa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7LlEXDTcedjWBIwa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7LlEXDTcedjWBIwa .marker.cross{stroke:#333333;}#mermaid-svg-7LlEXDTcedjWBIwa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7LlEXDTcedjWBIwa p{margin:0;}#mermaid-svg-7LlEXDTcedjWBIwa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7LlEXDTcedjWBIwa .cluster-label text{fill:#333;}#mermaid-svg-7LlEXDTcedjWBIwa .cluster-label span{color:#333;}#mermaid-svg-7LlEXDTcedjWBIwa .cluster-label span p{background-color:transparent;}#mermaid-svg-7LlEXDTcedjWBIwa .label text,#mermaid-svg-7LlEXDTcedjWBIwa span{fill:#333;color:#333;}#mermaid-svg-7LlEXDTcedjWBIwa .node rect,#mermaid-svg-7LlEXDTcedjWBIwa .node circle,#mermaid-svg-7LlEXDTcedjWBIwa .node ellipse,#mermaid-svg-7LlEXDTcedjWBIwa .node polygon,#mermaid-svg-7LlEXDTcedjWBIwa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7LlEXDTcedjWBIwa .rough-node .label text,#mermaid-svg-7LlEXDTcedjWBIwa .node .label text,#mermaid-svg-7LlEXDTcedjWBIwa .image-shape .label,#mermaid-svg-7LlEXDTcedjWBIwa .icon-shape .label{text-anchor:middle;}#mermaid-svg-7LlEXDTcedjWBIwa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7LlEXDTcedjWBIwa .rough-node .label,#mermaid-svg-7LlEXDTcedjWBIwa .node .label,#mermaid-svg-7LlEXDTcedjWBIwa .image-shape .label,#mermaid-svg-7LlEXDTcedjWBIwa .icon-shape .label{text-align:center;}#mermaid-svg-7LlEXDTcedjWBIwa .node.clickable{cursor:pointer;}#mermaid-svg-7LlEXDTcedjWBIwa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7LlEXDTcedjWBIwa .arrowheadPath{fill:#333333;}#mermaid-svg-7LlEXDTcedjWBIwa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7LlEXDTcedjWBIwa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7LlEXDTcedjWBIwa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7LlEXDTcedjWBIwa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7LlEXDTcedjWBIwa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7LlEXDTcedjWBIwa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7LlEXDTcedjWBIwa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7LlEXDTcedjWBIwa .cluster text{fill:#333;}#mermaid-svg-7LlEXDTcedjWBIwa .cluster span{color:#333;}#mermaid-svg-7LlEXDTcedjWBIwa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7LlEXDTcedjWBIwa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7LlEXDTcedjWBIwa rect.text{fill:none;stroke-width:0;}#mermaid-svg-7LlEXDTcedjWBIwa .icon-shape,#mermaid-svg-7LlEXDTcedjWBIwa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7LlEXDTcedjWBIwa .icon-shape p,#mermaid-svg-7LlEXDTcedjWBIwa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7LlEXDTcedjWBIwa .icon-shape .label rect,#mermaid-svg-7LlEXDTcedjWBIwa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7LlEXDTcedjWBIwa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7LlEXDTcedjWBIwa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7LlEXDTcedjWBIwa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有mmap方案
mmap 映射
零拷贝
内核缓冲区
用户空间直接访问
CPU 省下时间
处理数据
无mmap方案
read 数据拷贝
内核缓冲区
用户空间缓冲区
CPU 浪费时间
在拷贝上
1.3.3 有 mmap 与无 mmap 对比
| 对比维度 | 无 mmap(read/write) | 有 mmap |
|---|---|---|
| 数据拷贝次数 | 2 次(内核→用户缓冲区) | 0 次(直接映射) |
| CPU 占用 | 高,每次拷贝都占用 CPU | 低,省下的 CPU 可用于处理数据 |
| 内存占用 | 需要两份缓冲区 | 只需要一份,共享访问 |
| 适用场景 | 单帧采集,数据量小 | 视频流采集,数据量大 |
| 实现复杂度 | 简单,直接 read() | 稍微复杂,需要 mmap 配置 |
| 速度 | 慢 | 快 |
1.3.3a V4L2 三种内存映射方式对比
V4L2 支持三种缓冲区管理方式,mmap 只是其中一种:
| 方式 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| MMAP | 内核分配缓冲区,映射到用户空间 | 使用简单,兼容性好 | 缓冲区由内核管理,灵活性低 | 绝大多数场景 |
| USERPTR | 用户分配缓冲区,传给内核使用 | 用户可管理内存池 | 需要内存对齐,部分硬件不支持 | 需要自定义内存管理 |
| DMABUF | 通过 DMA 缓冲区共享 | 零拷贝跨设备共享 | 实现复杂,需要硬件支持 | GPU/VPU 协同处理 |
一句话总结:MMAP 是"内核帮你管内存",USERPTR 是"我自己管内存",DMABUF 是"我把内存直接给硬件用"。初学者用 MMAP 就够了,90% 的项目都只用到它。
1.3.4 缓冲区循环机制
4 个缓冲区就像流水线上的 4 个桶------驱动不断往空桶里装水,你不断拿走满桶,倒完水再放回流水线,循环往复,永不停止。
#mermaid-svg-2BDqLFttIi8Jd9dz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2BDqLFttIi8Jd9dz .error-icon{fill:#552222;}#mermaid-svg-2BDqLFttIi8Jd9dz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2BDqLFttIi8Jd9dz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2BDqLFttIi8Jd9dz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2BDqLFttIi8Jd9dz .marker.cross{stroke:#333333;}#mermaid-svg-2BDqLFttIi8Jd9dz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2BDqLFttIi8Jd9dz p{margin:0;}#mermaid-svg-2BDqLFttIi8Jd9dz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster-label text{fill:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster-label span{color:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster-label span p{background-color:transparent;}#mermaid-svg-2BDqLFttIi8Jd9dz .label text,#mermaid-svg-2BDqLFttIi8Jd9dz span{fill:#333;color:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz .node rect,#mermaid-svg-2BDqLFttIi8Jd9dz .node circle,#mermaid-svg-2BDqLFttIi8Jd9dz .node ellipse,#mermaid-svg-2BDqLFttIi8Jd9dz .node polygon,#mermaid-svg-2BDqLFttIi8Jd9dz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2BDqLFttIi8Jd9dz .rough-node .label text,#mermaid-svg-2BDqLFttIi8Jd9dz .node .label text,#mermaid-svg-2BDqLFttIi8Jd9dz .image-shape .label,#mermaid-svg-2BDqLFttIi8Jd9dz .icon-shape .label{text-anchor:middle;}#mermaid-svg-2BDqLFttIi8Jd9dz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2BDqLFttIi8Jd9dz .rough-node .label,#mermaid-svg-2BDqLFttIi8Jd9dz .node .label,#mermaid-svg-2BDqLFttIi8Jd9dz .image-shape .label,#mermaid-svg-2BDqLFttIi8Jd9dz .icon-shape .label{text-align:center;}#mermaid-svg-2BDqLFttIi8Jd9dz .node.clickable{cursor:pointer;}#mermaid-svg-2BDqLFttIi8Jd9dz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2BDqLFttIi8Jd9dz .arrowheadPath{fill:#333333;}#mermaid-svg-2BDqLFttIi8Jd9dz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2BDqLFttIi8Jd9dz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2BDqLFttIi8Jd9dz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2BDqLFttIi8Jd9dz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2BDqLFttIi8Jd9dz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2BDqLFttIi8Jd9dz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster text{fill:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz .cluster span{color:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2BDqLFttIi8Jd9dz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2BDqLFttIi8Jd9dz rect.text{fill:none;stroke-width:0;}#mermaid-svg-2BDqLFttIi8Jd9dz .icon-shape,#mermaid-svg-2BDqLFttIi8Jd9dz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2BDqLFttIi8Jd9dz .icon-shape p,#mermaid-svg-2BDqLFttIi8Jd9dz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2BDqLFttIi8Jd9dz .icon-shape .label rect,#mermaid-svg-2BDqLFttIi8Jd9dz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2BDqLFttIi8Jd9dz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2BDqLFttIi8Jd9dz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2BDqLFttIi8Jd9dz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用端
缓冲区池
驱动端
QBUF 入队
驱动填入数据
填充完成
DQBUF 出队
APP 读取
处理完 QBUF 重新入队
输入队列
空缓冲区等待填数据
摄像头驱动
采集一帧数据
缓冲区 0 空闲
缓冲区 1 已填满
缓冲区 2 已填满
缓冲区 3 空闲
应用程序
处理数据
输出队列
满缓冲区等待处理
1.3.5 select 等待机制
类比:select 就像你在餐厅等餐------你不用每分钟去问厨子"做好了没"(轮询),而是拿一个号码牌等着,叫到你再去取。select 就是那个"叫号系统"。
#mermaid-svg-h61q6rDARRWwc301{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h61q6rDARRWwc301 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h61q6rDARRWwc301 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h61q6rDARRWwc301 .error-icon{fill:#552222;}#mermaid-svg-h61q6rDARRWwc301 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h61q6rDARRWwc301 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h61q6rDARRWwc301 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h61q6rDARRWwc301 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h61q6rDARRWwc301 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h61q6rDARRWwc301 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h61q6rDARRWwc301 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h61q6rDARRWwc301 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h61q6rDARRWwc301 .marker.cross{stroke:#333333;}#mermaid-svg-h61q6rDARRWwc301 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h61q6rDARRWwc301 p{margin:0;}#mermaid-svg-h61q6rDARRWwc301 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-h61q6rDARRWwc301 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-h61q6rDARRWwc301 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-h61q6rDARRWwc301 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-h61q6rDARRWwc301 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-h61q6rDARRWwc301 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-h61q6rDARRWwc301 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-h61q6rDARRWwc301 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-h61q6rDARRWwc301 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-h61q6rDARRWwc301 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-h61q6rDARRWwc301 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-h61q6rDARRWwc301 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-h61q6rDARRWwc301 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-h61q6rDARRWwc301 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h61q6rDARRWwc301 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h61q6rDARRWwc301 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h61q6rDARRWwc301 .edgeLabel .label text{fill:#333;}#mermaid-svg-h61q6rDARRWwc301 .label div .edgeLabel{color:#333;}#mermaid-svg-h61q6rDARRWwc301 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-h61q6rDARRWwc301 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-h61q6rDARRWwc301 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-h61q6rDARRWwc301 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-h61q6rDARRWwc301 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-h61q6rDARRWwc301 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h61q6rDARRWwc301 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h61q6rDARRWwc301 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h61q6rDARRWwc301 .cluster-label,#mermaid-svg-h61q6rDARRWwc301 .nodeLabel{color:#131300;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-h61q6rDARRWwc301 .note-edge{stroke-dasharray:5;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-note text{fill:black;}#mermaid-svg-h61q6rDARRWwc301 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-h61q6rDARRWwc301 .statediagram .edgeLabel{color:red;}#mermaid-svg-h61q6rDARRWwc301 #dependencyStart,#mermaid-svg-h61q6rDARRWwc301 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-h61q6rDARRWwc301 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h61q6rDARRWwc301 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} select(fd)
摄像头数据到来
20秒无数据
设备异常
FD_ISSET 检查
YUV→RGB→JPG
重新 QBUF 入队
等待数据
数据就绪
超时返回
出错返回
读取帧
处理数据
处理帧
1.3.5a select 与 poll 对比
在实际嵌入式开发中,select() 和 poll() 都可以用来等待数据,两者各有优劣。
| 对比维度 | select() | poll() |
|---|---|---|
| 最大监视数 | FD_SETSIZE(通常 1024) | 无限制 |
| 数据结构 | fd_set 位图 |
struct pollfd 数组 |
| 可读性 | 宏操作(FD_SET/FD_ISSET)较繁琐 | 结构体成员直观 |
| 性能(大量 fd) | 每次遍历全部 fd,O(n) | 每次遍历全部 fd,O(n) |
| 超时精度 | 微秒(struct timeval) | 毫秒(int) |
| POSIX 标准 | ✅ 是 | ✅ 是 |
| 嵌入式适用性 | ✅ 简单场景,fd 少时推荐 | ✅ 大量 fd 或有特殊需求时推荐 |
一句话总结:监视 1 个摄像头 fd 时用 select 完全够用且代码更简单;如果需要同时监视几十个 fd(如多路摄像头),建议用 poll 以避免 1024 上限。
1.3.6 完整数据流时序
内核缓冲区 摄像头 V4L2 驱动 应用程序 内核缓冲区 摄像头 V4L2 驱动 应用程序 #mermaid-svg-T11hRcVSyAITMBBN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-T11hRcVSyAITMBBN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-T11hRcVSyAITMBBN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-T11hRcVSyAITMBBN .error-icon{fill:#552222;}#mermaid-svg-T11hRcVSyAITMBBN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-T11hRcVSyAITMBBN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-T11hRcVSyAITMBBN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-T11hRcVSyAITMBBN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-T11hRcVSyAITMBBN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-T11hRcVSyAITMBBN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-T11hRcVSyAITMBBN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-T11hRcVSyAITMBBN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-T11hRcVSyAITMBBN .marker.cross{stroke:#333333;}#mermaid-svg-T11hRcVSyAITMBBN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-T11hRcVSyAITMBBN p{margin:0;}#mermaid-svg-T11hRcVSyAITMBBN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T11hRcVSyAITMBBN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-T11hRcVSyAITMBBN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-T11hRcVSyAITMBBN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-T11hRcVSyAITMBBN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-T11hRcVSyAITMBBN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-T11hRcVSyAITMBBN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-T11hRcVSyAITMBBN .sequenceNumber{fill:white;}#mermaid-svg-T11hRcVSyAITMBBN #sequencenumber{fill:#333;}#mermaid-svg-T11hRcVSyAITMBBN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-T11hRcVSyAITMBBN .messageText{fill:#333;stroke:none;}#mermaid-svg-T11hRcVSyAITMBBN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T11hRcVSyAITMBBN .labelText,#mermaid-svg-T11hRcVSyAITMBBN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-T11hRcVSyAITMBBN .loopText,#mermaid-svg-T11hRcVSyAITMBBN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-T11hRcVSyAITMBBN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-T11hRcVSyAITMBBN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-T11hRcVSyAITMBBN .noteText,#mermaid-svg-T11hRcVSyAITMBBN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-T11hRcVSyAITMBBN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T11hRcVSyAITMBBN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T11hRcVSyAITMBBN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T11hRcVSyAITMBBN .actorPopupMenu{position:absolute;}#mermaid-svg-T11hRcVSyAITMBBN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-T11hRcVSyAITMBBN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T11hRcVSyAITMBBN .actor-man circle,#mermaid-svg-T11hRcVSyAITMBBN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-T11hRcVSyAITMBBN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 映射 4 个缓冲区 loop 入队 4 个缓冲区 YUYV → RGB → JPEG open(/dev/video0) fd VIDIOC_QUERYCAP 确认支持 CAPTURE VIDIOC_S_FMT (640x480 YUYV) 格式已设置 VIDIOC_REQBUFS (count=4) 分配 4 个缓冲区 VIDIOC_QUERYBUF (index=i) 缓冲区信息 mmap 映射到用户空间 VIDIOC_QBUF (index=i) VIDIOC_STREAMON 启动采集 填入第一帧数据 select 等待 数据就绪! VIDIOC_DQBUF 取出满缓冲区 VIDIOC_QBUF (重新入队)
第二章 图像格式与转换
2.1 YUV 格式详解
一句话总结
YUV 把颜色拆成"亮度(Y)"和"色度(UV)"两部分存储,利用人眼对亮度敏感、对色彩不敏感的特点,通过让多个像素共用 UV 分量来节省 50% 的存储空间。
类比:YUV 就像"黑白素描本 + 彩色画笔"------Y 是素描的轮廓线(亮度),UV 是用来上色的颜料(颜色)。即使你的彩色笔颜色很淡(UV 精度低),素描轮廓依然清晰。
2.1.0a 学术辨析:YUV 与 YCbCr 的区别
注意:严格来说,本文讨论的"YUV"实际上是 YCbCr。这是一个常见的学术混淆,值得专门澄清。
| 概念 | 全称 | 标准来源 | 数值范围 | 应用场景 |
|---|---|---|---|---|
| YUV | Y Luma + U/V Chrominance | 模拟电视(PAL/NTSC) | 连续模拟值 | 历史遗留,模拟视频 |
| YCbCr | Y Luma + Cb/Cr Chroma | 数字视频标准(ITU-R BT.601) | 量化后的数字值 | 数字摄像头、JPEG、H.264 |
两者关系 :YCbCr 是 YUV 的数字化版本。当我们在代码中操作 unsigned char Y, Cb, Cr 时,实际上是在操作 YCbCr。但业界(包括摄像头厂商、V4L2 文档)约定俗成地统称为"YUV",本文也沿用这个惯例。
一句话总结:YUV 是"模拟时代的名字",YCbCr 是"数字时代的真身"------就像你身份证上的名字和你的网名,大家叫惯了也就不改了。
2.1.1 为什么不用 RGB?
RGB 存储的 3 个痛点:
- 数据量大:每个像素 3 字节,一张 640×480 的图 ≈ 900KB,不利于传输和存储
- 兼容性差:几乎所有摄像头原生输出 YUV,而不是 RGB
- 不支持亮度调节:RGB 在光线昏暗时难以区分颜色
类比:RGB 存储每个像素的 R/G/B 三个值,就像给每个学生单独发一份教材;YUV 则是让相邻 4 个学生共用一份教材------因为人眼对颜色细节不敏感,看不太出来区别,但存储空间省了一半。
2.1.2 RGB 与 YUV 对比
| 对比维度 | RGB | YUV |
|---|---|---|
| 存储方式 | 每个像素 R/G/B 各 1 字节 | Y 亮度 + UV 色度分离 |
| 每像素字节数 | 3 字节(固定) | 1.5~3 字节(可调) |
| 人眼适配 | 均匀分配,不区分敏感度 | 亮度高精度 + 色度低精度 |
| 亮度调节 | 需要同时调整 R/G/B 三个分量 | 只需调整 Y 分量 |
| 摄像头输出 | 少数摄像头支持 | 几乎所有摄像头原生输出 |
| 兼容性 | 显示器直接使用 | 需要转换后才能显示 |
| 典型应用 | 图像处理、显示 | 视频传输、摄像头采集 |
2.1.3 YUV 三平面可视化
#mermaid-svg-RUbESFVRgYCoiJef{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RUbESFVRgYCoiJef .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RUbESFVRgYCoiJef .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RUbESFVRgYCoiJef .error-icon{fill:#552222;}#mermaid-svg-RUbESFVRgYCoiJef .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RUbESFVRgYCoiJef .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RUbESFVRgYCoiJef .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RUbESFVRgYCoiJef .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RUbESFVRgYCoiJef .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RUbESFVRgYCoiJef .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RUbESFVRgYCoiJef .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RUbESFVRgYCoiJef .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RUbESFVRgYCoiJef .marker.cross{stroke:#333333;}#mermaid-svg-RUbESFVRgYCoiJef svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RUbESFVRgYCoiJef p{margin:0;}#mermaid-svg-RUbESFVRgYCoiJef .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RUbESFVRgYCoiJef .cluster-label text{fill:#333;}#mermaid-svg-RUbESFVRgYCoiJef .cluster-label span{color:#333;}#mermaid-svg-RUbESFVRgYCoiJef .cluster-label span p{background-color:transparent;}#mermaid-svg-RUbESFVRgYCoiJef .label text,#mermaid-svg-RUbESFVRgYCoiJef span{fill:#333;color:#333;}#mermaid-svg-RUbESFVRgYCoiJef .node rect,#mermaid-svg-RUbESFVRgYCoiJef .node circle,#mermaid-svg-RUbESFVRgYCoiJef .node ellipse,#mermaid-svg-RUbESFVRgYCoiJef .node polygon,#mermaid-svg-RUbESFVRgYCoiJef .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RUbESFVRgYCoiJef .rough-node .label text,#mermaid-svg-RUbESFVRgYCoiJef .node .label text,#mermaid-svg-RUbESFVRgYCoiJef .image-shape .label,#mermaid-svg-RUbESFVRgYCoiJef .icon-shape .label{text-anchor:middle;}#mermaid-svg-RUbESFVRgYCoiJef .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RUbESFVRgYCoiJef .rough-node .label,#mermaid-svg-RUbESFVRgYCoiJef .node .label,#mermaid-svg-RUbESFVRgYCoiJef .image-shape .label,#mermaid-svg-RUbESFVRgYCoiJef .icon-shape .label{text-align:center;}#mermaid-svg-RUbESFVRgYCoiJef .node.clickable{cursor:pointer;}#mermaid-svg-RUbESFVRgYCoiJef .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RUbESFVRgYCoiJef .arrowheadPath{fill:#333333;}#mermaid-svg-RUbESFVRgYCoiJef .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RUbESFVRgYCoiJef .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RUbESFVRgYCoiJef .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RUbESFVRgYCoiJef .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RUbESFVRgYCoiJef .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RUbESFVRgYCoiJef .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RUbESFVRgYCoiJef .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RUbESFVRgYCoiJef .cluster text{fill:#333;}#mermaid-svg-RUbESFVRgYCoiJef .cluster span{color:#333;}#mermaid-svg-RUbESFVRgYCoiJef div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RUbESFVRgYCoiJef .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RUbESFVRgYCoiJef rect.text{fill:none;stroke-width:0;}#mermaid-svg-RUbESFVRgYCoiJef .icon-shape,#mermaid-svg-RUbESFVRgYCoiJef .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RUbESFVRgYCoiJef .icon-shape p,#mermaid-svg-RUbESFVRgYCoiJef .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RUbESFVRgYCoiJef .icon-shape .label rect,#mermaid-svg-RUbESFVRgYCoiJef .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RUbESFVRgYCoiJef .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RUbESFVRgYCoiJef .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RUbESFVRgYCoiJef :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 重建
拆分为三个平面
原始彩色图像
彩色图片
640×480
Y 平面
亮度图
就像黑白照片
640×480 每个像素
都有 Y 值
U 平面
蓝色色度
模糊的蓝色色调图
4:2:0 下仅
320×240 个 U 值
V 平面
红色色度
模糊的红色色调图
4:2:0 下仅
320×240 个 V 值
重新合成
Y + U + V = 彩色
一句话总结:YUV 把一张彩色图拆成"黑白轮廓图(Y)"+"蓝色色调图(U)"+"红色色调图(V)"三张图,合成回去就是彩色。UV 分辨率可以比 Y 低,因为人眼对彩色细节不敏感。
2.1.4 三种采样方式对比
| 采样方式 | 每 4 像素所需数据 | 节省比例 | 画质 | 典型应用 |
|---|---|---|---|---|
| YUV 4:4:4 | 12 字节 | 0%(同 RGB) | 无损 | 专业图像处理 |
| YUV 4:2:2 | 8 字节 | 33% | 高 | 广播级视频 |
| YUV 4:2:0 | 6 字节 | 50% | 良好 | 视频通话、流媒体 |
注意:4:2:0 是最常用的格式,视频通话和流媒体几乎都用它------当像素足够密集时,人眼几乎看不出 4:2:0 和 4:4:4 的区别,但文件大小差了一倍。
类比:4:4:4 就像 4 个人各吃一份套餐(每份都带饮料 UV);4:2:2 就像两个人共用一杯饮料;4:2:0 就像四个人共用一杯饮料------省钱了,但大家喝同一杯。
2.1.5 三种采样方式可视化
#mermaid-svg-XNEn2R9tSzbYhUo7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XNEn2R9tSzbYhUo7 .error-icon{fill:#552222;}#mermaid-svg-XNEn2R9tSzbYhUo7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XNEn2R9tSzbYhUo7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .marker.cross{stroke:#333333;}#mermaid-svg-XNEn2R9tSzbYhUo7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XNEn2R9tSzbYhUo7 p{margin:0;}#mermaid-svg-XNEn2R9tSzbYhUo7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster-label text{fill:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster-label span{color:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster-label span p{background-color:transparent;}#mermaid-svg-XNEn2R9tSzbYhUo7 .label text,#mermaid-svg-XNEn2R9tSzbYhUo7 span{fill:#333;color:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .node rect,#mermaid-svg-XNEn2R9tSzbYhUo7 .node circle,#mermaid-svg-XNEn2R9tSzbYhUo7 .node ellipse,#mermaid-svg-XNEn2R9tSzbYhUo7 .node polygon,#mermaid-svg-XNEn2R9tSzbYhUo7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .rough-node .label text,#mermaid-svg-XNEn2R9tSzbYhUo7 .node .label text,#mermaid-svg-XNEn2R9tSzbYhUo7 .image-shape .label,#mermaid-svg-XNEn2R9tSzbYhUo7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XNEn2R9tSzbYhUo7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .rough-node .label,#mermaid-svg-XNEn2R9tSzbYhUo7 .node .label,#mermaid-svg-XNEn2R9tSzbYhUo7 .image-shape .label,#mermaid-svg-XNEn2R9tSzbYhUo7 .icon-shape .label{text-align:center;}#mermaid-svg-XNEn2R9tSzbYhUo7 .node.clickable{cursor:pointer;}#mermaid-svg-XNEn2R9tSzbYhUo7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .arrowheadPath{fill:#333333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XNEn2R9tSzbYhUo7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XNEn2R9tSzbYhUo7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XNEn2R9tSzbYhUo7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster text{fill:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 .cluster span{color:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XNEn2R9tSzbYhUo7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XNEn2R9tSzbYhUo7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XNEn2R9tSzbYhUo7 .icon-shape,#mermaid-svg-XNEn2R9tSzbYhUo7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XNEn2R9tSzbYhUo7 .icon-shape p,#mermaid-svg-XNEn2R9tSzbYhUo7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XNEn2R9tSzbYhUo7 .icon-shape .label rect,#mermaid-svg-XNEn2R9tSzbYhUo7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XNEn2R9tSzbYhUo7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XNEn2R9tSzbYhUo7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XNEn2R9tSzbYhUo7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} YUV 4:2:0 --- 6 字节 --- 省 50%
像素0
Y 共用UV
像素1
Y 共用UV
像素2
Y 共用UV
像素3
Y 共用UV
YUV 4:2:2 --- 8 字节 --- 省 33%
像素0
Y U V
像素1
Y 共用UV
像素2
Y U V
像素3
Y 共用UV
YUV 4:4:4 --- 12 字节 --- 无损
像素0
Y U V
像素1
Y U V
像素2
Y U V
像素3
Y U V
2.1.6 数据量对比
#mermaid-svg-QUbXaDd7PNY54kgp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QUbXaDd7PNY54kgp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QUbXaDd7PNY54kgp .error-icon{fill:#552222;}#mermaid-svg-QUbXaDd7PNY54kgp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QUbXaDd7PNY54kgp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QUbXaDd7PNY54kgp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QUbXaDd7PNY54kgp .marker.cross{stroke:#333333;}#mermaid-svg-QUbXaDd7PNY54kgp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QUbXaDd7PNY54kgp p{margin:0;}#mermaid-svg-QUbXaDd7PNY54kgp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QUbXaDd7PNY54kgp .cluster-label text{fill:#333;}#mermaid-svg-QUbXaDd7PNY54kgp .cluster-label span{color:#333;}#mermaid-svg-QUbXaDd7PNY54kgp .cluster-label span p{background-color:transparent;}#mermaid-svg-QUbXaDd7PNY54kgp .label text,#mermaid-svg-QUbXaDd7PNY54kgp span{fill:#333;color:#333;}#mermaid-svg-QUbXaDd7PNY54kgp .node rect,#mermaid-svg-QUbXaDd7PNY54kgp .node circle,#mermaid-svg-QUbXaDd7PNY54kgp .node ellipse,#mermaid-svg-QUbXaDd7PNY54kgp .node polygon,#mermaid-svg-QUbXaDd7PNY54kgp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QUbXaDd7PNY54kgp .rough-node .label text,#mermaid-svg-QUbXaDd7PNY54kgp .node .label text,#mermaid-svg-QUbXaDd7PNY54kgp .image-shape .label,#mermaid-svg-QUbXaDd7PNY54kgp .icon-shape .label{text-anchor:middle;}#mermaid-svg-QUbXaDd7PNY54kgp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QUbXaDd7PNY54kgp .rough-node .label,#mermaid-svg-QUbXaDd7PNY54kgp .node .label,#mermaid-svg-QUbXaDd7PNY54kgp .image-shape .label,#mermaid-svg-QUbXaDd7PNY54kgp .icon-shape .label{text-align:center;}#mermaid-svg-QUbXaDd7PNY54kgp .node.clickable{cursor:pointer;}#mermaid-svg-QUbXaDd7PNY54kgp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QUbXaDd7PNY54kgp .arrowheadPath{fill:#333333;}#mermaid-svg-QUbXaDd7PNY54kgp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QUbXaDd7PNY54kgp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QUbXaDd7PNY54kgp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QUbXaDd7PNY54kgp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QUbXaDd7PNY54kgp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QUbXaDd7PNY54kgp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QUbXaDd7PNY54kgp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QUbXaDd7PNY54kgp .cluster text{fill:#333;}#mermaid-svg-QUbXaDd7PNY54kgp .cluster span{color:#333;}#mermaid-svg-QUbXaDd7PNY54kgp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QUbXaDd7PNY54kgp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QUbXaDd7PNY54kgp rect.text{fill:none;stroke-width:0;}#mermaid-svg-QUbXaDd7PNY54kgp .icon-shape,#mermaid-svg-QUbXaDd7PNY54kgp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QUbXaDd7PNY54kgp .icon-shape p,#mermaid-svg-QUbXaDd7PNY54kgp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QUbXaDd7PNY54kgp .icon-shape .label rect,#mermaid-svg-QUbXaDd7PNY54kgp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QUbXaDd7PNY54kgp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QUbXaDd7PNY54kgp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QUbXaDd7PNY54kgp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 640x480 一帧大小
每像素字节数
RGB
3.0 B/px
YUV 4:4:4
3.0 B/px
YUV 4:2:2
2.0 B/px
YUV 4:2:0
1.5 B/px
RGB
921 KB
YUV 4:4:4
921 KB
YUV 4:2:2
614 KB
YUV 4:2:0
460 KB
2.1.6a 色彩深度:8-bit vs 10-bit vs 12-bit
色彩深度决定了每个颜色分量用多少位(bit)来表示,直接影响画质的细腻程度。
| 色彩深度 | 每像素位数 | 每通道灰度级 | 总颜色数 | 文件大小 | 典型应用 |
|---|---|---|---|---|---|
| 8-bit | 24 | 0~255 | 1,670 万 | 基准 | 嵌入式摄像头、JPEG |
| 10-bit | 30 | 0~1023 | 10.7 亿 | +25% | 广播级视频、H.265 |
| 12-bit | 36 | 0~4095 | 687 亿 | +50% | RAW 摄影、专业影视 |
注意:本文讨论的摄像头输出均为 8-bit,这是嵌入式设备最主流的选择。10-bit 以上需要更宽的带宽和更强的处理能力,通常只在高端应用中见到。
2.1.7 两种存储方式对比
| 存储方式 | 排列方式 | 内存布局 | 优点 | 缺点 | 典型格式 |
|---|---|---|---|---|---|
| Planar(平面) | 所有 Y 连续→所有 U 连续→所有 V 连续 | YYYYYYYY UUUU VVVV |
适合压缩算法 | 需要多通道访问 | I420、NV12 |
| Packed(打包) | 每个像素的 YUV 连续存放 | YUYV YUYV YUYV |
访问简单,缓存友好 | 压缩效率略低 | YUYV、UYVY |
类比:Planar 就像把全班同学按性别排成三队------所有男生一队(Y)、所有女生一队(U)、所有老师一队(V)。Packed 就像一男一女一老师交替排队走。
2.1.8 Planar vs Packed 可视化
#mermaid-svg-Bv0S7anrMcwWOndl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bv0S7anrMcwWOndl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bv0S7anrMcwWOndl .error-icon{fill:#552222;}#mermaid-svg-Bv0S7anrMcwWOndl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bv0S7anrMcwWOndl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bv0S7anrMcwWOndl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bv0S7anrMcwWOndl .marker.cross{stroke:#333333;}#mermaid-svg-Bv0S7anrMcwWOndl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bv0S7anrMcwWOndl p{margin:0;}#mermaid-svg-Bv0S7anrMcwWOndl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bv0S7anrMcwWOndl .cluster-label text{fill:#333;}#mermaid-svg-Bv0S7anrMcwWOndl .cluster-label span{color:#333;}#mermaid-svg-Bv0S7anrMcwWOndl .cluster-label span p{background-color:transparent;}#mermaid-svg-Bv0S7anrMcwWOndl .label text,#mermaid-svg-Bv0S7anrMcwWOndl span{fill:#333;color:#333;}#mermaid-svg-Bv0S7anrMcwWOndl .node rect,#mermaid-svg-Bv0S7anrMcwWOndl .node circle,#mermaid-svg-Bv0S7anrMcwWOndl .node ellipse,#mermaid-svg-Bv0S7anrMcwWOndl .node polygon,#mermaid-svg-Bv0S7anrMcwWOndl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bv0S7anrMcwWOndl .rough-node .label text,#mermaid-svg-Bv0S7anrMcwWOndl .node .label text,#mermaid-svg-Bv0S7anrMcwWOndl .image-shape .label,#mermaid-svg-Bv0S7anrMcwWOndl .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bv0S7anrMcwWOndl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bv0S7anrMcwWOndl .rough-node .label,#mermaid-svg-Bv0S7anrMcwWOndl .node .label,#mermaid-svg-Bv0S7anrMcwWOndl .image-shape .label,#mermaid-svg-Bv0S7anrMcwWOndl .icon-shape .label{text-align:center;}#mermaid-svg-Bv0S7anrMcwWOndl .node.clickable{cursor:pointer;}#mermaid-svg-Bv0S7anrMcwWOndl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bv0S7anrMcwWOndl .arrowheadPath{fill:#333333;}#mermaid-svg-Bv0S7anrMcwWOndl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bv0S7anrMcwWOndl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bv0S7anrMcwWOndl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bv0S7anrMcwWOndl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bv0S7anrMcwWOndl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bv0S7anrMcwWOndl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bv0S7anrMcwWOndl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bv0S7anrMcwWOndl .cluster text{fill:#333;}#mermaid-svg-Bv0S7anrMcwWOndl .cluster span{color:#333;}#mermaid-svg-Bv0S7anrMcwWOndl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bv0S7anrMcwWOndl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bv0S7anrMcwWOndl rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bv0S7anrMcwWOndl .icon-shape,#mermaid-svg-Bv0S7anrMcwWOndl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bv0S7anrMcwWOndl .icon-shape p,#mermaid-svg-Bv0S7anrMcwWOndl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bv0S7anrMcwWOndl .icon-shape .label rect,#mermaid-svg-Bv0S7anrMcwWOndl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bv0S7anrMcwWOndl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bv0S7anrMcwWOndl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bv0S7anrMcwWOndl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Packed 存储
Packed 打包存储
Y0 U0 Y1 V0 | Y2 U1 Y3 V1 | Y4 U2 Y5 V2 | Y6 U3 Y7 V3
每个像素的 YUV 挨着放
Planar 存储
Planar 平面存储
Y 区
Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
所有 Y 连续排在一起
U 区
U0 U1 U2 U3
所有 U 连续排在一起
V 区
V0 V1 V2 V3
所有 V 连续排在一起
2.1.8a 补充:NV12 / NV21 格式(Android 标准格式)
除了 YUYV(Packed 4:2:2)之外,嵌入式领域还有一种非常常见的格式------NV12,它是 Android 系统 Camera2 API 的默认输出格式,也是视频编码器(如 H.264/H.265)最常用的输入格式。
NV12 的存储方式(Planar 变体------半平面 Semi-Planar):
-
Y 平面:所有 Y 值连续存储(640×480 = 307,200 字节)
-
UV 平面:U 和 V 交错存储,U 在前 V 在后(320×240 × 2 = 153,600 字节)
内存布局:YYYYYYYYYYYY...(Y 平面,连续)
UVUVUVUVUV...(UV 平面,交错)
NV21 与 NV12 的区别仅在于 UV 平面的顺序------NV21 是 V 在前 U 在后(VUVUVU...),这是 Android 早期版本的标准。
| 格式 | 类型 | UV 排列 | 常见于 |
|---|---|---|---|
| NV12 | Semi-Planar | U 先 V 后 | Android Camera2、视频编码器 |
| NV21 | Semi-Planar | V 先 U 后 | Android 旧版 Camera1 |
| I420 | Full Planar | Y 平面 + U 平面 + V 平面 | 视频会议、FFmpeg |
类比:NV12 就像 Planar 的"变形版"------Y 仍然是一整队男生,但 U 和 V 不再分两队,而是男女搭配站成一队。
2.1.9 YUYV(4:2:2 Packed)字节级布局
这是实际摄像头最常输出的格式------每 4 个字节描述 2 个像素。字节索引 0=Y0, 1=U, 2=Y1, 3=V。两个像素各取各的 Y,但共享 U 和 V。
#mermaid-svg-XSWxwGnJoWUNnlQI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XSWxwGnJoWUNnlQI .error-icon{fill:#552222;}#mermaid-svg-XSWxwGnJoWUNnlQI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XSWxwGnJoWUNnlQI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XSWxwGnJoWUNnlQI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XSWxwGnJoWUNnlQI .marker.cross{stroke:#333333;}#mermaid-svg-XSWxwGnJoWUNnlQI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XSWxwGnJoWUNnlQI p{margin:0;}#mermaid-svg-XSWxwGnJoWUNnlQI .mermaid-main-font{font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XSWxwGnJoWUNnlQI .exclude-range{fill:#eeeeee;}#mermaid-svg-XSWxwGnJoWUNnlQI .section{stroke:none;opacity:0.2;}#mermaid-svg-XSWxwGnJoWUNnlQI .section0{fill:rgba(102, 102, 255, 0.49);}#mermaid-svg-XSWxwGnJoWUNnlQI .section2{fill:#fff400;}#mermaid-svg-XSWxwGnJoWUNnlQI .section1,#mermaid-svg-XSWxwGnJoWUNnlQI .section3{fill:white;opacity:0.2;}#mermaid-svg-XSWxwGnJoWUNnlQI .sectionTitle0{fill:#333;}#mermaid-svg-XSWxwGnJoWUNnlQI .sectionTitle1{fill:#333;}#mermaid-svg-XSWxwGnJoWUNnlQI .sectionTitle2{fill:#333;}#mermaid-svg-XSWxwGnJoWUNnlQI .sectionTitle3{fill:#333;}#mermaid-svg-XSWxwGnJoWUNnlQI .sectionTitle{text-anchor:start;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XSWxwGnJoWUNnlQI .grid .tick{stroke:lightgrey;opacity:0.8;shape-rendering:crispEdges;}#mermaid-svg-XSWxwGnJoWUNnlQI .grid .tick text{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#333;}#mermaid-svg-XSWxwGnJoWUNnlQI .grid path{stroke-width:0;}#mermaid-svg-XSWxwGnJoWUNnlQI .today{fill:none;stroke:red;stroke-width:2px;}#mermaid-svg-XSWxwGnJoWUNnlQI .task{stroke-width:2;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskText{text-anchor:middle;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutsideRight{fill:black;text-anchor:start;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutsideLeft{fill:black;text-anchor:end;}#mermaid-svg-XSWxwGnJoWUNnlQI .task.clickable{cursor:pointer;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskText.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskText0,#mermaid-svg-XSWxwGnJoWUNnlQI .taskText1,#mermaid-svg-XSWxwGnJoWUNnlQI .taskText2,#mermaid-svg-XSWxwGnJoWUNnlQI .taskText3{fill:white;}#mermaid-svg-XSWxwGnJoWUNnlQI .task0,#mermaid-svg-XSWxwGnJoWUNnlQI .task1,#mermaid-svg-XSWxwGnJoWUNnlQI .task2,#mermaid-svg-XSWxwGnJoWUNnlQI .task3{fill:#8a90dd;stroke:#534fbc;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutside0,#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutside2{fill:black;}#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutside1,#mermaid-svg-XSWxwGnJoWUNnlQI .taskTextOutside3{fill:black;}#mermaid-svg-XSWxwGnJoWUNnlQI .active0,#mermaid-svg-XSWxwGnJoWUNnlQI .active1,#mermaid-svg-XSWxwGnJoWUNnlQI .active2,#mermaid-svg-XSWxwGnJoWUNnlQI .active3{fill:#bfc7ff;stroke:#534fbc;}#mermaid-svg-XSWxwGnJoWUNnlQI .activeText0,#mermaid-svg-XSWxwGnJoWUNnlQI .activeText1,#mermaid-svg-XSWxwGnJoWUNnlQI .activeText2,#mermaid-svg-XSWxwGnJoWUNnlQI .activeText3{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .done0,#mermaid-svg-XSWxwGnJoWUNnlQI .done1,#mermaid-svg-XSWxwGnJoWUNnlQI .done2,#mermaid-svg-XSWxwGnJoWUNnlQI .done3{stroke:grey;fill:lightgrey;stroke-width:2;}#mermaid-svg-XSWxwGnJoWUNnlQI .doneText0,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText1,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText2,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText3{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .doneText0.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText0.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText1.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText1.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText2.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText2.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText3.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .crit0,#mermaid-svg-XSWxwGnJoWUNnlQI .crit1,#mermaid-svg-XSWxwGnJoWUNnlQI .crit2,#mermaid-svg-XSWxwGnJoWUNnlQI .crit3{stroke:#ff8888;fill:red;stroke-width:2;}#mermaid-svg-XSWxwGnJoWUNnlQI .activeCrit0,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCrit1,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCrit2,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCrit3{stroke:#ff8888;fill:#bfc7ff;stroke-width:2;}#mermaid-svg-XSWxwGnJoWUNnlQI .doneCrit0,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCrit1,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCrit2,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCrit3{stroke:#ff8888;fill:lightgrey;stroke-width:2;cursor:pointer;shape-rendering:crispEdges;}#mermaid-svg-XSWxwGnJoWUNnlQI .milestone{transform:rotate(45deg) scale(0.8,0.8);}#mermaid-svg-XSWxwGnJoWUNnlQI .milestoneText{font-style:italic;}#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText0,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText1,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText2,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText3{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText0.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText0.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText1.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText1.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText2.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText2.taskTextOutsideRight,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText3.taskTextOutsideLeft,#mermaid-svg-XSWxwGnJoWUNnlQI .doneCritText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .vert{stroke:navy;}#mermaid-svg-XSWxwGnJoWUNnlQI .vertText{font-size:15px;text-anchor:middle;fill:navy!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .activeCritText0,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCritText1,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCritText2,#mermaid-svg-XSWxwGnJoWUNnlQI .activeCritText3{fill:black!important;}#mermaid-svg-XSWxwGnJoWUNnlQI .titleText{text-anchor:middle;font-size:18px;fill:#333;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XSWxwGnJoWUNnlQI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0 0 0 0 0 0 0 0 0 0 1 像素0-Y 像素0 像素0-U 像素0 像素1 像素1-Y 像素1 像素0-V 像素0 像素1 字节偏移 像素取值 YUYV 内存布局 --- 每 4 字节 = 2 像素
2.2 YUV 数据转 RGB
一句话总结
YUV 转 RGB 不是随便乘一乘就能算出来的,它有一套国际标准公式(BT.601/BT.709/BT.2020),对应不同清晰度的电视标准。
2.2.1 三大转换标准对比
| 标准 | 发布年份 | 适用场景 | 分辨率级别 | 类比 | 嵌入式常用 |
|---|---|---|---|---|---|
| BT.601 | 1982 | 标清电视 SDTV | 640×480 / 720×576 | 老式 CRT 电视 | ✅ 最常用 |
| BT.709 | 1990 | 高清电视 HDTV | 1280×720 / 1920×1080 | 1080p 液晶电视 | 部分使用 |
| BT.2020 | 2012 | 超高清 UHDTV | 4K / 8K | 最新的 HDR 电视 | 很少使用 |
2.2.2 标准演进时间线
#mermaid-svg-0BRp2DAMMYU7Ex89{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0BRp2DAMMYU7Ex89 .error-icon{fill:#552222;}#mermaid-svg-0BRp2DAMMYU7Ex89 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0BRp2DAMMYU7Ex89 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0BRp2DAMMYU7Ex89 .marker.cross{stroke:#333333;}#mermaid-svg-0BRp2DAMMYU7Ex89 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0BRp2DAMMYU7Ex89 p{margin:0;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge{stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 text{fill:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth--1{stroke-width:17;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-0{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-0{stroke-width:14;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-1{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-1{stroke-width:11;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 text{fill:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-2{stroke-width:8;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-3{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-3{stroke-width:5;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-4{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-4{stroke-width:2;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-5{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-5{stroke-width:-1;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-6{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-6{stroke-width:-4;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-7{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-7{stroke-width:-7;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-8{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-8{stroke-width:-10;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-9{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-9{stroke-width:-13;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 text{fill:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .node-icon-10{font-size:40px;color:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge-depth-10{stroke-width:-16;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-0BRp2DAMMYU7Ex89 .lineWrapper line{stroke:black;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled circle,#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:lightgray;}#mermaid-svg-0BRp2DAMMYU7Ex89 .disabled text{fill:#efefef;}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-root rect,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-root path,#mermaid-svg-0BRp2DAMMYU7Ex89 .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-0BRp2DAMMYU7Ex89 .section-root text{fill:#ffffff;}#mermaid-svg-0BRp2DAMMYU7Ex89 .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-0BRp2DAMMYU7Ex89 .edge{fill:none;}#mermaid-svg-0BRp2DAMMYU7Ex89 .eventWrapper{filter:brightness(120%);}#mermaid-svg-0BRp2DAMMYU7Ex89 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} BT.601 1982 年发布 标清电视 SDTV 640×480 / 720×576 嵌入式摄像头最常用 BT.709 1990 年发布 高清电视 HDTV 1280×720 / 1920×1080 现代显示器标准 BT.2020 2012 年发布 超高清 UHDTV 4K / 8K HDR 广色域 YUV→RGB 转换标准演进
2.2.3 三大标准色域范围对比
#mermaid-svg-43NnCLdRLx4DLUyB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-43NnCLdRLx4DLUyB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-43NnCLdRLx4DLUyB .error-icon{fill:#552222;}#mermaid-svg-43NnCLdRLx4DLUyB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-43NnCLdRLx4DLUyB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-43NnCLdRLx4DLUyB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-43NnCLdRLx4DLUyB .marker.cross{stroke:#333333;}#mermaid-svg-43NnCLdRLx4DLUyB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-43NnCLdRLx4DLUyB p{margin:0;}#mermaid-svg-43NnCLdRLx4DLUyB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-43NnCLdRLx4DLUyB .cluster-label text{fill:#333;}#mermaid-svg-43NnCLdRLx4DLUyB .cluster-label span{color:#333;}#mermaid-svg-43NnCLdRLx4DLUyB .cluster-label span p{background-color:transparent;}#mermaid-svg-43NnCLdRLx4DLUyB .label text,#mermaid-svg-43NnCLdRLx4DLUyB span{fill:#333;color:#333;}#mermaid-svg-43NnCLdRLx4DLUyB .node rect,#mermaid-svg-43NnCLdRLx4DLUyB .node circle,#mermaid-svg-43NnCLdRLx4DLUyB .node ellipse,#mermaid-svg-43NnCLdRLx4DLUyB .node polygon,#mermaid-svg-43NnCLdRLx4DLUyB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-43NnCLdRLx4DLUyB .rough-node .label text,#mermaid-svg-43NnCLdRLx4DLUyB .node .label text,#mermaid-svg-43NnCLdRLx4DLUyB .image-shape .label,#mermaid-svg-43NnCLdRLx4DLUyB .icon-shape .label{text-anchor:middle;}#mermaid-svg-43NnCLdRLx4DLUyB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-43NnCLdRLx4DLUyB .rough-node .label,#mermaid-svg-43NnCLdRLx4DLUyB .node .label,#mermaid-svg-43NnCLdRLx4DLUyB .image-shape .label,#mermaid-svg-43NnCLdRLx4DLUyB .icon-shape .label{text-align:center;}#mermaid-svg-43NnCLdRLx4DLUyB .node.clickable{cursor:pointer;}#mermaid-svg-43NnCLdRLx4DLUyB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-43NnCLdRLx4DLUyB .arrowheadPath{fill:#333333;}#mermaid-svg-43NnCLdRLx4DLUyB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-43NnCLdRLx4DLUyB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-43NnCLdRLx4DLUyB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-43NnCLdRLx4DLUyB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-43NnCLdRLx4DLUyB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-43NnCLdRLx4DLUyB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-43NnCLdRLx4DLUyB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-43NnCLdRLx4DLUyB .cluster text{fill:#333;}#mermaid-svg-43NnCLdRLx4DLUyB .cluster span{color:#333;}#mermaid-svg-43NnCLdRLx4DLUyB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-43NnCLdRLx4DLUyB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-43NnCLdRLx4DLUyB rect.text{fill:none;stroke-width:0;}#mermaid-svg-43NnCLdRLx4DLUyB .icon-shape,#mermaid-svg-43NnCLdRLx4DLUyB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-43NnCLdRLx4DLUyB .icon-shape p,#mermaid-svg-43NnCLdRLx4DLUyB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-43NnCLdRLx4DLUyB .icon-shape .label rect,#mermaid-svg-43NnCLdRLx4DLUyB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-43NnCLdRLx4DLUyB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-43NnCLdRLx4DLUyB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-43NnCLdRLx4DLUyB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} BT.2020 超高清 广色域
红色更深
绿色更翠
蓝色更紫
BT.709 高清 = sRGB 标准
红色正红
绿色正绿
蓝色正蓝
BT.601 标清
红色偏橙
绿色偏黄
蓝色偏紫
2.2.4 BT.601 转换公式
Limited Range(标准范围) :Y: 16~235, UV: 16~240(中心值 128)
Full Range(完整范围):Y: 0~255, UV: 0~255(中心值 128)
R = Y + 1.402 × (V - 128)
G = Y - 0.344 × (U - 128) - 0.714 × (V - 128)
B = Y + 1.772 × (U - 128)
注意:公式完全一样,区别仅在于输入范围不同。Limited Range 是广播电视标准,Full Range 是 PC 标准。嵌入式摄像头通常输出 Full Range。
2.2.5 YUYV 转 RGB 数据流
#mermaid-svg-tWf00CIEmol4ccAe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tWf00CIEmol4ccAe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tWf00CIEmol4ccAe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tWf00CIEmol4ccAe .error-icon{fill:#552222;}#mermaid-svg-tWf00CIEmol4ccAe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tWf00CIEmol4ccAe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tWf00CIEmol4ccAe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tWf00CIEmol4ccAe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tWf00CIEmol4ccAe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tWf00CIEmol4ccAe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tWf00CIEmol4ccAe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tWf00CIEmol4ccAe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tWf00CIEmol4ccAe .marker.cross{stroke:#333333;}#mermaid-svg-tWf00CIEmol4ccAe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tWf00CIEmol4ccAe p{margin:0;}#mermaid-svg-tWf00CIEmol4ccAe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tWf00CIEmol4ccAe .cluster-label text{fill:#333;}#mermaid-svg-tWf00CIEmol4ccAe .cluster-label span{color:#333;}#mermaid-svg-tWf00CIEmol4ccAe .cluster-label span p{background-color:transparent;}#mermaid-svg-tWf00CIEmol4ccAe .label text,#mermaid-svg-tWf00CIEmol4ccAe span{fill:#333;color:#333;}#mermaid-svg-tWf00CIEmol4ccAe .node rect,#mermaid-svg-tWf00CIEmol4ccAe .node circle,#mermaid-svg-tWf00CIEmol4ccAe .node ellipse,#mermaid-svg-tWf00CIEmol4ccAe .node polygon,#mermaid-svg-tWf00CIEmol4ccAe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tWf00CIEmol4ccAe .rough-node .label text,#mermaid-svg-tWf00CIEmol4ccAe .node .label text,#mermaid-svg-tWf00CIEmol4ccAe .image-shape .label,#mermaid-svg-tWf00CIEmol4ccAe .icon-shape .label{text-anchor:middle;}#mermaid-svg-tWf00CIEmol4ccAe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tWf00CIEmol4ccAe .rough-node .label,#mermaid-svg-tWf00CIEmol4ccAe .node .label,#mermaid-svg-tWf00CIEmol4ccAe .image-shape .label,#mermaid-svg-tWf00CIEmol4ccAe .icon-shape .label{text-align:center;}#mermaid-svg-tWf00CIEmol4ccAe .node.clickable{cursor:pointer;}#mermaid-svg-tWf00CIEmol4ccAe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tWf00CIEmol4ccAe .arrowheadPath{fill:#333333;}#mermaid-svg-tWf00CIEmol4ccAe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tWf00CIEmol4ccAe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tWf00CIEmol4ccAe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tWf00CIEmol4ccAe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tWf00CIEmol4ccAe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tWf00CIEmol4ccAe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tWf00CIEmol4ccAe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tWf00CIEmol4ccAe .cluster text{fill:#333;}#mermaid-svg-tWf00CIEmol4ccAe .cluster span{color:#333;}#mermaid-svg-tWf00CIEmol4ccAe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tWf00CIEmol4ccAe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tWf00CIEmol4ccAe rect.text{fill:none;stroke-width:0;}#mermaid-svg-tWf00CIEmol4ccAe .icon-shape,#mermaid-svg-tWf00CIEmol4ccAe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tWf00CIEmol4ccAe .icon-shape p,#mermaid-svg-tWf00CIEmol4ccAe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tWf00CIEmol4ccAe .icon-shape .label rect,#mermaid-svg-tWf00CIEmol4ccAe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tWf00CIEmol4ccAe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tWf00CIEmol4ccAe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tWf00CIEmol4ccAe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RGB输出
钳位
公式计算 像素1
公式计算 像素0
YUYV输入
Y0
U
Y1
V
R0 = Y0 + 1.402 × (V - 128)
G0 = Y0 - 0.344 × (U - 128) - 0.714 × (V - 128)
B0 = Y0 + 1.772 × (U - 128)
R1 = Y1 + 1.402 × (V - 128)
G1 = Y1 - 0.344 × (U - 128) - 0.714 × (V - 128)
B1 = Y1 + 1.772 × (U - 128)
像素0 ← 钳位到 0~255
像素1 ← 钳位到 0~255
RGB 数据流
R0 G0 B0 R1 G1 B1 ...
一句话总结:YUYV 每 4 字节转成 6 字节 RGB------两个像素共享同一组 UV,各自用自己的 Y 算出 R/G/B,最后钳位到 0~255 防止溢出。
2.3 JPEG 压缩原理与 libjpeg
一句话总结
JPEG 用"有损压缩"把图片缩小到原来的 1/10 甚至更小,libjpeg 就是帮我们完成这个压缩的开源库。
类比:JPEG 压缩就像给一幅画拍照发朋友圈------你保留了画面的主要内容,但丢掉了一些细节(比如背景的纹理),这样照片文件很小,但看起来还是那幅画。
2.3.1 JPEG 与原始格式对比
| 对比维度 | 原始 YUV/RGB | JPEG |
|---|---|---|
| 文件大小 | 大(640×480 ≈ 900KB) | 小(640×480 ≈ 50~200KB) |
| 压缩方式 | 无压缩 | 有损压缩 |
| 质量可调 | 不可调 | 1~100 可调 |
| 解码开销 | 无 | 需要解码才能显示 |
| 适用场景 | 图像处理、中间数据 | 存储、传输、分享 |
| 色彩精度 | 精确 | 有损,人眼可接受 |
2.3.1a JPEG 质量参数与文件大小关系
质量参数 1~100 控制的是"丢掉多少细节"------质量越低,文件越小,但画质损失越明显。
| 质量值 | 640×480 文件大小 | 压缩比 | 画质评价 | 典型应用 |
|---|---|---|---|---|
| 100 | ~300 KB | 3:1 | 肉眼无损 | 专业存档 |
| 90 | ~120 KB | 8:1 | 几乎无损 | 默认推荐值 |
| 75 | ~70 KB | 13:1 | 良好 | Web 图片 |
| 50 | ~45 KB | 20:1 | 可接受 | 缩略图 |
| 25 | ~30 KB | 30:1 | 有明显块效应 | 低带宽传输 |
| 10 | ~20 KB | 45:1 | 画质差 | 极低带宽场景 |
建议:嵌入式摄像头拍照保存时,质量 90 是最好的平衡点------文件足够小,人眼几乎看不出压缩痕迹。
2.3.2 JPEG 压缩 5 步流程
#mermaid-svg-YaebsmjZE74H2THJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YaebsmjZE74H2THJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YaebsmjZE74H2THJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YaebsmjZE74H2THJ .error-icon{fill:#552222;}#mermaid-svg-YaebsmjZE74H2THJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YaebsmjZE74H2THJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YaebsmjZE74H2THJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YaebsmjZE74H2THJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YaebsmjZE74H2THJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YaebsmjZE74H2THJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YaebsmjZE74H2THJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YaebsmjZE74H2THJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YaebsmjZE74H2THJ .marker.cross{stroke:#333333;}#mermaid-svg-YaebsmjZE74H2THJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YaebsmjZE74H2THJ p{margin:0;}#mermaid-svg-YaebsmjZE74H2THJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YaebsmjZE74H2THJ .cluster-label text{fill:#333;}#mermaid-svg-YaebsmjZE74H2THJ .cluster-label span{color:#333;}#mermaid-svg-YaebsmjZE74H2THJ .cluster-label span p{background-color:transparent;}#mermaid-svg-YaebsmjZE74H2THJ .label text,#mermaid-svg-YaebsmjZE74H2THJ span{fill:#333;color:#333;}#mermaid-svg-YaebsmjZE74H2THJ .node rect,#mermaid-svg-YaebsmjZE74H2THJ .node circle,#mermaid-svg-YaebsmjZE74H2THJ .node ellipse,#mermaid-svg-YaebsmjZE74H2THJ .node polygon,#mermaid-svg-YaebsmjZE74H2THJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YaebsmjZE74H2THJ .rough-node .label text,#mermaid-svg-YaebsmjZE74H2THJ .node .label text,#mermaid-svg-YaebsmjZE74H2THJ .image-shape .label,#mermaid-svg-YaebsmjZE74H2THJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-YaebsmjZE74H2THJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YaebsmjZE74H2THJ .rough-node .label,#mermaid-svg-YaebsmjZE74H2THJ .node .label,#mermaid-svg-YaebsmjZE74H2THJ .image-shape .label,#mermaid-svg-YaebsmjZE74H2THJ .icon-shape .label{text-align:center;}#mermaid-svg-YaebsmjZE74H2THJ .node.clickable{cursor:pointer;}#mermaid-svg-YaebsmjZE74H2THJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YaebsmjZE74H2THJ .arrowheadPath{fill:#333333;}#mermaid-svg-YaebsmjZE74H2THJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YaebsmjZE74H2THJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YaebsmjZE74H2THJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YaebsmjZE74H2THJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YaebsmjZE74H2THJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YaebsmjZE74H2THJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YaebsmjZE74H2THJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YaebsmjZE74H2THJ .cluster text{fill:#333;}#mermaid-svg-YaebsmjZE74H2THJ .cluster span{color:#333;}#mermaid-svg-YaebsmjZE74H2THJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YaebsmjZE74H2THJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YaebsmjZE74H2THJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-YaebsmjZE74H2THJ .icon-shape,#mermaid-svg-YaebsmjZE74H2THJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YaebsmjZE74H2THJ .icon-shape p,#mermaid-svg-YaebsmjZE74H2THJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YaebsmjZE74H2THJ .icon-shape .label rect,#mermaid-svg-YaebsmjZE74H2THJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YaebsmjZE74H2THJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YaebsmjZE74H2THJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YaebsmjZE74H2THJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RGB 像素数据
原始数据
色彩空间转换
RGB → YCbCr
下采样
4:2:0 降低 UV 分辨率
丢掉人眼不敏感的彩色细节
DCT 离散余弦变换
把 8×8 像素块
转为频率系数
量化
丢掉高频细节
保留低频轮廓
这是有损压缩的关键
熵编码
Huffman 编码
无损压缩剩余数据
JPEG 文件
压缩完成
一句话总结:JPEG 的"有损"发生在量化这一步------它把图像中细微的纹理细节(高频信息)近似掉,因为人眼看不太出来,但文件能小很多。质量参数 1~100 控制的就是"丢掉多少细节"。
2.3.2a JPEG 量化表------"有损"的秘密武器
JPEG 的"有损"发生在量化步骤。量化表是一个 8×8 的矩阵,每个元素代表对应 DCT 频率系数的量化步长:
标准亮度量化表(质量 = 50):
16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
解读:左上角是最低频分量(决定轮廓),量化步长小(16~10),保留更多细节。右下角是最 高频分量(决定纹理细节),量化步长大(61~99),大量信息被丢弃。这就是为什么 JPEG 文件能那么小------人眼对高频细节不敏感,丢掉也看不出来。
质量参数 1~100 的本质就是缩放这个量化表:质量越高,所有元素越小,保留越多;质量越低,所有元素越大,丢弃越多。
2.3.3 图片格式生态对比
除了 JPEG,嵌入式开发中还会遇到其他图片格式,了解它们的特点有助于在合适的场景做正确的选择。
| 格式 | 压缩方式 | 透明度 | 适合场景 | 不适合场景 |
|---|---|---|---|---|
| JPEG | 有损 | ❌ | 照片、实景图 | 文字截图、图表 |
| PNG | 无损 | ✅ | 图标、截图、文字 | 照片(文件太大) |
| BMP | 无压缩 | ❌ | 调试、中间数据 | 存储、传输 |
| WebP | 有损/无损 | ✅ | Web 图片(比 JPEG 小 30%) | 兼容性(老旧设备) |
一句话总结:照片存 JPEG,截图存 PNG,调试用 BMP,Web 图片用 WebP。
2.3.3a JPEG 文件中的 EXIF 信息
JPEG 文件不仅仅是压缩后的图像数据,还包含一个称为 EXIF(Exchangeable Image File Format)的元数据区,记录了拍摄时的各种信息:
#mermaid-svg-B1l5nUi45F16IxNL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B1l5nUi45F16IxNL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B1l5nUi45F16IxNL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B1l5nUi45F16IxNL .error-icon{fill:#552222;}#mermaid-svg-B1l5nUi45F16IxNL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B1l5nUi45F16IxNL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B1l5nUi45F16IxNL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B1l5nUi45F16IxNL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B1l5nUi45F16IxNL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B1l5nUi45F16IxNL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B1l5nUi45F16IxNL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B1l5nUi45F16IxNL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B1l5nUi45F16IxNL .marker.cross{stroke:#333333;}#mermaid-svg-B1l5nUi45F16IxNL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B1l5nUi45F16IxNL p{margin:0;}#mermaid-svg-B1l5nUi45F16IxNL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B1l5nUi45F16IxNL .cluster-label text{fill:#333;}#mermaid-svg-B1l5nUi45F16IxNL .cluster-label span{color:#333;}#mermaid-svg-B1l5nUi45F16IxNL .cluster-label span p{background-color:transparent;}#mermaid-svg-B1l5nUi45F16IxNL .label text,#mermaid-svg-B1l5nUi45F16IxNL span{fill:#333;color:#333;}#mermaid-svg-B1l5nUi45F16IxNL .node rect,#mermaid-svg-B1l5nUi45F16IxNL .node circle,#mermaid-svg-B1l5nUi45F16IxNL .node ellipse,#mermaid-svg-B1l5nUi45F16IxNL .node polygon,#mermaid-svg-B1l5nUi45F16IxNL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B1l5nUi45F16IxNL .rough-node .label text,#mermaid-svg-B1l5nUi45F16IxNL .node .label text,#mermaid-svg-B1l5nUi45F16IxNL .image-shape .label,#mermaid-svg-B1l5nUi45F16IxNL .icon-shape .label{text-anchor:middle;}#mermaid-svg-B1l5nUi45F16IxNL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B1l5nUi45F16IxNL .rough-node .label,#mermaid-svg-B1l5nUi45F16IxNL .node .label,#mermaid-svg-B1l5nUi45F16IxNL .image-shape .label,#mermaid-svg-B1l5nUi45F16IxNL .icon-shape .label{text-align:center;}#mermaid-svg-B1l5nUi45F16IxNL .node.clickable{cursor:pointer;}#mermaid-svg-B1l5nUi45F16IxNL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B1l5nUi45F16IxNL .arrowheadPath{fill:#333333;}#mermaid-svg-B1l5nUi45F16IxNL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B1l5nUi45F16IxNL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B1l5nUi45F16IxNL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B1l5nUi45F16IxNL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B1l5nUi45F16IxNL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B1l5nUi45F16IxNL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B1l5nUi45F16IxNL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B1l5nUi45F16IxNL .cluster text{fill:#333;}#mermaid-svg-B1l5nUi45F16IxNL .cluster span{color:#333;}#mermaid-svg-B1l5nUi45F16IxNL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B1l5nUi45F16IxNL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B1l5nUi45F16IxNL rect.text{fill:none;stroke-width:0;}#mermaid-svg-B1l5nUi45F16IxNL .icon-shape,#mermaid-svg-B1l5nUi45F16IxNL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B1l5nUi45F16IxNL .icon-shape p,#mermaid-svg-B1l5nUi45F16IxNL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B1l5nUi45F16IxNL .icon-shape .label rect,#mermaid-svg-B1l5nUi45F16IxNL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B1l5nUi45F16IxNL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B1l5nUi45F16IxNL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B1l5nUi45F16IxNL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 文件尾
图像数据
编码参数表
元数据区
文件头
SOI 0xFFD8
文件起始标记
APP1 EXIF 数据
拍摄时间
摄像头型号
焦距、光圈、ISO
缩略图
旋转方向
DHT Huffman 表
DQT 量化表
SOF 帧参数
压缩图像数据
多个 MCU 块
EOI 0xFFD9
文件结束标记
注意:我们的嵌入式代码生成的 JPEG 不包含 EXIF 信息,因为 libjpeg 默认只输出纯图像数据。如果需要嵌入 EXIF,需要额外用 libexif 库处理。
2.3.4 libjpeg 压缩流程
#mermaid-svg-5vnUHptNJYivbasc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5vnUHptNJYivbasc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5vnUHptNJYivbasc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5vnUHptNJYivbasc .error-icon{fill:#552222;}#mermaid-svg-5vnUHptNJYivbasc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5vnUHptNJYivbasc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5vnUHptNJYivbasc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5vnUHptNJYivbasc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5vnUHptNJYivbasc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5vnUHptNJYivbasc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5vnUHptNJYivbasc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5vnUHptNJYivbasc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5vnUHptNJYivbasc .marker.cross{stroke:#333333;}#mermaid-svg-5vnUHptNJYivbasc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5vnUHptNJYivbasc p{margin:0;}#mermaid-svg-5vnUHptNJYivbasc defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-5vnUHptNJYivbasc g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-5vnUHptNJYivbasc g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-5vnUHptNJYivbasc g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-5vnUHptNJYivbasc g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-5vnUHptNJYivbasc g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-5vnUHptNJYivbasc .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-5vnUHptNJYivbasc .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-5vnUHptNJYivbasc .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-5vnUHptNJYivbasc .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5vnUHptNJYivbasc .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-5vnUHptNJYivbasc .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-5vnUHptNJYivbasc .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-5vnUHptNJYivbasc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5vnUHptNJYivbasc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5vnUHptNJYivbasc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5vnUHptNJYivbasc .edgeLabel .label text{fill:#333;}#mermaid-svg-5vnUHptNJYivbasc .label div .edgeLabel{color:#333;}#mermaid-svg-5vnUHptNJYivbasc .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-5vnUHptNJYivbasc .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-5vnUHptNJYivbasc .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-5vnUHptNJYivbasc .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-5vnUHptNJYivbasc .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-5vnUHptNJYivbasc .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5vnUHptNJYivbasc .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5vnUHptNJYivbasc #statediagram-barbEnd{fill:#333333;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5vnUHptNJYivbasc .cluster-label,#mermaid-svg-5vnUHptNJYivbasc .nodeLabel{color:#131300;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-5vnUHptNJYivbasc .note-edge{stroke-dasharray:5;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-note text{fill:black;}#mermaid-svg-5vnUHptNJYivbasc .statediagram-note .nodeLabel{color:black;}#mermaid-svg-5vnUHptNJYivbasc .statediagram .edgeLabel{color:red;}#mermaid-svg-5vnUHptNJYivbasc #dependencyStart,#mermaid-svg-5vnUHptNJYivbasc #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-5vnUHptNJYivbasc .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5vnUHptNJYivbasc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} jpeg_create_compress
jpeg_stdio_dest
image_width / height
input_components / in_color_space
jpeg_set_defaults
jpeg_set_quality(quality)
jpeg_start_compress
jpeg_write_scanlines
while next_scanline < height
jpeg_finish_compress
jpeg_destroy_compress
创建压缩对象
绑定输出文件
设置参数
设置默认值
设置质量
开始压缩
逐行写入
完成压缩
销毁对象
在写入每一行之前
先将 YUYV 转为 RGB
存入 line_buf
2.3.4a libjpeg 交叉编译(ARM 平台)
要在 ARM 开发板上使用 libjpeg,需要先交叉编译 libjpeg 库:
bash
# 1. 下载 libjpeg-turbo 源码
wget https://github.com/libjpeg-turbo/libjpeg-turbo/archive/1.5.3.tar.gz
tar -zxvf libjpeg-turbo-1.5.3.tar.gz
cd libjpeg-turbo-1.5.3/
# 2. 交叉编译配置
./configure CC="arm-linux-gnueabihf-gcc" \
--host="arm-linux-gnueabihf" \
--prefix="/opt/arm/libjpeg/result" \
--enable-static
make
make install
编译产物:
include/--- 头文件(jpeglib.h等)lib/--- 库文件(libjpeg.a静态库,libturbojpeg.a)bin/--- 工具程序
注意 :如果只是在 Ubuntu 本机开发和测试,可以直接用
sudo apt-get install libjpeg-dev安装,无需交叉编译。
2.3.5 完整数据链路:从摄像头到 JPEG
#mermaid-svg-bnEzjYhPJV2yjkJA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bnEzjYhPJV2yjkJA .error-icon{fill:#552222;}#mermaid-svg-bnEzjYhPJV2yjkJA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bnEzjYhPJV2yjkJA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bnEzjYhPJV2yjkJA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bnEzjYhPJV2yjkJA .marker.cross{stroke:#333333;}#mermaid-svg-bnEzjYhPJV2yjkJA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bnEzjYhPJV2yjkJA p{margin:0;}#mermaid-svg-bnEzjYhPJV2yjkJA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster-label text{fill:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster-label span{color:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster-label span p{background-color:transparent;}#mermaid-svg-bnEzjYhPJV2yjkJA .label text,#mermaid-svg-bnEzjYhPJV2yjkJA span{fill:#333;color:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA .node rect,#mermaid-svg-bnEzjYhPJV2yjkJA .node circle,#mermaid-svg-bnEzjYhPJV2yjkJA .node ellipse,#mermaid-svg-bnEzjYhPJV2yjkJA .node polygon,#mermaid-svg-bnEzjYhPJV2yjkJA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bnEzjYhPJV2yjkJA .rough-node .label text,#mermaid-svg-bnEzjYhPJV2yjkJA .node .label text,#mermaid-svg-bnEzjYhPJV2yjkJA .image-shape .label,#mermaid-svg-bnEzjYhPJV2yjkJA .icon-shape .label{text-anchor:middle;}#mermaid-svg-bnEzjYhPJV2yjkJA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bnEzjYhPJV2yjkJA .rough-node .label,#mermaid-svg-bnEzjYhPJV2yjkJA .node .label,#mermaid-svg-bnEzjYhPJV2yjkJA .image-shape .label,#mermaid-svg-bnEzjYhPJV2yjkJA .icon-shape .label{text-align:center;}#mermaid-svg-bnEzjYhPJV2yjkJA .node.clickable{cursor:pointer;}#mermaid-svg-bnEzjYhPJV2yjkJA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bnEzjYhPJV2yjkJA .arrowheadPath{fill:#333333;}#mermaid-svg-bnEzjYhPJV2yjkJA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bnEzjYhPJV2yjkJA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bnEzjYhPJV2yjkJA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bnEzjYhPJV2yjkJA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bnEzjYhPJV2yjkJA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bnEzjYhPJV2yjkJA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster text{fill:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA .cluster span{color:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bnEzjYhPJV2yjkJA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bnEzjYhPJV2yjkJA rect.text{fill:none;stroke-width:0;}#mermaid-svg-bnEzjYhPJV2yjkJA .icon-shape,#mermaid-svg-bnEzjYhPJV2yjkJA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bnEzjYhPJV2yjkJA .icon-shape p,#mermaid-svg-bnEzjYhPJV2yjkJA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bnEzjYhPJV2yjkJA .icon-shape .label rect,#mermaid-svg-bnEzjYhPJV2yjkJA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bnEzjYhPJV2yjkJA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bnEzjYhPJV2yjkJA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bnEzjYhPJV2yjkJA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可选输出
压缩
转换
采集
摄像头硬件
V4L2 驱动
YUYV 格式
内核缓冲区
YUYV 原始数据
mmap 映射
用户空间直接访问
YUYV 数据
Y0 U Y1 V 每4字节2像素
BT.601 公式
YUV → RGB
RGB 数据
R0 G0 B0 每像素3字节
libjpeg 逐行压缩
picture.jpg
picture.yuv
原始数据
picture.rgb
调试用
一句话总结:摄像头 → YUYV 原始数据 → RGB 中间数据 → JPEG 压缩文件,三步走完一条完整的图像采集处理链路。
第三章 完整代码工程
3.1 系统架构总览
#mermaid-svg-mPBLdM1hI60W2nXW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mPBLdM1hI60W2nXW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mPBLdM1hI60W2nXW .error-icon{fill:#552222;}#mermaid-svg-mPBLdM1hI60W2nXW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mPBLdM1hI60W2nXW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mPBLdM1hI60W2nXW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mPBLdM1hI60W2nXW .marker.cross{stroke:#333333;}#mermaid-svg-mPBLdM1hI60W2nXW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mPBLdM1hI60W2nXW p{margin:0;}#mermaid-svg-mPBLdM1hI60W2nXW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mPBLdM1hI60W2nXW .cluster-label text{fill:#333;}#mermaid-svg-mPBLdM1hI60W2nXW .cluster-label span{color:#333;}#mermaid-svg-mPBLdM1hI60W2nXW .cluster-label span p{background-color:transparent;}#mermaid-svg-mPBLdM1hI60W2nXW .label text,#mermaid-svg-mPBLdM1hI60W2nXW span{fill:#333;color:#333;}#mermaid-svg-mPBLdM1hI60W2nXW .node rect,#mermaid-svg-mPBLdM1hI60W2nXW .node circle,#mermaid-svg-mPBLdM1hI60W2nXW .node ellipse,#mermaid-svg-mPBLdM1hI60W2nXW .node polygon,#mermaid-svg-mPBLdM1hI60W2nXW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mPBLdM1hI60W2nXW .rough-node .label text,#mermaid-svg-mPBLdM1hI60W2nXW .node .label text,#mermaid-svg-mPBLdM1hI60W2nXW .image-shape .label,#mermaid-svg-mPBLdM1hI60W2nXW .icon-shape .label{text-anchor:middle;}#mermaid-svg-mPBLdM1hI60W2nXW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mPBLdM1hI60W2nXW .rough-node .label,#mermaid-svg-mPBLdM1hI60W2nXW .node .label,#mermaid-svg-mPBLdM1hI60W2nXW .image-shape .label,#mermaid-svg-mPBLdM1hI60W2nXW .icon-shape .label{text-align:center;}#mermaid-svg-mPBLdM1hI60W2nXW .node.clickable{cursor:pointer;}#mermaid-svg-mPBLdM1hI60W2nXW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mPBLdM1hI60W2nXW .arrowheadPath{fill:#333333;}#mermaid-svg-mPBLdM1hI60W2nXW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mPBLdM1hI60W2nXW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mPBLdM1hI60W2nXW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mPBLdM1hI60W2nXW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mPBLdM1hI60W2nXW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mPBLdM1hI60W2nXW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mPBLdM1hI60W2nXW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mPBLdM1hI60W2nXW .cluster text{fill:#333;}#mermaid-svg-mPBLdM1hI60W2nXW .cluster span{color:#333;}#mermaid-svg-mPBLdM1hI60W2nXW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mPBLdM1hI60W2nXW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mPBLdM1hI60W2nXW rect.text{fill:none;stroke-width:0;}#mermaid-svg-mPBLdM1hI60W2nXW .icon-shape,#mermaid-svg-mPBLdM1hI60W2nXW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mPBLdM1hI60W2nXW .icon-shape p,#mermaid-svg-mPBLdM1hI60W2nXW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mPBLdM1hI60W2nXW .icon-shape .label rect,#mermaid-svg-mPBLdM1hI60W2nXW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mPBLdM1hI60W2nXW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mPBLdM1hI60W2nXW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mPBLdM1hI60W2nXW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输出文件
格式转换层
应用层
V4L2 驱动层
硬件层
USB 摄像头
/dev/video0
open 打开设备
VIDIOC_QUERYCAP
查询能力
VIDIOC_ENUM_FMT
枚举格式
VIDIOC_S_FMT
设置 YUYV 640x480
VIDIOC_REQBUFS
申请4个缓冲区
VIDIOC_QUERYBUF
查询缓冲区信息
mmap 映射到用户空间
VIDIOC_QBUF
入队
VIDIOC_STREAMON
开流
VIDIOC_DQBUF
取帧
init_camera
init_mmap
start_capturing
select 等待20秒
read_frame
yuyv_to_rgb
YUYV → RGB
yuyv_to_jpeg
YUYV → JPEG
picture.yuv
原始 YUYV
picture.rgb
RGB 数据
picture.jpg
JPEG 图片
3.1.1 函数调用关系
#mermaid-svg-EfA9EWFA1wd84A16{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EfA9EWFA1wd84A16 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EfA9EWFA1wd84A16 .error-icon{fill:#552222;}#mermaid-svg-EfA9EWFA1wd84A16 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EfA9EWFA1wd84A16 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EfA9EWFA1wd84A16 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EfA9EWFA1wd84A16 .marker.cross{stroke:#333333;}#mermaid-svg-EfA9EWFA1wd84A16 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EfA9EWFA1wd84A16 p{margin:0;}#mermaid-svg-EfA9EWFA1wd84A16 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EfA9EWFA1wd84A16 .cluster-label text{fill:#333;}#mermaid-svg-EfA9EWFA1wd84A16 .cluster-label span{color:#333;}#mermaid-svg-EfA9EWFA1wd84A16 .cluster-label span p{background-color:transparent;}#mermaid-svg-EfA9EWFA1wd84A16 .label text,#mermaid-svg-EfA9EWFA1wd84A16 span{fill:#333;color:#333;}#mermaid-svg-EfA9EWFA1wd84A16 .node rect,#mermaid-svg-EfA9EWFA1wd84A16 .node circle,#mermaid-svg-EfA9EWFA1wd84A16 .node ellipse,#mermaid-svg-EfA9EWFA1wd84A16 .node polygon,#mermaid-svg-EfA9EWFA1wd84A16 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EfA9EWFA1wd84A16 .rough-node .label text,#mermaid-svg-EfA9EWFA1wd84A16 .node .label text,#mermaid-svg-EfA9EWFA1wd84A16 .image-shape .label,#mermaid-svg-EfA9EWFA1wd84A16 .icon-shape .label{text-anchor:middle;}#mermaid-svg-EfA9EWFA1wd84A16 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EfA9EWFA1wd84A16 .rough-node .label,#mermaid-svg-EfA9EWFA1wd84A16 .node .label,#mermaid-svg-EfA9EWFA1wd84A16 .image-shape .label,#mermaid-svg-EfA9EWFA1wd84A16 .icon-shape .label{text-align:center;}#mermaid-svg-EfA9EWFA1wd84A16 .node.clickable{cursor:pointer;}#mermaid-svg-EfA9EWFA1wd84A16 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EfA9EWFA1wd84A16 .arrowheadPath{fill:#333333;}#mermaid-svg-EfA9EWFA1wd84A16 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EfA9EWFA1wd84A16 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EfA9EWFA1wd84A16 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EfA9EWFA1wd84A16 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EfA9EWFA1wd84A16 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EfA9EWFA1wd84A16 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EfA9EWFA1wd84A16 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EfA9EWFA1wd84A16 .cluster text{fill:#333;}#mermaid-svg-EfA9EWFA1wd84A16 .cluster span{color:#333;}#mermaid-svg-EfA9EWFA1wd84A16 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EfA9EWFA1wd84A16 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EfA9EWFA1wd84A16 rect.text{fill:none;stroke-width:0;}#mermaid-svg-EfA9EWFA1wd84A16 .icon-shape,#mermaid-svg-EfA9EWFA1wd84A16 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EfA9EWFA1wd84A16 .icon-shape p,#mermaid-svg-EfA9EWFA1wd84A16 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EfA9EWFA1wd84A16 .icon-shape .label rect,#mermaid-svg-EfA9EWFA1wd84A16 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EfA9EWFA1wd84A16 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EfA9EWFA1wd84A16 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EfA9EWFA1wd84A16 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main
init_camera
init_mmap
start_capturing
select
read_frame
open 设备
ioctl QUERYCAP
ioctl ENUM_FMT
ioctl S_FMT
ioctl REQBUFS
ioctl QUERYBUF
mmap 映射
ioctl QBUF
ioctl DQBUF
fwrite 保存 YUV
yuyv_to_rgb
yuyv_to_jpeg
ioctl QBUF 重新入队
fopen 写 RGB
fwrite RGB 数据
fopen 写 JPEG
jpeg_create_compress
设置参数
jpeg_start_compress
jpeg_write_scanlines 逐行
jpeg_finish_compress
3.2 代码实现
3.2.1 camera.h --- 头文件
c
#ifndef _CAMERA_H_
#define _CAMERA_H_
#define IMG_WIDTH 640
#define IMG_HEIGHT 480
// 存储映射缓冲区信息
struct camera_t {
void *start; // 映射后的用户空间地址
int length; // 缓冲区长度
};
// V4L2 操作
extern int init_camera(const char *dev_path);
extern int init_mmap(int fd);
extern int start_capturing(int fd);
extern int read_frame(int fd);
// 格式转换
extern int yuyv_to_rgb(const unsigned char *yuv,
const char *file_name,
int width, int height);
extern int yuyv_to_jpeg(const unsigned char *yuv,
const char *file_name,
int width, int height,
int quality);
#endif //_CAMERA_H_
3.2.2 camera.c --- 核心实现
c
#include "camera.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <error.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <jpeglib.h>
static struct camera_t *camera_data;
static int n_buffer;
/**
* init_camera - 打开摄像头并初始化为 YUYV 格式
* @dev_path: 设备路径,如 "/dev/video0"
* 返回: 成功返回 fd,失败返回 -1
*
* 一句话总结:打开设备、查能力、看格式、设格式,四步到位。
*/
int init_camera(const char *dev_path)
{
// 1. 打开设备(非阻塞模式)
int fd = open(dev_path, O_RDWR | O_NONBLOCK);
if (fd < 0) {
perror("open dev failure!");
return -1;
}
// 2. 查询设备能力
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("VIDIOC_QUERYCAP failure!");
return -1;
}
// 3. 确认是视频捕获设备
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, "错误:该设备不是视频捕获设备!\n");
return -1;
}
// 4. 枚举所有支持的格式
struct v4l2_fmtdesc fmt;
fmt.index = 0;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("支持的像素格式:\n");
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
printf(" [%c%c%c%c] %s\n",
fmt.pixelformat & 0xFF,
(fmt.pixelformat >> 8) & 0xFF,
(fmt.pixelformat >> 16) & 0xFF,
(fmt.pixelformat >> 24) & 0xFF,
fmt.description);
fmt.index++;
}
// 5. 设置采集格式为 YUYV 640x480
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = IMG_WIDTH;
format.fmt.pix.height = IMG_HEIGHT;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
format.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) {
perror("VIDIOC_S_FMT failure!");
return -1;
}
return fd;
}
/**
* init_mmap - 申请内核缓冲区并映射到用户空间
* @fd: 摄像头文件描述符
*
* 一句话总结:申请 4 个内核缓冲区,映射到用户空间,然后入队等待相机使用。
* 类比:准备 4 个桶放在流水线上,相机拍一帧就自动装满一个桶。
*/
int init_mmap(int fd)
{
// 1. 申请 4 个内核缓冲区
struct v4l2_requestbuffers reqbuf;
reqbuf.count = 4;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
perror("VIDIOC_REQBUFS failure!");
return -1;
}
// 2. 分配存储映射信息的数组
camera_data = calloc(reqbuf.count, sizeof(struct camera_t));
// 3. 逐个查询并映射
for (int i = 0; i < reqbuf.count; i++) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("VIDIOC_QUERYBUF failure!");
return -1;
}
camera_data[i].length = buf.length;
camera_data[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
if (camera_data[i].start == MAP_FAILED) {
perror("mmap failure!");
return -1;
}
}
// 4. 将所有缓冲区放入输入队列
for (int i = 0; i < reqbuf.count; i++) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF failure");
return -1;
}
}
n_buffer = reqbuf.count;
return 0;
}
/**
* start_capturing - 启动视频流采集
* @fd: 摄像头文件描述符
*
* 一句话总结:打开水龙头,相机开始往缓冲区里填数据。
*/
int start_capturing(int fd)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("VIDIOC_STREAMON failure!");
return -1;
}
return 0;
}
/**
* read_frame - 从队列中取出一帧数据,保存为 YUV/RGB/JPEG
* @fd: 摄像头文件描述符
*
* 一句话总结:从流水线上拿下一个满的桶,把里面的数据存成三种格式。
*/
int read_frame(int fd)
{
// 1. 从输出队列取出一个满的缓冲区
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("VIDIOC_DQBUF failure!");
return -1;
}
// 2. 获取帧数据指针和长度
void *frame_ptr = camera_data[buf.index].start;
int frame_len = camera_data[buf.index].length;
// 3. 保存原始 YUYV 数据
FILE *yuv_fp = fopen("./picture.yuv", "wb");
if (yuv_fp) {
fwrite(frame_ptr, frame_len, 1, yuv_fp);
fclose(yuv_fp);
}
// 4. 转换为 RGB 文件
yuyv_to_rgb(frame_ptr, "./picture.rgb", IMG_WIDTH, IMG_HEIGHT);
// 5. 压缩为 JPEG
yuyv_to_jpeg(frame_ptr, "./picture.jpg", IMG_WIDTH, IMG_HEIGHT, 90);
// 6. 将缓冲区重新放回输入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF failure!");
return -1;
}
return 0;
}
/**
* yuyv_to_rgb - 将 YUYV 数据转换为 RGB 文件
* @yuv: YUYV 原始数据
* @file_name: 输出 RGB 文件名
* @width: 图片宽度
* @height: 图片高度
*
* 一句话总结:遍历每个像素,用 BT.601 公式把 YUYV 变成 RGB,写入文件。
* 关键:YUYV 中每 2 个像素共享一组 UV。
*/
int yuyv_to_rgb(const unsigned char *yuv, const char *file_name,
int width, int height)
{
if (!yuv || !file_name || width == 0 || height == 0)
return -1;
FILE *fp = fopen(file_name, "wb");
if (!fp) return -1;
unsigned char *line_buf = malloc(width * 3);
int y, u, v, r, g, b;
for (int row = 0; row < height; row++) {
unsigned char *ptr = line_buf;
for (int col = 0; col < width; col += 2) {
// 第一个像素
y = yuv[0];
u = yuv[1];
v = yuv[3];
r = y + 1.402 * (v - 128);
g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
b = y + 1.772 * (u - 128);
// 钳位到 0~255
ptr[0] = (r > 255) ? 255 : (r < 0 ? 0 : r);
ptr[1] = (g > 255) ? 255 : (g < 0 ? 0 : g);
ptr[2] = (b > 255) ? 255 : (b < 0 ? 0 : b);
// 第二个像素(U、V 不变,只换 Y)
y = yuv[2];
r = y + 1.402 * (v - 128);
g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
b = y + 1.772 * (u - 128);
ptr[3] = (r > 255) ? 255 : (r < 0 ? 0 : r);
ptr[4] = (g > 255) ? 255 : (g < 0 ? 0 : g);
ptr[5] = (b > 255) ? 255 : (b < 0 ? 0 : b);
ptr += 6;
yuv += 4; // 跳过 Y0 U Y1 V
}
fwrite(line_buf, width * 3, 1, fp);
}
fclose(fp);
free(line_buf);
return 0;
}
/**
* yuyv_to_jpeg - 将 YUYV 数据压缩为 JPEG 文件
* @yuv: YUYV 原始数据
* @file_name: 输出 JPEG 文件名
* @width: 图片宽度
* @height: 图片高度
* @quality: 压缩质量 (1~100)
*
* 一句话总结:每一行先把 YUYV 转成 RGB,再喂给 libjpeg 逐行压缩。
* 类比:就像流水线作业------先清洗(YUV→RGB),再打包(libjpeg 压缩)。
*/
int yuyv_to_jpeg(const unsigned char *yuv, const char *file_name,
int width, int height, int quality)
{
FILE *fp = fopen(file_name, "wb");
if (!fp) return -1;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
unsigned char *line_buf = calloc(width * 3, 1);
int y, u, v, r, g, b;
while (cinfo.next_scanline < height) {
unsigned char *ptr = line_buf;
for (int col = 0; col < width; col += 2) {
y = yuv[0];
u = yuv[1];
v = yuv[3];
r = y + 1.402 * (v - 128);
g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
b = y + 1.772 * (u - 128);
ptr[0] = (r > 255) ? 255 : (r < 0 ? 0 : r);
ptr[1] = (g > 255) ? 255 : (g < 0 ? 0 : g);
ptr[2] = (b > 255) ? 255 : (b < 0 ? 0 : b);
y = yuv[2];
r = y + 1.402 * (v - 128);
g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
b = y + 1.772 * (u - 128);
ptr[3] = (r > 255) ? 255 : (r < 0 ? 0 : r);
ptr[4] = (g > 255) ? 255 : (g < 0 ? 0 : g);
ptr[5] = (b > 255) ? 255 : (b < 0 ? 0 : b);
ptr += 6;
yuv += 4;
}
row_pointer[0] = line_buf;
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(fp);
free(line_buf);
return 0;
}
3.2.3 main.c --- 主程序入口
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include "camera.h"
int main()
{
// 1. 初始化摄像头
int cam_fd = init_camera("/dev/video0");
if (cam_fd < 0) {
fprintf(stderr, "init_camera 失败!\n");
return -1;
}
// 2. 申请并映射缓冲区
if (init_mmap(cam_fd) < 0) {
fprintf(stderr, "init_mmap 失败!\n");
return -1;
}
// 3. 开始采集
if (start_capturing(cam_fd) < 0) {
fprintf(stderr, "start_capturing 失败!\n");
return -1;
}
// 4. 用 select 等待数据,超时 20 秒
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
fd_set fds;
FD_ZERO(&fds);
FD_SET(cam_fd, &fds);
printf("等待摄像头数据...(超时 20 秒)\n");
int r = select(cam_fd + 1, &fds, NULL, NULL, &tv);
if (r == 0) {
printf("超时:20 秒内没有数据到来\n");
return 0;
} else if (r < 0) {
perror("select 出错");
return -1;
}
// 5. 数据就绪,读取一帧
if (FD_ISSET(cam_fd, &fds)) {
printf("数据就绪,正在读取帧...\n");
if (read_frame(cam_fd) < 0) {
fprintf(stderr, "read_frame 失败!\n");
return -1;
}
printf("成功!已生成以下文件:\n");
printf(" picture.yuv --- 原始 YUYV 数据\n");
printf(" picture.rgb --- 转换后的 RGB 数据\n");
printf(" picture.jpg --- 压缩后的 JPEG 图片\n");
}
return 0;
}
3.2.4 Makefile 与编译说明
makefile
# 交叉编译工具链(ARM 平台示例)
CROSS_COMPILE = arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
# 目标平台编译(需要 libjpeg 库)
CFLAGS = -I/opt/arm/libjpeg/result/include
LDFLAGS = -L/opt/arm/libjpeg/result/lib -ljpeg
# 本机编译测试(Ubuntu x86)
# CFLAGS = $(shell pkg-config --cflags libjpeg)
# LDFLAGS = $(shell pkg-config --libs libjpeg)
TARGET = camera_capture
OBJS = main.o camera.o
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(TARGET) $(OBJS) *.yuv *.rgb *.jpg
.PHONY: all clean
编译步骤:
bash
# 1. 安装 libjpeg 开发库(Ubuntu x86 本机测试)
sudo apt-get install libjpeg-dev
# 2. 编译
make
# 3. 运行(需要摄像头设备)
sudo ./camera_capture
# 4. 查看结果
eog picture.jpg
注意:ARM 交叉编译需要先交叉编译 libjpeg 库(参考 2.3.4a 节),然后将生成的可执行文件拷贝到开发板上运行。
附录
A. 常见报错与解决
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
open dev failure! |
摄像头设备不存在或权限不足 | 检查 /dev/video* 是否存在,用 sudo 运行 |
VIDIOC_QUERYCAP failure! |
该设备不是 V4L2 设备 | 确认设备路径是否正确 |
camera can't CAPTURE! |
设备不支持视频捕获 | 换一个支持拍照的摄像头 |
VIDIOC_S_FMT failure! |
设置格式失败 | 先用 v4l2-ctl --list-devices 确认支持的格式 |
| cheese 窗口黑屏 | 虚拟机 USB 兼容性问题 | 调整 USB 控制器版本(2.0/3.0) |
| JPEG 图片花屏 | RGB 数据排列错误或计算公式不对 | 检查 YUYV 字节序和 BT.601/709 标准选择 |
B. 调试命令速查
bash
# 查看设备列表
v4l2-ctl --list-devices
# 查看摄像头所有参数
v4l2-ctl -d /dev/video0 --all
# 列出支持的格式
v4l2-ctl -d /dev/video0 --list-formats
# 查看当前格式
v4l2-ctl -d /dev/video0 --get-fmt-video
# 抓取一帧原始数据
v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,height=480,pixelformat=YUYV \
--stream-mmap --stream-to=frame.yuv --stream-count=1
# 查看图片
eog picture.jpg
C. 关键概念速查表
| 概念 | 一句话总结 |
|---|---|
| V4L2 | Linux 内核自带的视频设备 API,让开发者用一套函数操作不同品牌的摄像头 |
| ioctl | 万能控制函数,V4L2 的所有操作都通过它完成 |
| mmap | 在用户空间和内核空间之间开一扇门,避免数据拷贝,提升效率 |
| select | 用来等数据的函数,设好超时时间,有数据来或超时都会返回 |
| YUV | 把颜色拆成"亮度(Y)"和"色度(UV)",人眼对亮度敏感对色彩不敏感 |
| YUV 4:2:2 | 每 2 个像素共用一组 UV,节省 33% 空间 |
| YUV 4:2:0 | 每 4 个像素共用一组 UV,节省 50% 空间,视频通话最常用 |
| Planar 存储 | 所有 Y 挨着存,所有 U 挨着存,所有 V 挨着存 |
| Packed 存储 | 每个像素的 YUV 挨着存,再存下一个像素 |
| BT.601 | 标清电视时代的 YUV→RGB 转换标准,嵌入式摄像头最常用 |
| JPEG | 有损压缩格式,允许用户调整质量(1~100),文件小但会丢失细节 |
| libjpeg | 一个 C 语言库,接收 RGB 数据,输出 JPEG 文件 |
D. 知识体系脑图
#mermaid-svg-nggNHiYj9jijV0cX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nggNHiYj9jijV0cX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nggNHiYj9jijV0cX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nggNHiYj9jijV0cX .error-icon{fill:#552222;}#mermaid-svg-nggNHiYj9jijV0cX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nggNHiYj9jijV0cX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nggNHiYj9jijV0cX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nggNHiYj9jijV0cX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nggNHiYj9jijV0cX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nggNHiYj9jijV0cX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nggNHiYj9jijV0cX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nggNHiYj9jijV0cX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nggNHiYj9jijV0cX .marker.cross{stroke:#333333;}#mermaid-svg-nggNHiYj9jijV0cX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nggNHiYj9jijV0cX p{margin:0;}#mermaid-svg-nggNHiYj9jijV0cX .edge{stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .section--1 rect,#mermaid-svg-nggNHiYj9jijV0cX .section--1 path,#mermaid-svg-nggNHiYj9jijV0cX .section--1 circle,#mermaid-svg-nggNHiYj9jijV0cX .section--1 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section--1 text{fill:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth--1{stroke-width:17;}#mermaid-svg-nggNHiYj9jijV0cX .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-0 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-0 path,#mermaid-svg-nggNHiYj9jijV0cX .section-0 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-0 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-nggNHiYj9jijV0cX .section-0 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-0{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-0{stroke-width:14;}#mermaid-svg-nggNHiYj9jijV0cX .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-1 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-1 path,#mermaid-svg-nggNHiYj9jijV0cX .section-1 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-1 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-1 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-1{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-1{stroke-width:11;}#mermaid-svg-nggNHiYj9jijV0cX .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-2 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-2 path,#mermaid-svg-nggNHiYj9jijV0cX .section-2 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-2 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-2 text{fill:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-2{stroke-width:8;}#mermaid-svg-nggNHiYj9jijV0cX .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-3 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-3 path,#mermaid-svg-nggNHiYj9jijV0cX .section-3 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-3 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-3 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-3{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-3{stroke-width:5;}#mermaid-svg-nggNHiYj9jijV0cX .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-4 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-4 path,#mermaid-svg-nggNHiYj9jijV0cX .section-4 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-4 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-4 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-4{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-4{stroke-width:2;}#mermaid-svg-nggNHiYj9jijV0cX .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-5 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-5 path,#mermaid-svg-nggNHiYj9jijV0cX .section-5 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-5 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-5 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-5{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-5{stroke-width:-1;}#mermaid-svg-nggNHiYj9jijV0cX .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-6 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-6 path,#mermaid-svg-nggNHiYj9jijV0cX .section-6 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-6 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-6 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-6{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-6{stroke-width:-4;}#mermaid-svg-nggNHiYj9jijV0cX .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-7 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-7 path,#mermaid-svg-nggNHiYj9jijV0cX .section-7 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-7 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-7 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-7{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-7{stroke-width:-7;}#mermaid-svg-nggNHiYj9jijV0cX .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-8 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-8 path,#mermaid-svg-nggNHiYj9jijV0cX .section-8 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-8 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-8 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-8{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-8{stroke-width:-10;}#mermaid-svg-nggNHiYj9jijV0cX .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-9 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-9 path,#mermaid-svg-nggNHiYj9jijV0cX .section-9 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-9 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-9 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-9{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-9{stroke-width:-13;}#mermaid-svg-nggNHiYj9jijV0cX .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-10 rect,#mermaid-svg-nggNHiYj9jijV0cX .section-10 path,#mermaid-svg-nggNHiYj9jijV0cX .section-10 circle,#mermaid-svg-nggNHiYj9jijV0cX .section-10 polygon,#mermaid-svg-nggNHiYj9jijV0cX .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-10 text{fill:black;}#mermaid-svg-nggNHiYj9jijV0cX .node-icon-10{font-size:40px;color:black;}#mermaid-svg-nggNHiYj9jijV0cX .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .edge-depth-10{stroke-width:-16;}#mermaid-svg-nggNHiYj9jijV0cX .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-nggNHiYj9jijV0cX .disabled,#mermaid-svg-nggNHiYj9jijV0cX .disabled circle,#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:lightgray;}#mermaid-svg-nggNHiYj9jijV0cX .disabled text{fill:#efefef;}#mermaid-svg-nggNHiYj9jijV0cX .section-root rect,#mermaid-svg-nggNHiYj9jijV0cX .section-root path,#mermaid-svg-nggNHiYj9jijV0cX .section-root circle,#mermaid-svg-nggNHiYj9jijV0cX .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-nggNHiYj9jijV0cX .section-root text{fill:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .section-root span{color:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .section-2 span{color:#ffffff;}#mermaid-svg-nggNHiYj9jijV0cX .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-nggNHiYj9jijV0cX .edge{fill:none;}#mermaid-svg-nggNHiYj9jijV0cX .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-nggNHiYj9jijV0cX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 嵌入式摄像头
采集与处理
采集层
硬件设备
USB 摄像头
CSI 摄像头
HDMI 采集卡
工具
cheese 测试
v4l2-ctl 命令行
参数
分辨率 Width x Height
帧率 FPS
颜色空间 Colorspace
接口类型
V4L2 驱动层
核心概念
一切皆文件 /dev/video
ioctl 万能控制
文件描述符 fd
关键操作
QUERYCAP 查能力
ENUM_FMT 枚举格式
S_FMT 设置格式
REQBUFS 申请缓冲区
QUERYBUF 查询缓冲区
QBUF / DQBUF 入队出队
STREAMON / OFF 开关流
内存管理
mmap 零拷贝映射
select 等待机制
颜色空间
RGB vs YUV
数据量差异
人眼敏感度差异
摄像头兼容性
采样方式
4:4:4 无损
4:2:2 省33%
4:2:0 省50% 最常用
存储方式
Planar 平面存储
Packed 打包存储
转换标准
BT.601 标清
BT.709 高清
BT.2020 超高清
压缩输出
JPEG 格式
有损压缩
质量可调 1-100
DCT + 量化 + Huffman
libjpeg 库
初始化压缩对象
设置参数
逐行写入
完成压缩
代码工程
camera.h 头文件
camera.c 驱动实现
main.c 主程序入口
最终生成文件 :
picture.yuv(原始数据)、picture.rgb(RGB 数据)、picture.jpg(JPEG 图片)。