刚开始接触OpenCV的时候,大多数人都会先碰到两个绕不开的基础点:一个是cvtColor颜色空间转换,另一个就是line、circle这类基本图形绘制。这两个功能表面上看没什么关联,实际上在后续所有的视觉处理流程里,它们就像地基一样——你总要把图片从一种颜色空间转到另一种,也总要在画面上标注框、画线、写字。我在带新人的时候发现,凡是后面学得顺的,基本都是在早期把这两个点吃透了。这篇就把它们放在一起拆开讲讲,顺便把一些坑也列出来。
1. 整体设计拆解:为什么cvtColor和基本图形要放在一起学
1.1 先从OpenCV的BGR迷思说起
cvtColor这个函数,全称是convert color,一看名字就知道是干颜色空间转换的。很多刚入门的朋友第一次用OpenCV读图片,用imshow显示出来,发现颜色明显不对——红色变成了蓝色,绿色还是绿色,场景看起来像被调了滤镜。这不是你的代码写错了,而是OpenCV读入图片时默认用的是BGR顺序。
这里要补一个背景:图片文件在磁盘上普遍存的是RGB顺序(更准确说是sRGB色彩空间的RGB三通道),但OpenCV为了历史兼容性和底层图像处理库的习惯,读入内存后排列成了BGR。你要是直接把这个BGR数据当成RGB拿来显示,浏览器或者一般的显示组件会按RGB去解释,于是红蓝就互换了。cvtColor在这里的作用就是显式地做通道重排:cv2.COLOR_BGR2RGB把BGR数据转成RGB,用正确顺序交给显示端。
这个“颜色空间”的概念听起来玄乎,我用生活里的例子解释一下。同一件衣服,在白炽灯下、在日光灯下、在户外阳光下,你眼睛看到的颜色会有差异,但你知道衣服本身的颜色没变。图像处理里的颜色空间,就是一系列用来描述“颜色”的坐标系,有的按红绿蓝三原色来描述,有的按色调、饱和度、亮度来描述,有的按亮度与色差来描述。同一个像素,在BGR下可能是(200, 100, 50),到了HSV下就会变成另一组数值,但两个值都能对应到同一个物理颜色。cvtColor做的,就是把这些坐标系之间的换算。
1.2 cvtColor在整个视觉流程中的位置
cvtColor虽然本身只是个转换函数,但它几乎出现在所有OpenCV实战项目的“前置步骤”里。最典型的是物体检测和目标分割:直接用BGR图像做阈值分割,效果通常很差,因为光照变化会同时影响三个通道;先转换到HSV,用色调H通道做阈值,光照带来的亮度变化就被隔离出去了。另一个常见场景是人脸检测、边缘提取这类任务,通常先把彩色图转成灰度图,降低计算量,同时让梯度信息更集中。
我做过的某个模拟项目里,需要从视频流中识别特定颜色的目标物,一开始直接在BGR上按红色范围做inRange,效果一塌糊涂,参数怎么调都误检。后来把每一帧先cvtColor到HSV,再用H通道阈值卡范围,稳定性和鲁棒性立刻上来了。这就是cvtColor的典型价值:它不只是“换个颜色格式”,而是把颜色信息分成更适合算法处理的维度。
1.3 基本图形绘制的实际应用场景
基本图形绘制同样不只是“画个圆”这么简单。它在实际中的使用场景非常密集:目标检测结果需要用矩形框标出来;视频跟踪需要实时画一条运动轨迹;图像拼接需要把匹配的关键点连线画出来;数据增强时要在图像上随机画框、画线、加噪声。可以说,所有“可视化反馈”都依赖这些基础绘制函数。
我个人的体会是,尽早把line、circle、rectangle、ellipse、putText这几个函数用熟,后面做任何视觉项目都会顺手很多。你不需要花时间查参数,把精力集中在算法逻辑上。这也是为什么很多老工程师带新人时,都让新人先用这些函数写一个“画图小练手”,把坐标、颜色、线宽这几个概念彻底搞明白再说别的。
2. cvtColor核心细节解析与实操要点
2.1 最常用的三种转换:BGR2RGB、BGR2GRAY、BGR2HSV
cvtColor函数在OpenCV 4.x里的声明非常简单,Python和C++接口的参数基本一致:
dst = cv2.cvtColor(src, code[, dstCn])三个参数:输入图像、转换代码、输出图像的通道数(可选)。光转换类型就有上百种枚举值,但绝大多数项目里高频使用的其实就那么几个。
COLOR_BGR2RGB和COLOR_RGB2BGR是纯通道重排,不涉及任何数学计算,就是把第一通道和第三通道互换。之所以两个都有,是因为OpenCV同时支持BGR和RGB两种布局输入,你得告诉它你的输入数据到底按什么顺序存的。这里有个很多人忽略的细节:如果你的图片是用cv2.imread读的,它在内存里是BGR;如果用matplotlib的plt.imread读的,它读进来是RGB。你要想做统一处理,必须先弄清楚当前数据到底是什么布局,再决定用哪个转换,搞反了颜色照样不对。
COLOR_BGR2GRAY是灰度转换,这一步有真实的加权计算,公式是:
gray = 0.114 * B + 0.587 * G + 0.299 * R注意这三个系数不是平权的,绿色权重最大,蓝色最小。原因是人眼对绿色最敏感、对蓝色最不敏感,这个加权公式是为了让灰度图在视觉上尽量接近人眼感知的明暗。有些新手想省事,直接np.mean(img, axis=2)取三个通道平均,效果确实也能看,但对比度、层次感会比加权灰度差不少。OpenCV用这个公式是考虑了感知均匀性的,直接用官方的就行,别自己改动。
COLOR_BGR2HSV和COLOR_BGR2HLS则用于把颜色分解成色调、饱和度、亮度/明度。这个转换在颜色检测相关的功能上是绝对的主力。我在用OpenCV 4.5以上版本时,这些转换在内部还会启用一些优化路径,处理速度比旧版本快一些,但对使用方式没有影响,代码可以无缝迁移。
2.2 颜色空间转换的数学原理
cvtColor背后的本质是一个矩阵变换或者非线性映射。BGR到灰度的公式已经给出了,BGR到HSV这类转换则不是简单的线性变换,而是先对三个通道做归一化,再根据最大值最小值计算色调H:
假设R、G、B已经归一化到[0, 1],令max = max(R, G, B),min = min(R, G, B),差值delta = max - min。H的计算分几种情况:
- 如果
delta为0,说明是灰色,H定义为0。 - 如果
max是R,则H = 60 * (((G - B) / delta) mod 6)。 - 如果
max是G,则H = 60 * ((B - R) / delta + 2)。 - 如果
max是B,则H = 60 * ((R - G) / delta + 4)。
S(饱和度)在max为0时是0,否则等于delta / max。V(明度)就是max本身。
你不需要死记这些公式,但一定要理解这个非线性性质带来的一个直接后果:HSV三个通道的取值范围和BGR不一样。在OpenCV的8位图像里,H的取值范围被压缩到了0到180(为了让H能塞进uint8的单字节里),S和V都是0到255。这跟你在Photoshop里看到的H 0到360、S和V 0到100完全不同。我在做颜色阈值时,最常犯的错误就是把H的上下限设成300,结果发现根本匹配不到任何像素——因为H最大值就180。如果你是从别的工具里抄来的HSV阈值,记得先做换算:h_opencv = h_deg / 2,s_opencv = s_pct * 255 / 100,v_opencv = v_pct * 255 / 100。
2.3 容易忽略的第4个参数dstCn
cvtColor的第三个参数dstCn在实际编码中经常被忽略,但它在特定场景下很有用。它的含义是“目标图像的通道数”,默认是0,表示让函数根据转换类型自动决定。大多数情况下你不用管它,但有一个典型的例外:把BGR图转成带透明通道的BGRA图。
img_bgra = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2BGRA, 4)如果你漏掉最后的4,函数默认生成的还是三通道的BGR,而不是四通道的BGRA。反过来,如果你想把带透明通道的图去掉Alpha,可以用cv2.COLOR_BGRA2BGR,这时OpenCV会直接把Alpha通道丢掉。这类带Alpha的转换,dstCn传递了就精准了,不会出歧义。
2.4 实操心得:转换前后的数据类型坑
cvtColor不仅对通道数敏感,对数据类型同样敏感。大多数转换函数是针对8位无符号整型(uint8)设计的,也就是每通道0到255。如果你读入的图片是16位深度(某些相机输出、HDR图),或者数据已经做了归一化变成了float32类型,直接用默认的转换代码会报错或者得到全黑图。
正确做法是在转换前先做好类型对齐:如果是16位灰度转8位,用cv2.normalize拉伸到0到255;如果是float32的0到1范围,先乘255再转uint8。我记得有个项目里读取了16位TIFF图像,忘了转类型就调cvtColor,结果所有输出都是黑的,排查了很久才发现是数据类型不匹配。
还有一个值得注意的:cvtColor返回的新图像,数据是重新分配的,和原图不共享内存。也就是说你在转换结果上画画、改像素,不会影响到原图。这一点和很多numpy的切片视图不一样,别搞混了。
3. 基本图形绘制详解:从线段到文字的细节
3.1 五个核心绘制函数逐个拆解
OpenCV的绘图函数在Python接口里都在cv2下,C++里则在imgproc模块。每个函数的核心参数结构都很接近:在哪个图上画、画在什么坐标、用什么颜色、线宽多少。把这五个函数记住,日常工作就够用了。
import cv2 import numpy as np canvas = np.zeros((480, 640, 3), dtype=np.uint8) # 画直线,起点(50, 50),终点(200, 50),绿色,线宽2 cv2.line(canvas, (50, 50), (200, 50), (0, 255, 0), 2) # 画圆,圆心(100, 100),半径50,蓝色,线宽3 cv2.circle(canvas, (100, 100), 50, (255, 0, 0), 3) # 画矩形,左上角(50, 150),右下角(200, 300),红色,线宽2 cv2.rectangle(canvas, (50, 150), (200, 300), (0, 0, 255), 2) # 画椭圆,中心(300, 200),主轴半径(100, 50),旋转30度,线宽2 cv2.ellipse(canvas, (300, 200), (100, 50), 30, 0, 360, (255, 255, 0), 2) # 写文字,左上角(50, 400),字体为 Hershey Simplex,字号1,白色,线宽2 cv2.putText(canvas, "OpenCV4X", (50, 400), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)这里最让人困惑的一点是坐标系的定义。OpenCV的坐标原点在图像左上角,x轴向右,y轴向下,单位是像素。一个宽640、高480的图像,x范围是0到639,y范围是0到479。你写(50, 50)意味着横坐标50、纵坐标50,也就是离左边50像素、离上边50像素。这个从左上角起算的方向和很多数学坐标系是反的,新手在画椭圆旋转时经常因为方向搞反导致图形偏转方向不对。
3.2 颜色参数:为什么必须是int元组
很多人第一次调用cv2.circle传入颜色时,习惯性地写(0.5, 0.6, 0.7)这种浮点值,结果直接报错。原因在于OpenCV的绘制函数期望的颜色值是8位整数元组,每个通道范围0到255,对应uint8类型。在Python里,如果你用(0.0, 255.0, 0.0)这类浮点,部分函数会直接抛异常;在C++里,参数类型是Scalar,内部是double数组,浮点传入不会报错,但绘制出的颜色表现可能出乎意料。
提示:所有的BGR颜色元组,顺序永远是(B, G, R),不是(R, G, B)。你要画红色,写的是(0, 0, 255);要画蓝色,写的是(255, 0, 0)。这是初学阶段最容易搞错的地方,没有之一。
另外,通道值超出0到255范围也不会直接报错,函数会按截断或者取模处理。比如你传(300, 0, 0),OpenCV内部是把它看作一个普通的8位色值,不同版本表现不同,有的直接截断到255,有的在C++接口下会按Scalar的浮点形式处理,画完结果就有差异。遇到颜色不对的现象,先检查你的元组顺序和值范围,这是最简单的排查步骤。
还有一个很容易被坑到的情况:如果你在灰度图上绘制彩色图形,OpenCV会忽略你传的颜色值。因为灰度图本质上每个像素就一个通道,绘制函数会把颜色按照灰度亮度去解释,你画一个红色的圆,最终画出来是一个灰色的圆。如果你确实需要在灰度图像上叠加高亮标记,先把灰度图用cvtColor转回BGR三通道,再画彩色图形,这样标记颜色才能准确显示。
3.3 线宽、线型与反锯齿:从视觉到性能的权衡
线宽参数thickness的含义很直白:线的粗细,单位是像素。线宽为-1时表示填充整个图形,比如cv2.circle的thickness=-1就是实心圆,cv2.rectangle的thickness=-1就是填充矩形。这个负数的特殊含义是用得比较多的技巧,画掩膜、画标注框要填充实心区域时不二之选。
线型参数lineType有三个选项:cv2.LINE_4、cv2.LINE_8和cv2.LINE_AA。默认值是LINE_8,它用8邻域连接方式渲染直线,速度快,但边缘会有明显的锯齿。LINE_AA是抗锯齿模式,画出来的线边缘更平滑,视觉效果好,但计算量稍大。在视频绘制坐标轨迹这种一帧要画几百条线的场景,用LINE_8能明显减少耗时;在离线生成报告图、需要在演示中展示的图像里,我通常用LINE_AA,观感好得多。
有个细节:字体绘制函数putText里没有lineType参数,但底层渲染也是走同样的栅格化路径。在没有指定特殊选项时,中文字体会因为OpenCV内置字体只支持ASCII字符集而直接变成问号。要显示中文,只能把图像数据先转成PIL的Image对象,用PIL自带的中文字体绘制之后,再转回numpy数组,过程比较绕,但这是目前最稳定的方案。
3.4 多边形绘制与填充:polylines、fillPoly和fillConvexPoly
除了标准的圆形、矩形、椭圆,实际项目里更常用的是任意多边形:检测结果经常是凸多边形包围盒(比如旋转矩形的四个顶点),或者你需要在特定区域画一个不规则的标注区域。这时要用polylines和fillPoly。
pts = np.array([[10, 10], [100, 30], [120, 120], [30, 100]], dtype=np.int32) cv2.polylines(canvas, [pts], isClosed=True, color=(0, 255, 255), thickness=2)polylines的pts参数需要传入一个二维numpy数组,每一行是一个顶点坐标,数据类型必须是int32。很多人会把np.array的类型默认为int64,直接传进去会报一个类型不匹配的错误。这在C++接口里也一样,要求顶点数组是std::vector<cv::Point>,本质都是整型坐标。
fillPoly和polylines的用法区别在于:一个是填充多边形内部,一个是只画边缘。这两个函数在处理掩膜制作时非常实用。比如做ROI区域,你定义好一个闭合多边形,用fillPoly填充成白色(255),再将原图和掩膜做bitwise_and,就能把区域外的部分全部屏蔽掉。
fillConvexPoly则是fillPoly的快速版本,它只支持凸多边形。如果你确定多边形没有凹进去的地方,用fillConvexPoly性能更好;如果多边形可能包含凹点,或者顶点顺序不完全可控,用fillPoly更稳妥,它内部会做三角剖分,处理所有情况。
4. 实操过程:手把手实现一个图像绘制与转换Demo
4.1 环境准备与工程结构
在开始之前,先把环境准备好。OpenCV 4.x的Python安装非常省事:
pip install opencv-python opencv-contrib-pythonopencv-python是主库,包含基本图像处理和视频处理功能;opencv-contrib-python额外包含一些扩展模块(比如dnn、xfeatures2d)。如果你是纯学习cvtColor和绘制函数,只装opencv-python就够了,没必要背上额外的体积。C++用户则需要在支持OpenCV 4.x的系统上完成编译,或者直接用包管理器安装预编译版本。
4.2 完整Demo代码
下面这段代码我实际跑过,功能是:创建一个空白画布,在上面绘制几个基本图形和文字,然后展示原始绘制结果、灰度转换结果、HSV转换结果三张图,帮助直观感受颜色空间转换的前后差异。
import cv2 import numpy as np # 1. 创建空白画布 640x480,三通道,黑色背景 canvas = np.zeros((480, 640, 3), dtype=np.uint8) # 2. 绘制基本图形 cv2.line(canvas, (50, 50), (250, 50), (0, 255, 0), 3) cv2.rectangle(canvas, (50, 80), (250, 180), (0, 0, 255), 2) cv2.circle(canvas, (350, 120), 60, (255, 0, 0), 2) cv2.ellipse(canvas, (350, 200), (100, 50), 30, 0, 360, (255, 255, 0), 2) pts = np.array([[450, 50], [550, 80], [500, 150], [430, 120]], dtype=np.int32) cv2.polylines(canvas, [pts], True, (255, 0, 255), 2) cv2.putText(canvas, "OpenCV4X Demo", (50, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) # 3. 颜色空间转换 gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY) hsv = cv2.cvtColor(canvas, cv2.COLOR_BGR2HSV) # 4. 显示和保存结果 cv2.imshow("Canvas", canvas) cv2.imshow("Gray", gray) cv2.imshow("HSV", hsv) cv2.waitKey(0) cv2.destroyAllWindows() cv2.imwrite("canvas_bgr.jpg", canvas) cv2.imwrite("canvas_gray.jpg", gray) print("Done")这里有个关键点:创建画布时用np.zeros((480, 640, 3), dtype=np.uint8),顺序是先高度后宽度。OpenCV的imshow期望的输入形状是(height, width, channels),如果你写成(640, 480, 3),显示出来的图片会变形或者直接报维度错误。这种高在前、宽在后的约定,和很多人的习惯相反,是初学阶段最容易弄反的点。
4.3 运行效果与结果分析
运行上面的代码,会弹出三个窗口。第一个窗口能看到画布上各种颜色的图形和白色文字;第二个窗口的灰度图里,所有彩色图形都变成了不同深浅的灰色,绿色的线会显得比较亮(因为绿色权重高),红色的矩形会显得偏暗(红色权重偏低);第三个窗口的HSV图则很特别——它显示出来的颜色并不是你画图时指定的颜色,而是一张花花绿绿的伪彩色图。HSV的三个通道分别被当成BGR的三个通道显示出来,所以视觉上完全是另一样子:色调H映射成了蓝色区域,饱和度S映射成了绿色区域,明度V映射成了红色区域。
这个结果恰好说明了一个重要事实:HSV只是颜色的另一种数学表达,它的三个通道值本身并不是给人看的,而是给算法用的。你在HSV图上看到的不符合预期的“颜色”,并不是转换错了,而是“显示方式”不匹配。如果你想把HSV某个通道单独可视化,正确的做法是只取单个通道来显示:
h, s, v = cv2.split(hsv) cv2.imshow("H Channel", h) cv2.imshow("S Channel", s) cv2.imshow("V Channel", v)这样你就能看到纯H通道、纯S通道、纯V通道各自的灰度分布,它们才是算法真正拿来分析的数据。
4.4 组合实战:绘制标记并在转换后继续处理
把绘制和转换这两个技能组合起来,能做出很多实际可用的东西。我做过的某个模拟项目里,需要先识别画面中的某个圆形目标,然后在原图上把它圈出来,同时还要在灰度图上叠加一个半透明的ROI区域。
流程大致是:每帧图片先转灰度,用HoughCircles检测圆形,得到圆心和半径,再在原图上用cv2.circle画一个绿色的圈表示检测结果。这个流程的核心就是把“算法处理”和“可视化绘制”解耦——算法在灰度图上跑,可视化在原图上画。如果你在彩色图上转换前就画好圈,转灰度后圈的颜色信息就丢了,所以顺序一定要想清楚:先处理、后绘制。
另外,在做目标跟踪时,我经常会把历史轨迹点收集到一个列表里,然后用cv2.polylines把所有轨迹点用折线连起来,加一个LINE_AA线型,连起来的效果很平滑。这个方法比逐一点cv2.circle画圆点高效得多,尤其在轨迹点数量很大时,绘制性能差距明显。
5. 常见问题与排查技巧实录
5.1 高频错误与解决方案速查表
我在社区和日常带人的过程中,把新手最容易踩的坑汇总成了下面这张表,你可以直接保存下来对照排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 图片颜色偏蓝/偏红 | cvtColor用反了或者BGR/RGB布局搞错 | 确认输入数据布局,按需使用COLOR_BGR2RGB或COLOR_RGB2BGR |
| 绘制函数传入浮点颜色报错 | 颜色值必须是8位整型 | 确保元组内是int类型,范围0到255 |
| 画线/画圆位置偏了 | 坐标顺序或者宽高方向搞反 | 记住(x, y)就是(列, 行),x向右y向下 |
| 灰度图上画彩色图形没反应 | 灰度图是单通道,无法显示彩色 | 先cvtColor转成BGR,再绘制彩色图形 |
putText中文显示成问号 | OpenCV内置字体不支持中文 | 转PIL后绘制,或改用英文/拼音 |
| HSV颜色检测范围对不上 | H取值范围搞错(0到180非0到360) | 先将外部HSV值换算到OpenCV范围,H除以2 |
创建画布后imshow报错维度不对 | np.zeros先写宽度后写高度 | 形状必须是(height, width, channels) |
转换16位或浮点图直接调用cvtColor报错 | 数据类型不匹配 | 先归一化到uint8,再转换 |
5.2 排查思路:如何定位颜色显示不对的问题
颜色显示不对,是视觉项目中最容易让人抓狂的问题。排查的时候不要乱试参数,按顺序做就能快速定位。
第一步,确认数据来源。图片是cv2.imread读的,数据就是BGR;用matplotlib读的就是RGB;相机SDK输出可能是YUV或者Raw格式。不同的来源决定了你数据当前的通道顺序。
第二步,显示前先验证。把img.shape打印出来,确认是(h, w, 3)还是(h, w, 4),通道数是否和你预期一致。如果带Alpha通道,显示的时候没有去掉Alpha,颜色也会偏暗或者异常。
第三步,单独检查某个通道。用cv2.split把通道拆开,逐个显示,看哪个通道的数值分布和你的预期对不上。比如一张纯红色图片,在BGR布局下(B, G, R)应该是(0, 0, 255),如果你看到通道1(即R通道)是0而通道2(即B通道)是255,说明数据布局是RGB,需要转一下。
这个方法我屡试不爽。先判断数据对错,再质疑代码逻辑,顺序搞对,排查效率能提升一倍。
5.3 几个容易被忽略的坑
第一个坑是cv2.imshow之后没有写cv2.waitKey(0)。很多人写完后发现窗口闪一下就没了,就是这个原因。waitKey不光负责等待按键,它还负责把窗口的事件循环跑起来,没有它窗口内容根本没有机会刷新出来。
第二个坑是cv2.imwrite保存图片时,对路径后缀敏感。你写canvas.png会保存成PNG,写canvas.jpg会保存成JPEG。如果后缀是.jpeg、.webp这些,OpenCV 4.x通常也支持,但如果你写了一个怪后缀,比如.data,保存时会直接报错。
第三个坑和内存有关:在循环里反复调用np.zeros创建新画布,如果每一帧画完没有释放旧画布,Python的垃圾回收机制在内存占用高时才会启动,长时间运行视频处理脚本,内存会慢慢爬升。我一般会在每帧开始时主动把上一帧的引用置空,或者用canvas.fill(0)而不是重新np.zeros,这样可以把分配新内存的开销降到最低。
第四个坑是关于fillPoly的顶点数组类型。上面代码里我显式写了dtype=np.int32,这很关键。如果省略,numpy会默认用int64,OpenCV的Python绑定检查到类型不匹配就会抛异常。这个错误信息有时会比较隐晦,新手不容易联想到是数据类型问题。顺便说一句,所有需要传入坐标数组的OpenCV函数,顶点坐标基本都是这个要求。
5.4 从绘制到实战的过渡建议
如果你已经能熟练用这些函数画出各种图形,也搞懂了cvtColor不同转换之间的区别,下一步可以试试把这些技能组合起来做一个完整的小项目。比如找一个实时摄像头画面,每一帧上做背景差分,把运动区域用矩形框标出来;或者拍一张有纹理的图片,转HSV后按颜色阈值提取目标区域,再用fillPoly制作掩膜,把背景替换成纯色。这几个综合练习能把绘制、转换、掩膜、显示的知识点全部串起来。
我在带新人的时候,通常会布置一个“画白板”的小任务:打开一个空白画布,先用rectangle画出一个表格,再用putText写出表头,然后画几条数据曲线,最后把画布转成灰度并保存。这个任务看起来简单,但能把坐标计算、颜色配置、绘制顺序、通道转换全部练到,做完之后对这两个知识点的理解会比单纯看文档扎实得多。
我个人在实际操作中的体会是,这两个知识点本身的API并不复杂,真正的难点在三点:记住BGR顺序、搞懂坐标系、想清楚数据布局。只要这三点不出错,cvtColor和基本图形绘制加起来就是一行一行函数调用的事。你越早把这个基础打牢,后面学滤波、边缘检测、轮廓分析、目标跟踪的时候,就越能把注意力放在算法本身上,而不是被画框画线这些琐碎细节拖后腿。
最后再分享一个小技巧:如果你在调试OpenCV时不确定当前数据是什么颜色空间、什么通道顺序,与其反复猜,不如直接生成一张包含红绿蓝三色块的测试图,跑一遍你的处理流程,看看输出结果哪个颜色保留、哪个颜色互换、哪个颜色丢失,一眼就能看出问题出在哪里。这个方法帮我解决过很多看起来毫无头绪的颜色异常问题,强烈推荐。