做机器视觉项目,第一步就是和工业相机打交道。面对 SDK 里几十种 PixelFormat 选项——Mono8、BayerRG8、YUV422Packed、RGB8Packed……你知道它们的区别、性能差异和适用场景吗?本文从传感器原理出发,系统梳理工业相机的常见图像格式,帮你做出正确的选型决策。


一、工业相机成像基础

1.1 传感器类型决定原始格式

工业相机的传感器(CCD/CMOS)分为两大类:

传感器类型输出格式典型应用
黑白传感器Mono(灰度)缺陷检测、测量、OCR
彩色传感器(Bayer滤镜)Bayer Raw颜色识别、分拣、外观检测

黑白传感器:每个像素直接输出光强值,无颜色信息,信噪比高。

彩色传感器:在每个像素上覆盖 Bayer 滤色片(红/绿/蓝),每个像素只感知一种颜色,需要插值算法(Demosaicing)还原全彩图像。

Bayer 滤色片排列示意(RGGB 模式):

  R  G  R  G  R  G        传感器每个像素只能"看到"一种颜色
  G  B  G  B  G  B        通过插值算法还原出完整的RGB图像
  R  G  R  G  R  G        绿色像素是红/蓝的两倍(人眼对绿色更敏感)
  G  B  G  B  G  B

1.2 位深度

工业相机的 ADC(模数转换器)决定了每个像素的位深度:

位深度灰度级数动态范围典型场景
8 bit256 级48 dB通用检测、分拣
10 bit1024 级60 dB半导体、精密测量
12 bit4096 级72 dB医学影像、HDR
14 bit16384 级84 dB科学成像、天文
16 bit65536 级96 dB光谱分析、极端动态范围

实际工程中 8bit 覆盖 80% 以上的场景。 高位深度意味着更大的数据量和更慢的传输速度,除非确实需要精细的灰度分辨力,否则 8bit 是最优选择。


二、灰度格式(Mono)

2.1 Mono8

像素排列:
┌────┬────┬────┬────┐
│ P0 │ P1 │ P2 │ P3 │    每个像素 = 1 字节 (0~255)
│ 8b │ 8b │ 8b │ 8b │    内存连续排列
└────┴────┴────┴────┘

数据量:Width × Height × 1 字节
示例:2048×1536 = 3,145,728 字节 ≈ 3.0 MB/帧

优势:

  • ✅ 数据量最小,传输带宽需求低
  • ✅ 每个像素都是有效感光面积,信噪比最高
  • ✅ 无需 Demosaicing,CPU 零开销
  • ✅ 直接对应 OpenCV 的 CV_8UC1,无需任何格式转换
  • ✅ 黑白传感器的空间分辨率是同尺寸彩色传感器的 2 倍以上

劣势:

  • ❌ 无颜色信息
  • ❌ 无法区分亮度相同但颜色不同的物体

适用场景:

  • 尺寸测量、定位对准
  • 表面缺陷检测(划痕、凹坑、异物)
  • OCR/条码/二维码识别
  • 高速运动物体检测(帧率优先)
  • 受电弓磨耗检测、接触网几何参数检测等铁路 4C/5C 应用

OpenCV 读取:

// Mono8 数据可以直接包装为 cv::Mat
cv::Mat image(height, width, CV_8UC1, buffer);

2.2 Mono10 / Mono12

Mono10(非Packed)像素排列:
┌──────────┬──────────┬──────────┐
│    P0    │    P1    │    P2    │   每个像素 = 2 字节 (16 bit 存储)
│  10 bit  │  10 bit  │  10 bit  │   高 6 bit 填零
│ 有效+补0  │ 有效+补0  │ 有效+补0  │
└──────────┴──────────┴──────────┘

数据量:Width × Height × 2 字节
示例:2048×1536 = 6,291,456 字节 ≈ 6.0 MB/帧(是 Mono8 的 2 倍)

优势:

  • ✅ 更高灰度分辨力(1024/4096 级 vs 256 级)
  • ✅ 适合高动态范围场景(同时看到亮区和暗区的细节)
  • ✅ 后期可以通过查找表(LUT)灵活映射到 8bit

劣势:

  • ❌ 数据量翻倍,对传输带宽要求高
  • ❌ 多数深度学习模型只接受 8bit 输入,需要额外的位深度转换
  • ❌ 显示时仍然要映射到 8bit,视觉上差异不一定明显

适用场景:

  • 半导体晶圆检测(需要区分极微小的灰度差异)
  • 医学 X 光成像
  • 焊缝质量检测(金属高反光 + 暗区共存)

位深度转换:

// Mono10 → Mono8(右移 2 位)
cv::Mat mono10(height, width, CV_16UC1, buffer);
cv::Mat mono8;
mono10.convertTo(mono8, CV_8UC1, 1.0 / 4.0);  // 10bit → 8bit: 除以4

// Mono12 → Mono8(右移 4 位)
mono12.convertTo(mono8, CV_8UC1, 1.0 / 16.0);

2.3 Mono10Packed / Mono12Packed

为了减少高位深度的传输带宽,相机厂商定义了 Packed 格式:

Mono12Packed 排列(以 2 像素为一组):

  字节0      字节1      字节2
┌────────┐┌────────┐┌────────┐
│ P0 高8b ││P1低4|P0低4││ P1 高8b │
└────────┘└────────┘└────────┘

2 个 12bit 像素 = 3 字节(而非 4 字节)
节省 25% 带宽

优势:

  • ✅ 相比非 Packed 格式,带宽节省 20%~25%
  • ✅ 保留完整的高位深度信息

劣势:

  • ❌ CPU 解包开销大(位操作 + 移位),影响实时性
  • ❌ 无法直接映射为 OpenCV Mat,需要自己写解包代码
  • ❌ 不同厂商的 Packed 格式可能不完全兼容(GigE Vision 标准 vs 厂商私有)

适用场景:

  • 带宽受限但需要高位深度的 GigE 相机场景
  • 离线分析(不需要实时处理)

解包示例(Mono12Packed → Mono16):

void unpackMono12Packed(const uint8_t* src, uint16_t* dst, int pixelCount) {
    for (int i = 0; i < pixelCount; i += 2) {
        // 像素0:高8bit在byte0,低4bit在byte1的低4位
        dst[i]     = (static_cast<uint16_t>(src[0]) << 4) | (src[1] & 0x0F);
        // 像素1:高8bit在byte2,低4bit在byte1的高4位
        dst[i + 1] = (static_cast<uint16_t>(src[2]) << 4) | ((src[1] >> 4) & 0x0F);
        src += 3;
    }
}

三、Bayer 格式(彩色传感器原始输出)

3.1 BayerXX8(BayerRG8 / BayerGR8 / BayerGB8 / BayerBG8)

Bayer 格式是彩色 CMOS 传感器的原始输出,每个像素只有一个颜色通道的值:

BayerRG8 排列:                BayerGR8 排列:
┌───┬───┬───┬───┐             ┌───┬───┬───┬───┐
│ R │ G │ R │ G │             │ G │ R │ G │ R │
├───┼───┼───┼───┤             ├───┼───┼───┼───┤
│ G │ B │ G │ B │             │ B │ G │ B │ G │
├───┼───┼───┼───┤             ├───┼───┼───┼───┤
│ R │ G │ R │ G │             │ G │ R │ G │ R │
└───┴───┴───┴───┘             └───┴───┴───┴───┘

每个像素 = 1 字节,数据量 = Mono8 相同
但携带颜色信息(需要 Demosaicing 还原)

四种命名方式对应左上角 2×2 块的排列顺序:
- BayerRG: 左上=R, 右上=G, 左下=G, 右下=B
- BayerGR: 左上=G, 右上=R, 左下=B, 右下=G
- BayerGB: 左上=G, 右上=B, 左下=R, 右下=G
- BayerBG: 左上=B, 右上=G, 左下=G, 右下=R

优势:

  • ✅ 数据量 = Mono8,仅为 RGB8 的 1/3,传输带宽需求最低
  • ✅ 保留传感器原始信息,后期可选择不同的 Demosaicing 算法(质量/速度权衡)
  • ✅ 在相机端完成传输,Demosaicing 在主机端用 CPU/GPU 完成(灵活分配算力)
  • ✅ GigE/USB3 带宽有限时,Bayer 是获取彩色图像的最佳选择

劣势:

  • ❌ 需要 Demosaicing(去马赛克)处理,有 CPU 开销
  • ❌ Demosaicing 会引入插值伪影(特别是边缘区域,出现"拉链效应")
  • ❌ 实际颜色分辨率低于标称分辨率(每个像素只有 1/3 的颜色信息)
  • ❌ 不同 Bayer 模式需要对应不同的转换代码,选错模式会导致颜色严重偏差

适用场景:

  • GigE 相机传输彩色图像(带宽有限,Bayer 是标准选择)
  • 需要同时兼顾帧率和颜色的场景
  • 相机端算力有限,把 Demosaicing 交给主机 GPU 的场景

OpenCV Demosaicing:

cv::Mat bayer(height, width, CV_8UC1, buffer);
cv::Mat bgr;

// 根据相机的 Bayer 模式选择对应的转换码(选错会色偏!)
cv::cvtColor(bayer, bgr, cv::COLOR_BayerRG2BGR);   // BayerRG → BGR
cv::cvtColor(bayer, bgr, cv::COLOR_BayerGR2BGR);   // BayerGR → BGR
cv::cvtColor(bayer, bgr, cv::COLOR_BayerGB2BGR);   // BayerGB → BGR
cv::cvtColor(bayer, bgr, cv::COLOR_BayerBG2BGR);   // BayerBG → BGR

// GPU加速版本(OpenCV CUDA)
cv::cuda::GpuMat d_bayer(height, width, CV_8UC1, d_buffer);
cv::cuda::GpuMat d_bgr;
cv::cuda::demosaicing(d_bayer, d_bgr, cv::cuda::COLOR_BayerRG2BGR_MHT);
// MHT = Malvar-He-Cutler算法,质量比双线性插值更好

3.2 BayerXX10 / BayerXX12

高位深度的 Bayer 格式,常见于高端工业相机。

数据量对比(2048×1536 分辨率):
- BayerRG8:     3.0 MB/帧
- BayerRG10:    6.0 MB/帧(2字节/像素存储)
- BayerRG12:    6.0 MB/帧(2字节/像素存储)
- BayerRG12Packed: 4.5 MB/帧

适用场景:

  • 需要高颜色精度的彩色检测(如宝石分级、医药包装颜色检测)
  • 同时需要颜色和高动态范围的场景

四、RGB / BGR 格式

4.1 RGB8 / BGR8(又称 RGB8Packed)

RGB8 像素排列:
┌─────────────┬─────────────┬─────────────┐
│  Pixel 0    │  Pixel 1    │  Pixel 2    │
│ R │ G │ B   │ R │ G │ B   │ R │ G │ B   │
│ 8b│ 8b│ 8b  │ 8b│ 8b│ 8b  │ 8b│ 8b│ 8b  │
└─────────────┴─────────────┴─────────────┘

数据量:Width × Height × 3 字节
示例:2048×1536 = 9,437,184 字节 ≈ 9.0 MB/帧(是 Mono8 的 3 倍)

优势:

  • ✅ 相机端已完成 Demosaicing,主机无需额外处理
  • ✅ 直接对应 OpenCV 的 CV_8UC3,即拿即用
  • ✅ 颜色准确,无 Demosaicing 伪影
  • ✅ 开发调试方便,图像所见即所得

劣势:

  • ❌ 数据量是 Bayer 的 3 倍,严重消耗传输带宽
  • ❌ GigE 相机帧率会降至 Bayer 模式的约 1/3
  • ❌ 相机端 Demosaicing 质量可能不如主机端高级算法
  • ❌ 占用相机端 FPGA/ISP 资源,可能影响其他功能

适用场景:

  • USB3 Vision 相机(带宽充足,5 Gbps)
  • 对帧率要求不高但对开发效率要求高的场景
  • 快速原型验证
  • CoaXPress/Camera Link 等高带宽接口

注意 RGB 与 BGR 的区别:

// 工业相机通常输出 RGB 顺序
// OpenCV 内部使用 BGR 顺序
// 如果不转换,红色和蓝色会互换!

cv::Mat rgb_image(height, width, CV_8UC3, buffer);  // 相机输出的RGB数据
cv::Mat bgr_image;
cv::cvtColor(rgb_image, bgr_image, cv::COLOR_RGB2BGR);  // 转为OpenCV的BGR

// 或者,某些相机SDK可以直接配置输出BGR,省去这一步

4.2 RGBa8 / BGRa8(带 Alpha 通道)

RGBa8 像素排列:
┌──────────────────┬──────────────────┐
│    Pixel 0       │    Pixel 1       │
│ R │ G │ B │ A    │ R │ G │ B │ A    │
│ 8b│ 8b│ 8b│ 8b   │ 8b│ 8b│ 8b│ 8b   │
└──────────────────┴──────────────────┘

数据量:Width × Height × 4 字节

优势:

  • ✅ 4 字节对齐,GPU 处理效率更高(CUDA 天然支持 uchar4,内存合并访问)
  • ✅ Alpha 通道可用于 ROI 掩码

劣势:

  • ❌ 数据量比 RGB8 多 33%,Alpha 通道通常是浪费的
  • ❌ 传输带宽进一步增大

适用场景:

  • GPU 处理 Pipeline(CUDA kernel 处理 4 字节对齐的数据更快)
  • 需要透明度/掩码信息的合成场景

五、YUV 格式

5.1 YUV 色彩空间

YUV 将图像分为亮度(Y)和色度(U/V)两部分:

Y  = 亮度分量(Luminance),人眼最敏感
U  = 蓝色色度(Cb),即 B-Y 的缩放版本
V  = 红色色度(Cr),即 R-Y 的缩放版本

核心思想: 人眼对亮度远比对颜色敏感,因此可以用全分辨率传输亮度,用较低分辨率传输色度,在视觉质量几乎无损的情况下大幅减少数据量。

5.2 YUV422(又称 YCbCr422、YUYV)

YUV422 像素排列(每 2 个像素共享一组 UV):
┌──────────────────────────────┐
│ Y0 │ U0 │ Y1 │ V0 │ Y2 │ U1 │ Y3 │ V1 │ ...
└──────────────────────────────┘

Pixel 0 = (Y0, U0, V0)
Pixel 1 = (Y1, U0, V0)  ← 与 Pixel 0 共享 U0, V0
Pixel 2 = (Y2, U1, V1)
Pixel 3 = (Y3, U1, V1)  ← 与 Pixel 2 共享 U1, V1

数据量:Width × Height × 2 字节(RGB的 2/3)
示例:2048×1536 = 6,291,456 字节 ≈ 6.0 MB/帧

优势:

  • ✅ 数据量是 RGB 的 2/3,显著节省带宽
  • ✅ 亮度分量(Y)保持全分辨率,检测精度影响小
  • ✅ 视觉质量接近 RGB(人眼对色度不敏感)
  • ✅ 硬件编解码友好(H.264/H.265 编码器原生支持 YUV)
  • ✅ Y 分量可直接当作灰度图使用,无需转换

劣势:

  • ❌ 色度水平分辨率减半,对精确颜色边缘检测有影响
  • ❌ 转换为 RGB 需要矩阵运算,有 CPU 开销
  • ❌ 排列方式多样(YUYV、UYVY、NV12...),容易搞混

适用场景:

  • 视频流传输(监控、视频会议)
  • 带宽受限但需要颜色信息的 GigE 相机
  • 后接视频编码器的场景(省去 RGB→YUV 转换)
  • 只需要亮度信息时直接提取 Y 分量

OpenCV 转换:

// YUV422 (YUYV) → BGR
cv::Mat yuv(height, width, CV_8UC2, buffer);  // 注意是 2 通道
cv::Mat bgr;
cv::cvtColor(yuv, bgr, cv::COLOR_YUV2BGR_YUYV);

// 只提取Y分量(等效灰度图,零开销)
cv::Mat y_channel(height, width, CV_8UC1);
for (int i = 0; i < height * width; ++i) {
    y_channel.data[i] = buffer[i * 2];  // Y在偶数字节位置
}

5.3 YUV411

YUV411(每 4 个像素共享一组 UV):
┌──────────────────────────────────────┐
│ U0 │ Y0 │ Y1 │ V0 │ Y2 │ Y3 │ ...
└──────────────────────────────────────┘

Pixel 0~3 共享同一组 (U0, V0)

数据量:Width × Height × 1.5 字节(RGB的 1/2)

优势:

  • ✅ 数据量只有 RGB 的一半

劣势:

  • ❌ 色度水平分辨率只有亮度的 1/4,颜色严重劣化
  • ❌ 实际使用较少,多数图像处理库支持不完善

适用场景:

  • 极端带宽受限 + 颜色不重要的场景(基本已被 YUV422 取代)

六、各格式数据量与带宽对比

以 2048×1536 分辨率 为例:

格式每像素字节数单帧大小30fps 带宽需求相对于 RGB8
Mono81.03.0 MB90 MB/s33%
Mono10Packed1.253.75 MB112 MB/s42%
Mono102.06.0 MB180 MB/s67%
Mono122.06.0 MB180 MB/s67%
BayerRG81.03.0 MB90 MB/s33%
BayerRG12Packed1.54.5 MB135 MB/s50%
YUV4111.54.5 MB135 MB/s50%
YUV4222.06.0 MB180 MB/s67%
RGB83.09.0 MB270 MB/s100%
RGBa84.012.0 MB360 MB/s133%

接口带宽上限参考:

接口类型理论带宽实际可用带宽适合的格式
GigE (1 Gbps)125 MB/s~110 MB/sMono8、BayerXX8
USB3 Vision (5 Gbps)625 MB/s~400 MB/sRGB8、YUV422
10GigE (10 Gbps)1250 MB/s~1100 MB/s所有格式
CoaXPress (6.25 Gbps×4)3125 MB/s~2500 MB/s高分辨率+高帧率
Camera Link HS可达 16 Gbps+~12 Gbps+线扫相机

实战经验:GigE 相机传彩色图优先选 BayerXX8(3.0 MB/帧),而不是 RGB8(9.0 MB/帧)。后者在千兆网下 30fps 就要 270 MB/s,超出接口上限。


七、深度学习部署中的格式选择

在 YOLO/分类等深度学习推理 Pipeline 中,模型输入通常是:

float32, RGB, CHW, 归一化到 [0, 1]

不同相机格式到模型输入的转换路径和开销差异巨大:

7.1 转换路径对比

Mono8 ──────→ 复制到3通道 ──→ float32 ──→ /255 ──→ CHW    [最短路径]
                  ↑ 简单复制

BayerRG8 ───→ Demosaicing ──→ BGR ──→ RGB ──→ float32 ──→ /255 ──→ CHW
                  ↑ CPU密集型

YUV422 ─────→ YUV→BGR ──→ RGB ──→ float32 ──→ /255 ──→ CHW
                ↑ 矩阵运算

RGB8 ───────→ RGB→BGR(OpenCV) 或直接使用 ──→ float32 ──→ /255 ──→ CHW
                  ↑ 可能不需要

BGR8 ───────→ float32 ──→ /255 ──→ BGR→RGB ──→ CHW    [次短路径]

7.2 GPU Pipeline 中的最佳实践

最优路径(GPU全流程):

相机 → Mono8(GPU) → NPP: Dup_C1C3 → Resize → Padding → /255 → CHW → TensorRT
                         ↑ 全程GPU,零CPU拷贝

次优路径:
相机 → BayerRG8(GPU) → CUDA Demosaicing → Resize → Padding → /255 → BGR→RGB → CHW → TensorRT
                        ↑ GPU Demosaicing(如OpenCV cuda::demosaicing)

最差路径:
相机 → RGB8(CPU) → memcpy到GPU → Resize → Padding → /255 → CHW → TensorRT
        ↑ 3倍数据量 + CPU→GPU拷贝

推荐:如果场景允许用灰度,优先 Mono8;需要颜色,优先 Bayer + GPU Demosaicing。尽量避免在带宽受限的接口上使用 RGB8。


八、格式选型决策树

                        需要颜色信息吗?
                       /              \
                     否                是
                     |                  |
                 Mono8              带宽充足吗?
                (最佳性能)          /          \
                                 是            否
                                 |              |
                            RGB8/BGR8      BayerXX8
                          (最简单开发)    (1/3带宽)
                                            |
                                     Demosaicing
                                     在哪里做?
                                    /          \
                                 相机端       主机端
                                 (选RGB8)    (选Bayer)
                                              |
                                          有GPU吗?
                                         /        \
                                       是          否
                                       |            |
                                  GPU Demosaicing  CPU Demosaicing
                                  (cuda/NPP)      (OpenCV cvtColor)

                         需要高动态范围吗?
                        /                 \
                      否                   是
                      |                     |
                   8bit              10bit/12bit
                 (够用就好)          (Packed节省带宽)

九、常见踩坑指南

9.1 Bayer 模式选错

症状: 图像颜色严重偏移,红蓝互换,或出现棋盘格伪影。

原因: 相机实际输出 BayerRG8,代码用了 COLOR_BayerBG2BGR。

排查方法:

// 打印相机SDK返回的PixelFormat,确认是哪种Bayer模式
std::cout << "Camera PixelFormat: " << camera.getPixelFormat() << std::endl;
// 然后使用对应的转换码

9.2 RGB/BGR 搞反

症状: 图像颜色偏蓝或偏红(红蓝互换)。

原因: 工业相机输出 RGB 顺序,直接当 BGR 塞给 OpenCV。

修复:

cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
// 或者直接在相机 SDK 里设置输出 BGR 顺序

9.3 Packed 格式处理错误

症状: 图像出现规律性的亮暗条纹或错位。

原因: 把 Mono12Packed(1.5 字节/像素)当作 Mono16(2 字节/像素)来读。

修复: 先用正确的解包函数转为标准位深度格式,再处理。

9.4 步长(Stride)问题

症状: 图像倾斜或出现锯齿状错位。

原因: 相机返回的图像行步长(Stride)不等于 Width × BytesPerPixel,可能有对齐填充。

修复:

// 使用相机SDK返回的 stride,而不是自己计算
int stride = camera.getPayloadStride();  // 可能比 width*bpp 更大
cv::Mat image(height, width, CV_8UC1, buffer, stride);  // 第5个参数指定步长

9.5 GigE 丢包导致图像花屏

症状: 图像出现水平方向的彩色噪点条纹。

原因: 千兆网传输 RGB8 帧率过高,网卡缓冲区溢出丢包。

修复:

  • 改用 BayerXX8 降低带宽
  • 增大网卡接收缓冲区(Jumbo Frame = 9000)
  • 使用万兆网卡

十、总结速查表

我的场景推荐格式理由
尺寸测量/定位Mono8高分辨率、高帧率、高信噪比
表面缺陷检测Mono8 或 Mono12灰度检测为主,高位深看暗部细节
颜色分拣(GigE)BayerXX8带宽友好,主机端 Demosaicing
颜色分拣(USB3)RGB8带宽充足,开发简单
视频监控/录制YUV422编码器原生支持,带宽适中
GPU 深度学习推理Mono8 > BayerXX8 > BGR8数据量从小到大
半导体/医学成像Mono12 / BayerXX12高动态范围必需
高速运动捕捉Mono8最大帧率
3D 结构光Mono8 + 外部触发灰度足够,帧率优先

一句话选型原则:能用灰度就不用彩色,能用 Bayer 就不用 RGB,能用 8bit 就不用高位深。每多一份数据量,都会在传输、存储、处理三个环节付出代价。


技术栈参考: GigE Vision / USB3 Vision / NVIDIA NPP / OpenCV 4.x / TensorRT / CUDA

主流工业相机品牌: Basler (Pylon SDK) / HIKROBOT (MVS SDK) / Daheng (Galaxy SDK) / FLIR (Spinnaker SDK) / Baumer / IDS

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐