OpenCV 30 讲学习总结:从零基础到机器人视觉

本文系统梳理 OpenCV C++ 课程的核心知识点,涵盖色彩空间转换、Mat 对象、像素操作、归一化、图像缩放与插值、翻转、视频读写、卷积、高斯模糊及人脸检测。所有代码均来自实际项目 QuickDemo 类,已在 Visual Studio + OpenCV 4.4+ 环境下验证通过,并附常见编译与运行错误解决方案。


一、课程内容筛选(按机器人视觉需求)

根据机器人 + YOLO 的目标,将 30 讲分为三类:

类型 章节 说明
精学 图像读写、色彩空间转换、Mat 对象、归一化、图像缩放、摄像头读取、卷积 高频操作,必须熟练掌握
过一遍 像素遍历、通道分离、像素统计、翻转、旋转、视频保存、高斯模糊、人脸检测 理解流程,用时能查
跳过 滚动条、键盘鼠标交互、绘图、直方图等 与机器人感知关系不大

二、图像窗口与显示基础

  • namedWindow("win", WINDOW_FREERATIO) 允许自由调整窗口比例。
  • waitKey(0) 无限等待按键;waitKey(30) 等待 30 毫秒,常用于视频循环。
  • 图像深度:24 位(3 通道 × 8 位/通道),像素值范围 0~255。
cpp 复制代码
// 对应 QuickDemo 中 colorSpace_Demo 等函数的开头
Mat src = imread("D:/test.jpg", IMREAD_COLOR);
if (src.empty()) return -1;
namedWindow("输入", WINDOW_FREERATIO);
imshow("输入", src);
waitKey(0);
destroyAllWindows();

三、Mat 对象创建与赋值

  • Mat 包含头部(尺寸、类型、通道)、数据指针、引用计数。
  • 深拷贝:clone() 或 copyTo(),独立内存。
  • 浅拷贝:直接赋值,共享数据。
cpp 复制代码
// 来自 QuickDemo::mat_creation_demo
Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3);
m3 = Scalar(0, 0, 255);          // 红色
cout << "width: " << m3.cols << " height: " << m3.rows << " channels: " << m3.channels() << endl;

Mat m4;
m3.copyTo(m4);
m4 = Scalar(0, 255, 255);        // 黄色,不影响 m3
imshow("图像", m3);
imshow("图像4", m4);

四、像素读写操作

  • 灰度图:image.at<uchar>(row, col)
  • 彩色图:image.at<Vec3b>(row, col)[0](0=B,1=G,2=R)
  • 指针遍历效率更高(参考 pixel_visit_demo):
cpp 复制代码
// 来自 QuickDemo::pixel_visit_demo(指针版本)
int w = image.cols, h = image.rows, dims = image.channels();
for (int row = 0; row < h; row++) {
    uchar* current_row = image.ptr<uchar>(row);
    for (int col = 0; col < w; col++) {
        if (dims == 1) { // 灰度图
            int pv = *current_row;
            *current_row++ = 255 - pv;
        }
        if (dims == 3) { // 彩色图
            *current_row++ = 255 - *current_row;
            *current_row++ = 255 - *current_row;
            *current_row++ = 255 - *current_row;
        }
    }
}
imshow("像素读写演示", image);

五、通道分离与合并

cpp 复制代码
// 来自 QuickDemo::channels_demo
vector<Mat> mv;
split(image, mv);
imshow("蓝色", mv[0]);
imshow("绿色", mv[1]);
imshow("红色", mv[2]);

Mat dst;
mv[0] = 0;
// mv[1] = 0;
merge(mv, dst);
imshow("蓝色通道置零", dst);

int from_to[] = { 0, 2, 1, 1, 2, 0 };
mixChannels(&image, 1, &dst, 1, from_to, 3);
imshow("通道重映射", dst);

六、色彩空间转换(重点,位于归一化之前)

转换函数 cvtColor(src, dst, code),常用常量值:

常量 数值 说明
COLOR_BGR2GRAY 6 彩色 → 灰度
COLOR_GRAY2BGR 8 灰度 → 伪彩色(无法恢复真彩色)
COLOR_BGR2HSV 40 BGR → HSV
COLOR_HSV2BGR 54 HSV → BGR
COLOR_BGR2RGB 4 通道交换

HSV 范围(OpenCV 特有)

  • H: 0 ~ 180(色调)
  • S: 0 ~ 255(饱和度)
  • V: 0 ~ 255(明度)

HSV 空间在颜色分割时更稳定,不受光照强度影响。

cpp 复制代码
// 来自 QuickDemo::colorSpace_Demo
Mat gray, hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
cvtColor(image, gray, COLOR_BGR2GRAY);
imshow("HSV", hsv);
imshow("灰度", gray);
imwrite("D:/hsv.png", hsv);
imwrite("D:/gray.png", gray);

常见错误 :混淆 BGR 与 RGB 导致颜色异常,可用 cvtColor(src, dst, COLOR_BGR2RGB) 修正;HSV 中 H 值误用 0~360 导致分割失败。

6.1 提取指定色彩范围(inRange)与背景替换

cpp 复制代码
// 来自 QuickDemo::inrange_demo
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat mask;
inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask);
Mat redback = Mat::zeros(image.size(), image.type());
redback = Scalar(40, 40, 200);
bitwise_not(mask, mask);
imshow("mask", mask);
image.copyTo(redback, mask);
imshow("roi区域提取", redback);

七、图像归一化(深度学习前处理关键)

重要:归一化必须在色彩空间转换之后进行,因为神经网络通常要求输入为 0,1 或 -1,1 范围的浮点数。

目的:将像素值从 0,255 映射到 0,1 或 -1,1。

cpp 复制代码
// 来自 QuickDemo::norm_demo
Mat dst;
cout << image.type() << endl;          // 16 (CV_8UC3)
image.convertTo(image, CV_32F);        // 改为浮点
cout << image.type() << endl;          // 21 (CV_32FC3)
normalize(image, dst, 1.0, 0, NORM_MINMAX);
cout << dst.type() << endl;            // 21
imshow("图像数据归一化", dst);

注意 :convertTo 只改变数据类型,不改变数值范围,必须再除以 255 或使用 normalize。


八、图像放缩与插值

cpp 复制代码
// 来自 QuickDemo::resize_demo
Mat zoomin, zoomout;
int h = image.rows, w = image.cols;
resize(image, zoomin, Size(w/2, h/2), 0, 0, INTER_LINEAR);
imshow("zoomin", zoomin);
resize(image, zoomout, Size(w*1.5, h*1.5), 0, 0, INTER_LINEAR);
imshow("zoomout", zoomout);

插值算法(由快到慢、由低到高质量):

  • INTER_NEAREST:最近邻,速度快,有锯齿
  • INTER_LINEAR:双线性(默认),质量与速度平衡
  • INTER_CUBIC:双三次,较慢,质量更高
  • INTER_LANCZOS4:Lanczos 插值,质量最高但最慢

缩小图像时推荐 INTER_AREA。


九、图像翻转与旋转

翻转:

cpp 复制代码
// 来自 QuickDemo::flip_demo
Mat dst;
// flip(image, dst, 0); // 上下翻转
// flip(image, dst, 1); // 左右翻转
flip(image, dst, -1); // 180°旋转
imshow("图像翻转", dst);

旋转(仿射变换):

cpp 复制代码
// 来自 QuickDemo::rotate_demo
Mat dst, M;
int w = image.cols, h = image.rows;
M = getRotationMatrix2D(Point2f(w/2, h/2), 45, 1.0);
double cos = abs(M.at<double>(0,0));
double sin = abs(M.at<double>(0,1));
int nw = cos*w + sin*h;
int nh = sin*w + cos*h;
M.at<double>(0,2) += (nw/2 - w/2);
M.at<double>(1,2) += (nh/2 - h/2);
warpAffine(image, dst, M, Size(nw, nh), INTER_LINEAR, 0, Scalar(255,255,0));
imshow("旋转演示", dst);

十、摄像头与视频读取

cpp 复制代码
// 来自 QuickDemo::video_demo(读取部分)
VideoCapture capture("D:/images/video/example_dsh.mp4"); // 也可用 0 打开摄像头
int frame_width = capture.get(CAP_PROP_FRAME_WIDTH);
int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT);
double fps = capture.get(CAP_PROP_FPS);
Mat frame;
while (true) {
    capture.read(frame);
    if (frame.empty()) break;
    imshow("frame", frame);
    if (waitKey(1) == 27) break;   // ESC 退出
}
capture.release();

十一、视频保存

cpp 复制代码
// 来自 QuickDemo::video_demo(保存部分)
VideoWriter writer("D:/test.mp4", capture.get(CAP_PROP_FOURCC), fps,
                   Size(frame_width, frame_height), true);
while (capture.read(frame)) {
    writer.write(frame);
}
writer.release();

常用 fourcc:'M','J','P','G'(MJPG)、'X','V','I','D'(XVID)。


十二、图像卷积(均值模糊)

卷积核在图像上滑动,每个位置做加权和。均值模糊核所有系数相等。

cpp 复制代码
// 来自 QuickDemo::blur_demo
Mat dst;
blur(image, dst, Size(15, 15), Point(-1, -1));
imshow("图像模糊", dst);
  • 核大小必须为奇数(1,3,5,...)
  • Point(-1,-1) 表示锚点位于核中心

十三、高斯模糊

高斯核权重符合正态分布,中心最大,离中心越远权重越小,能更好地保留边缘。

cpp 复制代码
// 来自 QuickDemo::gaussian_blur_demo
Mat dst;
GaussianBlur(image, dst, Size(0, 0), 15);   // sigma=15,核大小自动计算
imshow("高斯模糊", dst);

高斯函数公式(近似):

G(x,y) = \\frac{1}{2\\pi\\sigma\^2} e^{-\\frac{x^2+y^2}{2\\sigma^2}}

  • sigma 越大,图像越模糊
  • 核大小可设为 Size(0,0) 由 sigma 自动推导

十四、人脸检测(DNN 模块)

使用 OpenCV DNN 加载 TensorFlow 模型进行人脸检测。

cpp 复制代码
// 来自 QuickDemo::face_detection_demo
string root_dir = "D:/opencv-4.4.0/opencv/sources/samples/dnn/face_detector/";
dnn::Net net = dnn::readNetFromTensorflow(root_dir + "opencv_face_detector_uint8.pb",
                                          root_dir + "opencv_face_detector.pbtxt");
VideoCapture capture("D:/images/video/example_dsh.mp4");
Mat frame;
while (true) {
    capture.read(frame);
    if (frame.empty()) break;
    Mat blob = dnn::blobFromImage(frame, 1.0, Size(300,300),
                                  Scalar(104,177,123), false, false);
    net.setInput(blob);
    Mat probs = net.forward();   // 输出维度: 1x1xNx7
    Mat detectionMat(probs.size[2], probs.size[3], CV_32F, probs.ptr<float>());
    for (int i = 0; i < detectionMat.rows; i++) {
        float confidence = detectionMat.at<float>(i, 2);
        if (confidence > 0.5) {
            int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frame.cols);
            int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frame.rows);
            int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frame.cols);
            int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frame.rows);
            Rect box(x1, y1, x2 - x1, y2 - y1);
            rectangle(frame, box, Scalar(0,0,255), 2, 8, 0);
        }
    }
    imshow("人脸检测演示", frame);
    if (waitKey(1) == 27) break;
}

输出解析:

  • 矩阵形状 1×1×N×7,N 为检测框数量
  • 每个框的 7 个值:[batch_id, class_id, confidence, x1, y1, x2, y2]
  • 坐标是归一化的,需乘以原图宽高

十五、常见错误与解决方案

错误现象 原因 解决方法
窗口一闪而过 缺少 waitKey(0) 添加 waitKey(0)
imread 失败 路径单反斜杠 \ 被转义 使用 D:/ 或 D:\\
LNK1104 无法打开 .exe 上次程序未关闭,进程占用 关闭图像窗口,任务管理器结束进程
LNK2005 main 重复定义 多个 .cpp 文件包含 main 从项目中排除多余文件
C4244 警告 float 转 int 可能丢数据 显式强制转换 (int)x1
C4819 编码警告 文件含有中文且非 UTF-8 删除中文注释或另存为 UTF-8 with BOM
人脸检测置信度低 阈值 >0.5 太高 调试时降低到 0.3
卷积核大小为偶数 OpenCV 要求奇数 使用 3,5,7 等奇数
归一化后显示全黑 浮点图像 0,1 直接保存 imshow 可显示,保存前转回 CV_8U
VS 重装后环境丢失 未使用属性表 创建 .props 属性表,新项目直接添加
inRange 提取颜色不准确 光照影响 动态调整阈值或先做直方图均衡化

十六、心得体会

  1. 学以致用:OpenCV 只是工具,重点是与 YOLO、ROS2 结合。不需要精通全部 API,但必须熟练掌握图像获取 → 预处理(色彩空间转换 + 归一化)→ 推理 → 后处理这一链条。
  2. 笔记习惯 :手写笔记记录关键函数和参数,比光看视频记忆更深刻。例如整理 cvtColor 常量和对应数值,随时查阅。
  3. HSV 比 RGB 更适合作颜色分割:对光照不敏感,机器人视觉中提取目标颜色非常实用。
  4. 调试技巧 :cout 输出尺寸、类型,imshow 分步观察中间结果(特别是 mask),遇到链接错误先检查进程占用。
  5. 属性表 :创建 .props 属性表后,新建项目配置 OpenCV 只需 5 秒,强烈推荐。
  6. 色彩空间转换必须在归一化之前:因为归一化通常是在最终输入网络前对像素值范围进行压缩,而颜色转换是改变数据语义,必须先完成。

十七、总结

通过本次学习,我掌握了:

  • 图像读取、显示、保存及窗口操作
  • 色彩空间转换(BGR↔GRAY/HSV)以及利用 inRange 进行颜色提取和背景替换
  • Mat 对象的深/浅拷贝、创建空白图像
  • 像素级读写(灰度与彩色)与通道分离合并
  • 图像归一化(convertTo + /255 或 normalize)
  • 几何变换(缩放、翻转、旋转)
  • 视频摄像头读取与保存
  • 图像卷积(均值模糊)与高斯模糊
  • 基于 DNN 模块的人脸检测(加载模型、解析输出、绘制结果)
  • 常见编译/运行错误及解决方案

接下来,我将把上述能力迁移到 ROS2 环境中,结合 cv_bridge 实现从 ROS 话题读取图像,并接入 YOLO 进行目标检测,最终完成实物机器人的视觉导航。

相关推荐
TMT星球4 分钟前
度小满与香港科技大学联合发布轩辕4.5金融大模型
人工智能·金融
GJGCY5 分钟前
金融AI Agent安全如何评估?数据出站、身份映射、参数绑定与审计追踪机制
人工智能·经验分享·安全·ai·金融·自动化·智能体
甲维斯9 分钟前
Codex,Zcode,KimiCode电脑操作能力对比!
人工智能·游戏开发
ckjr00712 分钟前
手机里的AI功能越来越多,如何让它真正提高日常使用效率?
人工智能·智能手机
美狐美颜SDK开放平台14 分钟前
开发直播APP、电商短视频与视频社交平台时,能否共用一套美颜SDK?
android·图像处理·人工智能·音视频·直播美颜sdk
youzuos14 分钟前
大模型面试别背术语:用一次故障讲清工程能力:工程拆解与验证
人工智能
财迅通Ai21 分钟前
MLCC业务爆发式增长,带动昀冢科技三季度扭亏为盈
大数据·人工智能·科技·昀冢科技
AI日报派送佬21 分钟前
2026年11月02日 AI 行业日报|商汤开源 Loop-9B、Pika 发布 H3 Flex 音视频模型、Meta× 傅利叶推出人形机器人 Max
人工智能·微软·开源·音视频·openai·1024程序员节·ai日报
义嘉泰23 分钟前
心率能测出来不难,难的是低功耗、稳定、还能量产
人工智能·科技
具身AGI24 分钟前
家用导航的安全,要看它离什么多近多快
人工智能·深度学习·安全