OpenCV 核心功能:cv::Mat 内存布局、BGR 通道序与处理链
核心功能:cv::Mat 内存布局、BGR 通道序与处理链
cv::Mat 的结构必须理解清楚,否则后面所有内存相关的 bug 都会以奇怪的方式出现。Mat 本身只有几十字节的头部,包含行数 rows、列数 cols、元素类型 type()、指向像素数据的指针 data、行步长 step(单位是字节)以及一个指向引用计数的指针。像素数据是单独分配的。
由此推出三条关键规则:
- 拷贝是浅拷贝。
Mat b = a;或Mat b(a);只复制头部并让引用计数加一,两者共享同一块像素数据,改一个另一个也跟着变。需要独立数据必须写Mat b = a.clone();或a.copyTo(b);。 - ROI 也是共享的。
Mat roi = img(cv::Rect(x, y, w, h));只是头部指向原图的一块区域,写roi会直接改到原图,且不会做边界检查。 - 访问像素不能假设行连续。只有当
mat.isContinuous()为真时才有step == cols * elemSize();从原图裁出来的 ROI 通常不连续,按data[i * cols + j]这种写法算地址就会错行。正确写法是按行取指针:uchar *row = mat.ptr<uchar>(y);。
通道序是第二个高频陷阱。OpenCV 的彩色图像默认是 BGR 排列,而绝大多数深度学习模型在训练时用的是 RGB,Python 生态里的 PIL、matplotlib 也按 RGB 理解。所以「读图 → 送模型」这条路上,要么用 cvtColor(img, rgb, COLOR_BGR2RGB) 显式转换,要么在 blobFromImage 里把 swapRB 设为 true,两者取其一,绝不能都做或都不做。
第三个要点是处理链的顺序。blobFromImage 做的事依次是:按 size 缩放、按 swapRB 决定是否交换红蓝通道、减去 mean、乘以 scalefactor、最后把 HWC 布局转成 CHW 并加一维 batch。这里的 mean 与 scalefactor 必须与训练时的预处理完全一致,包括 mean 是按 BGR 顺序还是 RGB 顺序给出的,这也是「模型换到端侧精度掉一半」的最常见原因。
常用接口速查:
| 用途 | C++ 接口 | Python 接口 |
|---|---|---|
| 读图(默认 BGR) | cv::imread(path, cv::IMREAD_COLOR) | cv2.imread(path, cv2.IMREAD_COLOR) |
| 颜色空间转换 | cv::cvtColor(src, dst, cv::COLOR_BGR2RGB) | cv2.cvtColor(src, cv2.COLOR_BGR2RGB) |
| 缩放 | cv::resize(src, dst, size, 0, 0, cv::INTER_LINEAR) | cv2.resize(src, size, interpolation=cv2.INTER_LINEAR) |
| 深拷贝 | src.clone() / src.copyTo(dst) | src.copy() |
| 构造模型输入 | cv::dnn::blobFromImage(...) | cv2.dnn.blobFromImage(...) |
| 加载 ONNX | cv::dnn::readNetFromONNX(path) | cv2.dnn.readNetFromONNX(path) |
| 推理 | net.setInput(blob); net.forward() | net.setInput(blob); net.forward() |
| 重叠框抑制 | cv::dnn::NMSBoxes(...) | cv2.dnn.NMSBoxes(...) |
需要强调一点:OpenCV 的 dnn 模块是推理运行时,不是训练框架。它加载的是已经训练好的模型(ONNX 最常见),负责前向计算。模型算子支持范围随版本变化,遇到不支持的算子需要换导出版本或调整模型结构,这一点务必先查官方文档再动手。

