目录
[2.1.4.cv::destroyWindow 函数](#2.1.4.cv::destroyWindow 函数)
[2.3.2.透明通道(Alpha 通道)](#2.3.2.透明通道(Alpha 通道))
[3.2.OpenCV 中的色彩空间转换](#3.2.OpenCV 中的色彩空间转换)
一.在VS2026里面配置openCV
首先,我们需要去这个opencv官网:OpenCV - Open Computer Vision Library

点进去之后,我们直接选则最新的版本进行下载

我们这里选则windows

下载完之后,我们直接双击打开

这个就是告诉你需要把它下载到哪里去,那么我们自己选择一个目录即可
接下来我告诉你
opencv的动态库是下面这两个

opencv的静态库是下面这些

我们就需要将动态库和静态库都烤出来,也就是下面这2个目录拷贝出来

我们就拷贝到下面这个目录里面来

但是有了库文件还不够,我们需要头文件,头文件其实都在下面这个目录里

那么我们直接把这个include目录给拷贝出来

同样的,也是拷贝到下面这个目录来

现在这个目录里就有了我们opencv的库文件和头文件了

现在我们就可以去写我们的代码来测试一下了
我们直接打开vs2026,创建一个空项目,然后就按照下面来

我们就打开了下面这样子的窗口

这里就有4种情况
| 配置 | 平台 | 说明 |
|---|---|---|
| Debug | x86 | 调试版本,32位程序,包含调试符号,未优化,便于断点、单步调试 |
| Debug | x64 | 调试版本,64位程序,包含调试符号,未优化,适合本地64位调试 |
| Release | x86 | 发布版本,32位程序,已优化,不包含调试信息,性能最佳 |
| Release | x64 | 发布版本,64位程序,已优化,适合最终发布和部署 |
这个其实在VS的下面这个状态栏就能看到

我们就根据自己的需要来进行配置
我们先以Debug x64环境为例来进行配置

取个名字

然后就会发现下面多了一个东西

我们去文件夹看看,其实也是生成了一个配置文件,我们只需要去修改这个配置文件,后面我们就可以直接套用这个配置文件,不需要再进行繁琐的配置了

我们去修改这个配置文件

我们先就来配置头文件路径


我们点击确定
接下来我们来配置一下库文件的路径


点击确认,接下来我们需要去指定链接目标

其实是链接下面哪一个库的

- opencv_world4120.lib库是用于Release模式的
- opencv_world4120d.lib是用于Debug模式的
我们这里是****Debug x64环境,所以我们选择填写 opencv_world4120d.lib
如果说你是Release环境,那么你就填写opencv_world4120.lib

点击确定,这样子就好了,现在我们编写一个opencv程序
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img(400, 600, CV_8UC3, cv::Scalar(255, 255, 255)); // 白色背景
cv::putText(img, "Hello OpenCV", cv::Point(100, 200),
cv::FONT_HERSHEY_SIMPLEX, 1.5, cv::Scalar(0, 0, 255), 2);
cv::imshow("窗口", img);
cv::waitKey(0);
return 0;
}
我们在下面这种环境下运行

我们一运行,就会发现

这个是缺少了动态库的警告,还记得我们的动态库在哪里吗?

我们直接将这个动态库给拷贝过来,拷贝到哪里呢?点击下面这个打开文件


现在我们重新运行

非常完美。
如果说你觉得麻烦,那么我们也可以去借助环境变量的方式来进行配置动态库

然后把我们动态库所在目录配置进去就好了

这样子就好了。注意要重启一下VS
后续,如果说我们想要在新项目里面使用我们的配置,就直接添加现有的

再选择我们之前搞好的.props文件

这样子就OK了
窗口标题出现乱码
显示的图片窗口标题出现乱码,是因为 OpenCV 在 Windows 上默认使用 ANSI(本地编码,如 GBK)来处理字符串,而你的源代码文件(例如 VS 中新建的 .cpp 文件)很可能使用的是 UTF-8 编码(无 BOM)。两种编码不匹配,导致中文字符显示为乱码。

那么我们就可以来进行设置一下,去VS的高级保存选项设置成GBK编码即可

这样子就行了

二.图像读取与显示
2.1.基础示例
使用OPENCV显示一张图片,这个就是最基本的东西
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("Qt.png");
if (img.empty()) {
std::cout << "错误:无法加载图片,请检查路径!" << std::endl;
return -1;
}
cv::imshow("图片", img);
cv::waitKey(10000); // 等待任意按键后关闭窗口
return 0;
}

我们仔细看看这里面的几个函数
2.1.1.cv::imread函数
作用:读取图像文件,并将其解码为一个 OpenCV 的矩阵对象(cv::Mat)。
参数:
第一个参数(必需):图像文件的路径。这里是 "Qt.png"(相对路径,表示与可执行文件同目录下的 Qt.png 文件)。
第二个参数(可选):读取方式,用整数或枚举值指定。常见取值有:
- cv::IMREAD_COLOR(默认值,等价于 1):加载彩色图像,忽略透明度(输出 3 通道 BGR)。
- cv::IMREAD_GRAYSCALE(0):加载为灰度图(单通道)。
- cv::IMREAD_UNCHANGED(-1):保留图像原样(包括 alpha 通道)。
- 本代码省略了第二个参数,因此使用默认值 cv::IMREAD_COLOR。
返回值:cv::Mat 对象。如果读取失败(比如文件不存在、格式不支持),则返回一个空矩阵(img.empty() == true)。
2.1.2.cv::imshow函数
作用:在窗口中显示图像。窗口会自动调整大小以适应图像。
参数:
- 第一个参数("图片"):窗口名称(字符串)。如果之前没有创建过同名窗口,OpenCV 会自动创建一个新窗口。
- 第二个参数(img):要显示的图像(cv::Mat 对象)。
注意:**imshow 本身不会让窗口保留显示,它只是把图像交给窗口系统。**真正让窗口"停留"并刷新的是 cv::waitKey。
2.1.3.cv::waitKey函数
作用:等待用户按键事件,并同时处理窗口事件(刷新、重绘等)。没有它,图像窗口可能会一闪而过或完全不显示。
参数:
**延迟时间(毫秒)。**这里是 10000 毫秒,即 10 秒。
- 若延时 > 0:等待指定毫秒,或在这期间按下任意键后立即返回。
- 若延时 = 0:无限等待,直到用户按下任意键。
- 若延时 < 0:不等按键,立即返回(很少用)。
返回值:
返回按下的按键的 ASCII 码(如果没有任何按键,则返回 -1)。本代码未使用返回值。
2.1.4.cv::destroyWindow 函数
作用:关闭并销毁指定名称的 OpenCV 窗口,释放与该窗口关联的系统资源(包括窗口句柄、内
部缓冲区等)。
它只有一个参数:**要销毁的窗口的名称。**该名称必须与之前调用 cv::namedWindow 或 cv::imshow 时使用的窗口名称完全一致(区分大小写)。
我们看一个简单的例子
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 创建第一个画布(红色背景 + 文字)
cv::Mat canvas1(300, 400, CV_8UC3, cv::Scalar(0, 0, 255)); // 红色
cv::putText(canvas1, "Window 1 - I will be closed",
cv::Point(50, 150),
cv::FONT_HERSHEY_SIMPLEX,
0.7, cv::Scalar(255, 255, 255), 2);
// 2. 创建第二个画布(绿色背景 + 文字)
cv::Mat canvas2(300, 400, CV_8UC3, cv::Scalar(0, 255, 0)); // 绿色
cv::putText(canvas2, "Window 2 - I will stay",
cv::Point(50, 150),
cv::FONT_HERSHEY_SIMPLEX,
0.7, cv::Scalar(0, 0, 0), 2);
// 3. 显示两个窗口
cv::imshow("Window 1", canvas1);
cv::imshow("Window 2", canvas2);
std::cout << "两个窗口已显示,按任意键关闭 Window 1..." << std::endl;
// 4. 等待按键,然后关闭第一个窗口
cv::waitKey(0);
cv::destroyWindow("Window 1"); // 只关闭 Window 1
std::cout << "Window 1 已关闭,Window 2 仍然存在。按任意键退出程序..." << std::endl;
// 5. 再次等待按键,然后程序结束(第二个窗口会随进程结束自动销毁)
cv::waitKey(0);
std::cout << "程序结束,Window 2 自动关闭。" << std::endl;
return 0;
}

我们点击左边红色的opencv窗口,点击回车

是不是很神奇!!!
2.1.5.cv::destroyAllWindows函数
OpenCV 还提供了一个批量销毁所有窗口的函数:cv::destroyAllWindows函数
- 作用:销毁当前程序创建的所有 OpenCV 窗口(无论窗口名称是什么)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建第一个画布(红色背景 + 文字)
cv::Mat canvas1(300, 400, CV_8UC3, cv::Scalar(0, 0, 255));
cv::putText(canvas1, "Window 1", cv::Point(150, 150),
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255), 2);
// 创建第二个画布(绿色背景 + 文字)
cv::Mat canvas2(300, 400, CV_8UC3, cv::Scalar(0, 255, 0));
cv::putText(canvas2, "Window 2", cv::Point(150, 150),
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0), 2);
// 显示两个窗口
cv::imshow("Window 1", canvas1);
cv::imshow("Window 2", canvas2);
std::cout << "两个窗口已显示。按任意键将调用 destroyAllWindows() 关闭所有窗口..." << std::endl;
// 等待按键
cv::waitKey(0);
// 一次性销毁所有 OpenCV 窗口
cv::destroyAllWindows();
std::cout << "已调用 destroyAllWindows(),所有窗口均已关闭。" << std::endl;
// 再等待一下,让用户看到控制台消息后按任意键退出程序
std::cout << "按任意键退出程序..." << std::endl;
std::cin.get(); // 或 cv::waitKey(0),但此时所有窗口已销毁,建议用控制台输入
return 0;
}
这个就很简单了
2.2.cv::namedWindow函数(图片自适应)
现在我们放一张长度很长,宽度一般的图片,我们再使用上面那段代码打开,就会发现出现显示不全的问题

第一个参数:String类型,意义是窗口的唯一标识符(名称)。后续所有与该窗口有关的操作(如 imshow、destroyWindow 等)都要通过这个名字来引用它。
第二个参数:flags
- 类型:int(整数,但通常使用预定义的枚举常量,可读性更好)。
- 默认值:WINDOW_AUTOSIZE(即如果不写这个参数,或者写 0 或 1 等,OpenCV 会采用自动调整大小的行为)。
- 作用:控制窗口的外观和用户交互方式。不同的 flags 值会决定窗口边框能否拖拽、内容是否随窗口缩放、是否启用 OpenGL、是否带工具栏等。
下面详细解释常用的 flags 取值及其效果:
WINDOW_NORMAL
- 这个标志告诉 OpenCV:用户可以自由调整窗口的大小。你可以用鼠标拖拽窗口的边框或角落,把窗口拉大或缩小。
- 当窗口大小改变时,内部显示的图像会被 OpenCV 自动缩放(使用插值算法)以适应新的窗口尺寸。这可能导致图像看起来变模糊或变形,但不会改变原始图像数据本身。
- 适用于需要用户交互地改变显示尺寸的场景,比如图像查看器。
- 如果图像非常大,使用 WINDOW_NORMAL 可以一开始就显示为缩小的视图,而不会撑满屏幕。
WINDOW_AUTOSIZE
- 这是 默认 标志。 它的行为刚好相反:窗口大小完全由图像本身的尺寸决定,用户无法通过鼠标改变窗口的大小。
- 如果图像是 800×600 像素,窗口就会被创建为 800×600 大小(不包括标题栏和边框)。如果图像尺寸超过屏幕分辨率,窗口可能超出屏幕范围,此时你可能需要移动窗口才能看到全部内容。
- 优点:图像以 1:1 的原始像素显示,不会因缩放而失真。缺点:用户无法调整大小,不适合需要在不同屏幕尺寸下查看的场景。
我们看看这个WINDOW_NORMAL
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("long.jpg");
if (img.empty()) {
std::cout << "错误:无法加载图片,请检查路径!" << std::endl;
return -1;
}
cv::namedWindow("Window1", cv::WINDOW_NORMAL);
cv::imshow("Window1", img);//注意这里的窗口名称必须与上面的一致
cv::waitKey(0); // 等待任意按键后关闭窗口
return 0;
}

我们可以去拉伸这个图片

2.3.imread函数的第二个参数
2.3.1.灰度图
- 先想想你见过的"黑白照片"或"黑白电视"
你看过老式的黑白照片吗?或者老电影里那种没有颜色的画面?
那种照片里,没有红色、没有蓝色、没有绿色------只有不同程度的灰色:有的地方很黑(比如头发、阴影),有的地方很白(比如雪地、亮光),中间还有浅灰、中灰、深灰等等。
灰度图就是这种"只有灰色,没有彩色"的图片。
- 把彩色世界想象成"画画"
-
彩色图片就像你用一盒彩色蜡笔画出来的画:你可以用红色画太阳,用绿色画草地,用蓝色画天空。
-
灰度图就像你只用一支铅笔 (或者一根黑色/灰色的蜡笔)来画:你通过用力重一点 画出深黑色,轻轻画 画出浅灰色,不画就留白成白色。整幅画只有黑、白、以及中间的各种灰色,没有别的颜色。
- 为什么要有灰度图?
因为对于很多事情,我们其实不需要知道颜色 ,只需要知道"亮不亮"、"暗不暗"。
比如:
-
你想识别一张照片里有没有猫:猫的形状、耳朵、眼睛、胡子,这些信息跟它是黄猫还是黑猫没关系。你把彩色照片变成灰度图,猫还是那个形状,但数据变小了(电脑处理起来快很多)。
-
你想把一篇文章扫描进电脑:文字是黑是白比它是什么颜色重要,灰度图足够用了。
- 灰度图在电脑里的样子
电脑里,灰度图的每一个"点"(像素)只存一个数字:
-
0 表示纯黑色
-
255 表示纯白色
-
中间的数字(比如 128)表示中灰色(一半白一半黑的感觉)
你可以把灰度图理解成一张由很多小格子组成的图,每个格子要么黑、要么白、要么某种灰色。就像填字游戏里每个格子只涂一种"灰度颜料",没有红绿蓝。
那,我们要怎么才能显示灰度图呢?
这个其实是和cv::imread函数的第二个参数有问题
作用:读取图像文件,并将其解码为一个 OpenCV 的矩阵对象(cv::Mat)。
参数:
第一个参数(必需):图像文件的路径。这里是 "Qt.png"(相对路径,表示与可执行文件同目录下的 Qt.png 文件)。
第二个参数(可选):读取方式,用整数或枚举值指定。常见取值有:
- cv::IMREAD_GRAYSCALE:加载为灰度图(单通道)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("Qt.png", cv::IMREAD_GRAYSCALE);
if (img.empty()) {
std::cout << "错误:无法加载图片,请检查路径!" << std::endl;
return -1;
}
cv::imshow("Window1", img);//注意这里的窗口名称必须与上面的一致
cv::waitKey(0); // 等待任意按键后关闭窗口
return 0;
}

这样子就很OK了
2.3.2.透明通道(Alpha 通道)
- 先回忆一下"通道"是什么
-
一张彩色图片(比如常见的 RGB 图片)可以看作由三张"单色图"叠在一起:一张记录红色有多少、一张记录绿色有多少、一张记录蓝色有多少。这三张就是三个通道。
-
灰度图只有一张"亮度图",所以是一个通道。
- 透明通道是"第四张图"
透明通道(通常叫 Alpha 通道,记作 A)就像是额外附加的一张灰度图 ,但它记录的不是颜色亮度,而是每个像素的"不透明程度"。
-
0 表示完全透明(看不见,像玻璃一样透过去)
-
255 表示完全不透明(实心的,挡住后面的东西)
-
中间的数字(如 128)表示半透明(像毛玻璃或半透明的塑料纸,后面的东西能透过来一些)
所以,一张带透明通道的彩色图像实际上是 四个通道:R(红)、G(绿)、B(蓝)、A(透明)。
- 用贴纸和玻璃纸来比喻
想象你手里有一张印着卡通人物的贴纸:
-
卡通人物本身是完全不透明的(你看不到贴纸背面的东西) → 相当于 Alpha = 255。
-
贴纸空白的地方是完全透明的(你可以透过空白处看到下面的桌面) → 相当于 Alpha = 0。
-
如果你用一张半透明的磨砂玻璃纸盖在照片上,玻璃纸本身让照片模糊地透过来 → 相当于 Alpha = 某个中间值(比如 128)。
透明通道就是告诉你:这张图的每个点,到底有多"实心",有多"透光"。
- 生活中哪里用到透明通道?
-
PNG 图片:很多网站的 Logo、图标(比如微信的绿色对话气泡图标)背景是透明的,这样无论放到什么背景色上都不会有一个白色方块框住它。这种透明就是用 Alpha 通道实现的。
-
PPT 里的半透明形状:你可以在 PowerPoint 里画一个圆形,把填充透明度设为 50%,这样它会半透明地盖住背后的文字。这个 50% 就是 Alpha 值。
-
视频特效:电影里的"火焰"、"爆炸"素材常常是带透明通道的,方便合成到其他画面上。
- 透明通道 vs 灰度图
-
灰度图本身是一张完整的图像,你可以单独看到它(一张黑白照片)。
-
透明通道不能单独作为一张"图像"来看(虽然它也是灰度值),它必须依附于一张彩色图像,用来控制这张彩色图像的哪些部分透明、哪些不透明。
你可以在 Photoshop 里打开一个带透明区域的 PNG 图片,然后切换到"通道"面板,就会看到四个通道:红、绿、蓝、以及一个叫"Alpha"的通道。点开 Alpha 通道,你会发现它显示为一张黑白图------白色区域表示不透明,黑色区域表示完全透明,灰色表示半透明。
- 为什么需要透明通道?
没有透明通道时(比如普通的 JPG 图片),图片永远是矩形 的,即使你画了一个圆形的人物,它的背景也是某种颜色(通常是白色或黑色),放到另一个背景上就会很突兀。
有了透明通道,你就可以把人物"抠"出来,让背景完全透明,然后自由地合成到任何其他图片上,就像把贴纸撕下来贴到别的地方一样。
那么我们的图片如果有通信通道,那么我们就需要将它加载出来
同样是imread的第二个参数:读取方式,用整数或枚举值指定。常见取值有:
- cv::IMREAD_UNCHANGED(-1):保留图像原样(包括 alpha 通道)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("Qt.png", cv::IMREAD_UNCHANGED);
if (img.empty()) {
std::cout << "错误:无法加载图片,请检查路径!" << std::endl;
return -1;
}
cv::imshow("Window1", img);//注意这里的窗口名称必须与上面的一致
cv::waitKey(0); // 等待任意按键后关闭窗口
return 0;
}
这个还是非常简单的
三.图像色彩空间转换
3.1.色彩空间是什么
阳光通过三棱镜会分解成红、橙、黄、绿、蓝、靛、紫------这是连续的物理光谱。但是计算机和人类不可能存储无穷多种颜色,所以我们需要一种有限的、数字化的方式来描述颜色。
色彩空间就是一套规则,它规定了:
- 用哪几个"基本量"来描述颜色(比如用三个数字)
- 这三个数字分别代表什么物理含义
- 所有可能的颜色如何用这三个数字的组合来表示
你可以把色彩空间想象成三维坐标系,就像空间中的点用 (x, y, z) 表示一样,颜色也用三个坐标值来表示。
我们举一个例子来:
- 例子1:RGB 色彩空间
RGB 是"红(Red)、绿(Green)、蓝(Blue)"的缩写。
原理:通过三种颜色的光按不同强度混合,可以产生几乎所有人眼能看到的颜色。你的手机屏幕、电脑显示器就是通过红、绿、蓝三个小灯来显示所有颜色的。
表示法:每个颜色用一个三元组 (R, G, B) 表示,通常每个分量范围 0~255(0 表示没有该颜色,255 表示该颜色最强)。
- (255,0,0) 表示纯红
- (0,255,0) 表示纯绿
- (0,0,255) 表示纯蓝
- (255,255,255) 表示白色
- (0,0,0) 表示黑色
- (128,128,128) 表示中灰色
所以当你拍一张彩色照片,计算机存储的是每个像素的 (R,G,B) 三个数字。这就是RGB 色彩空间。
- 例子2:灰度色彩空间
灰度色彩空间只有一个数字:亮度。0 表示黑,255 表示白,中间的数字表示不同深浅的灰色。
所以灰度图只有亮度信息,没有颜色信息。
- 例子3:HSV 色彩空间
HSV 是色调(Hue)、饱和度(Saturation)、明度(Value)的缩写。
- 色调(H):表示是什么颜色,用角度 0°~360° 表示,0° 红色,120° 绿色,240° 蓝色等。
- 饱和度(S):表示颜色浓淡,0% 是灰色,100% 是纯彩色。
- 明度(V):表示颜色有多亮,0% 是黑色,100% 是最亮。
HSV 更符合人类直觉:比如你想调一个"更红的颜色",你会改变色调;想让它"更鲜艳",就增加饱和度;想让它"更亮",就增加明度。
什么是"转换"?
转换就是:已知某个像素在一种色彩空间下的坐标值(比如 RGB 值),通过一个数学公式,计算出它在另一种色彩空间下的坐标值(比如 HSV 值)。
因为两个色彩空间描述的是同一个颜色,只是用了不同的语言。就像你手里有一杯水:你可以说它的体积是 500 毫升,也可以说它是 0.5 升。数字变了,但水量没变。同样,颜色没变,只是表达方式变了。
举例:RGB 转灰度
有一个很经典的公式:
cpp
灰度 = 0.299 × R + 0.587 × G + 0.114 × B
比如一个像素的 RGB 是 (255, 100, 50)(很亮的红橙色)。
计算:0.299×255 ≈ 76.2,0.587×100 = 58.7,0.114×50 = 5.7,总和 ≈ 140.6。
四舍五入后灰度值 141。
所以这个像素在灰度空间里是 141(中等偏亮的灰色)。
这个转换就是色彩空间转换的一个具体例子。
三、为什么要做这种转换?(重点)
既然图像本来已经是 RGB 了,为什么还要转成别的?因为不同的图像处理任务在不同的色彩空间里做起来更简单、更准确。
场景1. 想把红色变得更红,但不改变亮度
在 RGB 空间里,红色是 (255,0,0)。你想让它"更红",只能把红色分量增大------但最大值已经是 255 了。而且你稍微改一下三个数字,亮度也会跟着变,很难控制。
显然我们就是不能在RGB图像色彩空间里面进行调整的,我们需要转换到HSV图像色彩空间去。
在 HSV 空间里,颜色、饱和度、亮度是三个独立的分量。
-
色调(H)决定是什么颜色(红、绿、蓝...)
-
饱和度(S)决定颜色浓不浓
-
明度(V)决定亮不亮
你只要增加饱和度 S ,颜色就会变得更浓、更鲜艳,而不会改变它是红色还是亮度。如果你想保持亮度只改颜色,那就改 H。分开控制,简单直接。
我们如果想要调整饱和度,就先将图像色彩空间从RGB转换成HSV,调整完饱和度了之后,我们再转换回我们的RGB图像色彩空间。
场景2:想把彩色照片变成黑白效果
- 不需要手动调整每个像素的RGB值,只需将图像从 RGB 色彩空间转换为灰度(GRAY)色彩空间即可。
- 很多滤镜就是这样做的。
场景3:改变照片的"色调"而不改变亮度
- 在 RGB 里,你想把整张照片变得偏红一些,你会把每个像素的 R 分量增加。但这样做同时也会改变亮度(因为红色更亮了)。
- 在 HSV 里,亮度是单独一个通道 V。你只需要改变 H(色调)通道,保持 S 和 V 不变,就能改变颜色而保持亮度完全不变。这样调色效果更自然。
场景4:颜色识别/物体跟踪
- 比如你想写一个程序追踪一个红色小球。在 RGB 空间里,"红色"没有一个简单的范围,因为受到光线亮度影响,红色可能看起来像粉色、暗红、橙色等。
- 但在 HSV 空间里,红色对应 H 值大约 0~10 和 160~180,而且你可以过滤掉太暗(V 太低)或太灰(S 太低)的像素,就能更稳定地找到红色物体。
场景5:图像压缩(视频、JPEG)
- 视频编码常用 YCbCr 色彩空间:Y 是亮度,Cb/Cr 是蓝色和红色的色差。因为人眼对亮度细节敏感,对颜色细节不敏感,可以大幅压缩颜色信息而不易察觉。
- 这也是为什么很多格式需要从 RGB 转到 YCbCr。
3.2.OpenCV 中的色彩空间转换
OpenCV 提供了 cv::cvtColor() 函数,专门做这件事。
第一个参数:输入图像,cv::Mat类型
第二个参数:输出图像,cv::Mat类型
第三个参数:转换类型(或称为转换代码),是一个整数常量,用来指定从哪种色彩空间转换到哪种色彩空间。OpenCV 在 cv::ColorConversionCodes 枚举中定义了数百种转换代码,最常见的是 COLOR_BGR2GRAY、COLOR_BGR2HSV、COLOR_HSV2BGR、COLOR_BGR2RGB 等。
作用:告诉函数使用哪个数学公式来重新计算每个像素的颜色值。它是整个转换的核心指令。
命名规则:通常形式为 COLOR_<源空间>2<目标空间>,例如:
- COLOR_BGR2GRAY:BGR 彩色 → 灰度
- COLOR_GRAY2BGR:灰度 → BGR(输出三个通道,每个通道的值都等于原灰度值)
- COLOR_BGR2HSV:BGR → HSV
- COLOR_HSV2BGR:HSV → BGR
BGR 和 RGB 是两种不同的通道顺序,不是同一个东西。不过因为它们在数值上很像(都是三个通道,只是排列顺序不同),所以很容易被误认为是一回事。它们本质的区别
- RGB:第一个分量是红色(R),第二个是绿色(G),第三个是蓝色(B)。
- BGR:第一个分量是蓝色(B),第二个是绿色(G),第三个是红色(R)。
对于同一个颜色(比如纯红色),RGB 存为 (255, 0, 0),而 BGR 存为 (0, 0, 255)。数字完全不同,如果不做转换,直接按 RGB 去读 BGR 的数据,红色就会变成蓝色。
在 OpenCV 里,你用 imshow 显示图像时,它自动帮你把 BGR 转成了屏幕能正确显示的颜色,所以你看到的颜色是正常的,感觉不到区别。
示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
class QuickDemo
{
public:
void colorSpace_Demo(cv::Mat& image)
{
cv::Mat gray, hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::imshow("HSV", hsv);
cv::imshow("灰度", gray);
cv::imwrite("D:/hsv.png", hsv);
cv::imwrite("D:/gray.png", gray);
}
};
int main() {
cv::Mat img = cv::imread("Qt.png", cv::IMREAD_UNCHANGED);
if (img.empty()) {
std::cout << "错误:无法加载图片,请检查路径!" << std::endl;
return -1;
}
QuickDemo qd;
qd.colorSpace_Demo(img);
cv::imshow("Window1", img);//注意这里的窗口名称必须与上面的一致
cv::waitKey(0); // 等待任意按键后关闭窗口
return 0;
}
我们这里使用了cv::imwrite,它其实是有3个参数在里面的,但是最常用的就是前两个参数
cv::imwrite 的前两个参数
第1个参数:filename
是什么:要保存的文件名,带路径(字符串)。
决定格式:OpenCV 根据文件名的扩展名(如 .png、.jpg、.bmp)自动选择保存格式。
例子:
- "result.png" → 保存为 PNG 格式
- "photo.jpg" → 保存为 JPEG 格式
- "C:/images/out.bmp" → 保存为 BMP 格式
第2个参数:img
- 是什么:你要保存的图像数据,就是 cv::Mat 类型的对象。
- 要求:图像必须非空(已经成功加载或创建了内容)。
- 注意:OpenCV 默认使用 BGR 颜色顺序,但当你保存为 .jpg 或 .png 等常见格式时,OpenCV 内部会自动转换成正确的颜色,所以你直接保存即可,不需要额外转换。

我们去看看保存的情况,和我们这里显示的是一样的。