OpenCV C++ 基础学习阶段总结
Learning Resources
Web Pages
Videos
Raw Notes
1. 图片、视频与摄像头输入
1 | Mat img=imread(path); |
imread 把磁盘图片读取为 Mat,imshow 提交窗口显示内容,waitKey 等待按键并处理窗口事件
视频和摄像头通过 VideoCapture 读取:
1 | VideoCapture cap(path); |
USB 摄像头在 Ubuntu 中通常表现为 /dev/video0、/dev/video1 等设备一个摄像头可能暴露多个视频节点,因此程序打不开时要依次确认设备编号、cap.isOpened()、空帧和用户的 video 组权限
2. 基础图像处理
1 | 原图 → 灰度图 → 高斯模糊 → Canny 边缘 → 膨胀/腐蚀 |
1 | cvtColor(img,imggray,COLOR_BGR2GRAY); |
当前练习代码虽然生成了 imggray 和 imgblur,但 Canny 仍然传入 img,如果要让灰度化和高斯模糊真正参与边缘检测,应该写成 Canny(imgblur,imgcanny,25,75)
cvtColor:转换颜色空间,例如 BGR 转灰度或 HSVGaussianBlur:平滑图像并抑制噪声,避免边缘检测产生过多碎边Canny:检测灰度变化明显的位置,输出二值边缘图dilate:扩大白色区域,可连接断裂边缘erode:缩小白色区域,可去除小噪点
使用 Size(1,1) 核时,膨胀和腐蚀几乎不会产生变化
3. 缩放、裁剪与 ROI
resize 用于调整图像尺寸:
1 | resize(img,imageresize,Size(),0.5,0.5); |
当 Size() 为空时,后面的 fx、fy 表示横向和纵向缩放倍率
裁剪通过 ROI 完成:
1 | Rect cut(200,100,300,300); |
Rect(x,y,width,height) 中,x、y 是左上角坐标,坐标原点位于图像左上角,向右为 x 正方向,向下为 y 正方向
1 | Mat view=img(rect); // 与原图共享像素数据 |
4. 绘图与文字
OpenCV 可以创建空白画布并绘制图形:
1 | Mat img(512,512,CV_8UC3,Scalar(255,255,255)); |
Point、Scalar、线宽和填充方式, 默认颜色顺序是 BGR
5. 透视变换
透视变换可以把倾斜的平面区域拉正,常用于文档扫描、卡片矫正、车道鸟瞰图等任务
1 | Point2f src[4]={{529,142},{771,190},{405,395},{674,457}}; |
getPerspectiveTransform 根据四组对应点计算 3×3 透视矩阵,warpPerspective 再根据矩阵重新采样整张图像
四个点必须一一对应,并保持一致顺序,左上、右上、左下、右下
6. HSV 颜色检测与滑动条
颜色检测先把 BGR 图像转换为 HSV:
1 | cvtColor(img,imgHSV,COLOR_BGR2HSV); |
HSV 将颜色、饱和度和亮度分开:
- H:色相,OpenCV 中范围为
0~179 - S:饱和度,范围为
0~255 - V:明度,范围为
0~255
inRange 对每个像素进行范围判断:
1 | Scalar lower(hmin,smin,vmin); |
三个通道全部位于上下界之间时,输出 mask 的对应像素为 255;否则为 0因此 mask 是一张用于表达“保留哪些区域”的单通道二值图
namedWindow 创建窗口,createTrackbar 把滑动条位置与变量绑定:
1 | namedWindow("Trackbars",WINDOW_NORMAL); |
lower 和 upper 放在循环内部,是为了每一轮都读取滑动条更新后的值这里不是程序显式创建了两个线程,而是主循环交替完成:
1 | 读取当前变量 → inRange → imshow → waitKey 处理窗口事件 → 下一轮 |
waitKey(1) 不只是等待按键,还会让 OpenCV 处理窗口重绘、鼠标、键盘和滑动条事件
7. 轮廓与形状识别
完整流程为:
1 | 灰度化 → 模糊 → Canny → 膨胀 → findContours → 面积过滤 → 多边形近似 → 外接矩形 → 形状分类 |
findContours 从二值图中提取白色区域的边界:
1 | vector<vector<Point>> contours; |
contours[i]表示第i个轮廓- 每个轮廓是按边界顺序排列的一组
Point RETR_EXTERNAL只取最外层轮廓CHAIN_APPROX_SIMPLE压缩共线点,只保留关键点
contourArea 把轮廓点视为闭合多边形,计算几何面积:
形状识别部分使用:
1 | float peri=arcLength(contours[i],true); |
arcLength:计算轮廓周长,true代表闭合曲线approxPolyDP:用较少顶点近似轮廓boundingRect:返回能够包围指定点集或非零像素的最小水平外接矩形- 顶点为 3 个时判断为三角形
- 顶点为 4 个时通过宽高比区分正方形和矩形
- 顶点多于 4 个时粗略判断为圆
8. Haar 级联分类器
使用预训练 XML 分类器完成人脸检测:
1 | CascadeClassifier faceCascade; |
detectMultiScale 会在不同尺度上搜索目标,结果以 vector<Rect> 返回,每个 Rect 都是一个检测框之后使用 rectangle 把结果画到原图上
1.1:缩放比例检测器会把图片按不同比例缩放来找不同大小的人脸
- 越接近
1.0,检测更细,可能更准,但更慢 1.1是常用值
10:邻近框数量阈值
- 数值越大,要求越严格,误检少,但可能漏检
- 数值越小,容易检测出更多框,但误检也可能变多
Projects
Project 1:虚拟画板
项目处理链路:
1 | 摄像头画面 |
这个项目把输入、颜色分割、轮廓定位和结果绘制组合成了一个实时系统newPoints 持续保存检测点,所以物体移动后会留下轨迹
当前实现可以继续加强:检查摄像头是否打开、优先选择最大有效轮廓、对 mask 做形态学去噪,并限制历史点数量
1 |
|
Project 2:文档扫描器
项目处理链路:
1 | 读取纸张图片 |
reorder 使用坐标和与坐标差判断四个角点:
x+y最小:左上角x+y最大:右下角x-y最大:右上角x-y最小:左下角
这个项目把轮廓提取、几何判断、透视变换和 ROI 串成了一条完整的传统视觉流水线
需要补充的边界保护是:没有找到四边形时,不能直接调用 reorder;ROI 的宽高也必须为正并位于图像内部
1 |
|
Project 3:车牌检测与裁剪
项目处理链路:
1 | 摄像头画面 |
裁剪:
1 | Mat imgCrop=img(plates[i]); |
plates[i] 是 Rect(x,y,width,height)这行代码创建的是原图对应区域的视图,通常不复制像素如果裁剪图需要脱离原图长期保存或单独修改,可以使用:
1 | Mat imgCrop=img(plates[i]).clone(); |
写入文件
1 | imwrite("Resources/Plates/"+to_string(i)+".png",imgCrop); |
1 |
|
OpenCV 函数功能说明
图片、视频与窗口
imread:从磁盘读取图片并返回一个Mat
1 | cv::Mat cv::imread(const cv::String& filename,int flags=cv::IMREAD_COLOR); |
filename:图片路径,支持由文件扩展名识别的常见图片格式flags:读取模式,IMREAD_COLOR读取 BGR 彩色图,IMREAD_GRAYSCALE读取灰度图,IMREAD_UNCHANGED保留原始通道返回值:读取成功时返回图像矩阵,失败时返回空
Mat,可以使用empty()检查imshow:把图像提交到指定窗口显示
1 | void cv::imshow(const cv::String& winname,cv::InputArray mat); |
winname:窗口名称,同名窗口会复用mat:需要显示的图像imshow负责提交画面,窗口刷新和事件响应通常还需要配合waitKeywaitKey:等待键盘输入并处理 OpenCV 窗口事件
1 | int cv::waitKey(int delay=0); |
delay:等待时间,单位为毫秒,传0表示一直等待,传正数表示至少等待对应时间返回值:检测到按键时返回按键码,没有按键时通常返回
-1除了读取键盘,它还负责窗口重绘、鼠标和 trackbar 等 HighGUI 事件
VideoCapture:打开摄像头或视频文件
1 | cv::VideoCapture cap(index,apiPreference); |
index:摄像头编号,0通常对应/dev/video0filename:视频文件路径apiPreference:视频后端,Ubuntu 下可以指定cv::CAP_V4L2,省略时由 OpenCV 自动选择一个 USB 摄像头可能对应多个
/dev/video*,需要确认哪个节点能输出正常图像流VideoCapture::isOpened:检查摄像头或视频是否成功打开
1 | bool cv::VideoCapture::isOpened() const; |
返回
true表示输入源已经打开返回
false时不能继续假设后续帧有效VideoCapture::read:从视频或摄像头读取下一帧
1 | bool cv::VideoCapture::read(cv::OutputArray image); |
image:用于接收当前帧的Mat返回值:成功读取一帧时返回
true,读取失败或视频结束时返回false即使返回值未被使用,也应该用
img.empty()保护后续图像处理Mat::empty:判断Mat是否没有有效图像数据
1 | bool cv::Mat::empty() const; |
返回
true表示矩阵没有元素或没有绑定有效数据常用于检查
imread、摄像头帧和视频帧是否有效Mat::size:获取图像宽高
1 | cv::Size size=img.size(); |
- 返回的
Size中,width对应列数,height对应行数
基础图像处理
cvtColor:在不同颜色空间之间转换图像
1 | void cv::cvtColor(cv::InputArray src,cv::OutputArray dst,int code,int dstCn=0); |
src:输入图像dst:转换后的输出图像code:转换方式,例如COLOR_BGR2GRAY、COLOR_BGR2HSVdstCn:输出通道数,传0时由code自动决定GaussianBlur:使用高斯核平滑图像并抑制噪声
1 | void cv::GaussianBlur(cv::InputArray src,cv::OutputArray dst,cv::Size ksize,double sigmaX,double sigmaY=0,int borderType=cv::BORDER_DEFAULT); |
src:输入图像dst:模糊后的图像ksize:高斯核大小,宽高通常使用正奇数,例如(3,3)、(5,5)、(7,7)sigmaX:X 方向高斯标准差,传0时由ksize自动计算sigmaY:Y 方向高斯标准差,传0时使用sigmaXborderType:计算图像边缘时的像素补齐方式,通常使用BORDER_DEFAULTCanny:检测灰度变化明显的边缘并输出二值边缘图
1 | void cv::Canny(cv::InputArray image,cv::OutputArray edges,double threshold1,double threshold2,int apertureSize=3,bool L2gradient=false); |
image:输入图像,通常先灰度化和模糊edges:输出的单通道二值边缘图threshold1:低阈值,低于该值的梯度通常被舍弃threshold2:高阈值,高于该值的梯度被视为强边缘apertureSize:Sobel 算子的孔径大小,通常为3L2gradient:是否使用更精确的 L2 梯度计算,默认使用较快的 L1 近似getStructuringElement:创建膨胀和腐蚀使用的结构元素
1 | cv::Mat cv::getStructuringElement(int shape,cv::Size ksize,cv::Point anchor=cv::Point(-1,-1)); |
shape:核形状,常用MORPH_RECT、MORPH_CROSS、MORPH_ELLIPSEksize:核的宽和高,Size(1,1)基本不会改变图像,常用Size(3,3)或Size(5,5)anchor:锚点位置,Point(-1,-1)表示使用核中心dilate:扩大二值图中的白色区域
1 | void cv::dilate(cv::InputArray src,cv::OutputArray dst,cv::InputArray kernel,cv::Point anchor=cv::Point(-1,-1),int iterations=1,int borderType=cv::BORDER_CONSTANT,const cv::Scalar& borderValue=cv::morphologyDefaultBorderValue()); |
src:输入图像dst:膨胀结果kernel:结构元素anchor:核的锚点,默认使用中心iterations:膨胀次数,次数越多白色区域扩张越明显borderType:图像边界的像素扩展方式borderValue:固定边界模式使用的填充值常用于连接断裂边缘和填补小空隙
erode:缩小二值图中的白色区域
1 | void cv::erode(cv::InputArray src,cv::OutputArray dst,cv::InputArray kernel,cv::Point anchor=cv::Point(-1,-1),int iterations=1,int borderType=cv::BORDER_CONSTANT,const cv::Scalar& borderValue=cv::morphologyDefaultBorderValue()); |
src:输入图像dst:腐蚀结果kernel:结构元素iterations:腐蚀次数,次数越多白色区域缩小越明显borderType:图像边界的像素扩展方式borderValue:固定边界模式使用的填充值常用于去除小白点和分离粘连区域
resize:把图像缩放到指定大小或指定比例
1 | void cv::resize(cv::InputArray src,cv::OutputArray dst,cv::Size dsize,double fx=0,double fy=0,int interpolation=cv::INTER_LINEAR); |
src:输入图像dst:缩放后的图像dsize:目标尺寸,传空Size()时由fx、fy计算fx:水平方向缩放倍率fy:垂直方向缩放倍率interpolation:插值方式,缩小时常用INTER_AREA,放大时常用INTER_LINEAR或INTER_CUBIC
ROI、复制与绘图
Mat::operator()(Rect):创建指定矩形区域的 ROI 视图
1 | cv::Mat roi=img(cv::Rect(x,y,width,height)); |
x、y:ROI 左上角坐标width、height:ROI 宽度和高度返回的
Mat默认与原图共享像素数据,修改 ROI 会影响原图对应区域ROI 必须完整位于图像范围内,否则会触发断言错误
Mat::clone:复制一份独立的图像数据
1 | cv::Mat copy=roi.clone(); |
返回值:与原图内容相同但拥有独立存储空间的新
Mat当裁剪结果需要长期保存、单独修改或原图即将释放时使用
circle:在图像上绘制圆或实心圆
1 | void cv::circle(cv::InputOutputArray img,cv::Point center,int radius,const cv::Scalar& color,int thickness=1,int lineType=cv::LINE_8,int shift=0); |
img:被绘制的图像center:圆心坐标radius:半径color:BGR 颜色thickness:线宽,传FILLED或负数表示填充lineType:线型,LINE_AA可以获得抗锯齿效果rectangle:在图像上绘制矩形
1 | void cv::rectangle(cv::InputOutputArray img,cv::Point pt1,cv::Point pt2,const cv::Scalar& color,int thickness=1,int lineType=cv::LINE_8,int shift=0); |
pt1:矩形左上角pt2:矩形右下角color:BGR 颜色thickness:线宽,传FILLED表示填充矩形也可以直接把
Rect作为矩形参数line:在两个点之间绘制线段
1 | void cv::line(cv::InputOutputArray img,cv::Point pt1,cv::Point pt2,const cv::Scalar& color,int thickness=1,int lineType=cv::LINE_8,int shift=0); |
pt1、pt2:线段起点和终点color:BGR 颜色thickness:线宽lineType:线型putText:把英文、数字和 OpenCV Hershey 字体字符绘制到图像上
1 | void cv::putText(cv::InputOutputArray img,const cv::String& text,cv::Point org,int fontFace,double fontScale,cv::Scalar color,int thickness=1,int lineType=cv::LINE_8,bool bottomLeftOrigin=false); |
text:要绘制的字符串org:文本基线左下角坐标fontFace:字体,例如FONT_HERSHEY_PLAIN、FONT_HERSHEY_DUPLEXfontScale:字体缩放比例color:BGR 颜色thickness:笔画宽度- 默认
putText不直接支持中文字体
透视变换
getPerspectiveTransform:根据四组对应点计算3×3透视变换矩阵
1 | cv::Mat cv::getPerspectiveTransform(const cv::Point2f src[],const cv::Point2f dst[],int solveMethod=cv::DECOMP_LU); |
src:原图中的四个点dst:目标图中的四个对应点solveMethod:求解线性方程组的方法,通常使用默认值四个源点和目标点必须顺序对应
用户当前代码使用
Point2f[4]静态数组,接口类型必须匹配Point2fwarpPerspective:根据透视矩阵把整张图像映射到新视角
1 | void cv::warpPerspective(cv::InputArray src,cv::OutputArray dst,cv::InputArray M,cv::Size dsize,int flags=cv::INTER_LINEAR,int borderMode=cv::BORDER_CONSTANT,const cv::Scalar& borderValue=cv::Scalar()); |
src:输入图像dst:透视变换后的输出图像M:3×3透视矩阵dsize:输出图像尺寸flags:插值方式,默认使用双线性插值borderMode:映射到原图范围外时的边界处理方式borderValue:使用固定边界时的填充值,默认是黑色
HSV、掩膜与交互控件
inRange:判断每个像素的所有通道是否位于指定上下界之间
1 | void cv::inRange(cv::InputArray src,cv::InputArray lowerb,cv::InputArray upperb,cv::OutputArray dst); |
src:输入图像,颜色检测时通常使用 HSV 图lowerb:每个通道的下界upperb:每个通道的上界dst:单通道二值 mask,符合全部范围条件时为255,否则为0namedWindow:创建一个用于显示图像或放置 trackbar 的窗口
1 | void cv::namedWindow(const cv::String& winname,int flags=cv::WINDOW_AUTOSIZE); |
winname:窗口名称flags:窗口模式,WINDOW_AUTOSIZE自动匹配图像大小,WINDOW_NORMAL允许调整窗口尺寸(640,200)在 C++ 中是逗号表达式,不是窗口尺寸resizeWindow:设置WINDOW_NORMAL窗口的大小
1 | void cv::resizeWindow(const cv::String& winname,int width,int height); |
winname:已经创建的窗口名称width、height:目标窗口宽度和高度createTrackbar:在窗口中创建滑动条并绑定整数值
1 | int cv::createTrackbar(const cv::String& trackbarname,const cv::String& winname,int* value,int count,cv::TrackbarCallback onChange=0,void* userdata=0); |
trackbarname:滑动条名称winname:滑动条所在窗口value:与滑动条绑定的整数变量地址count:滑动条最大值,最小值默认为0onChange:数值变化时调用的回调函数,传0表示不使用回调userdata:传给回调函数的自定义数据- 当前代码没有使用回调,而是在循环中不断读取被 trackbar 更新的变量
轮廓与形状分析
findContours:从二值图中提取轮廓点集合
1 | void cv::findContours(cv::InputArray image,cv::OutputArrayOfArrays contours,cv::OutputArray hierarchy,int mode,int method,cv::Point offset=cv::Point()); |
image:输入二值图,通常由阈值、inRange或Canny得到contours:输出轮廓集合,每个轮廓都是一个vector<Point>hierarchy:输出轮廓层级,每项记录相邻、子轮廓和父轮廓索引mode:轮廓检索方式,RETR_EXTERNAL只保留最外层轮廓method:点保存方式,CHAIN_APPROX_SIMPLE会压缩共线点offset:给所有轮廓点增加的坐标偏移量drawContours:把一个或多个轮廓绘制到图像上
1 | void cv::drawContours(cv::InputOutputArray image,cv::InputArrayOfArrays contours,int contourIdx,const cv::Scalar& color,int thickness=1,int lineType=cv::LINE_8,cv::InputArray hierarchy=cv::noArray(),int maxLevel=INT_MAX,cv::Point offset=cv::Point()); |
image:被绘制的图像contours:轮廓集合contourIdx:需要绘制的轮廓索引,传-1表示绘制全部color:BGR 颜色thickness:线宽,传负数可以填充轮廓内部hierarchy、maxLevel:控制按层级绘制的范围contourArea:计算轮廓围成的几何面积
1 | double cv::contourArea(cv::InputArray contour,bool oriented=false); |
contour:轮廓点集合oriented:传false返回绝对面积,传true返回带方向符号的面积内部根据轮廓点形成的闭合多边形计算面积,不等同于简单统计白色像素数量
arcLength:计算曲线长度或闭合轮廓周长
1 | double cv::arcLength(cv::InputArray curve,bool closed); |
curve:输入点集closed:传true时把最后一个点和第一个点连接,按闭合轮廓计算返回值:曲线长度
approxPolyDP:使用 Douglas-Peucker 算法减少轮廓点并得到多边形近似
1 | void cv::approxPolyDP(cv::InputArray curve,cv::OutputArray approxCurve,double epsilon,bool closed); |
curve:原始轮廓approxCurve:近似后的点集epsilon:允许的最大近似误差,常写成轮廓周长的某个比例,例如0.02*periclosed:是否把曲线视为闭合轮廓epsilon越大,近似越粗,保留的顶点通常越少boundingRect:计算能够包围点集或非零像素的最小水平外接矩形
1 | cv::Rect cv::boundingRect(cv::InputArray array); |
array:二维点集或单通道非零像素图返回值:
Rect(x,y,width,height)水平外接矩形不会随物体角度旋转
Rect::tl和Rect::br:获取矩形左上角和右下角
1 | cv::Point topLeft=rect.tl(); |
tl()返回(x,y)br()返回(x+width,y+height)- 常与
rectangle配合绘制检测框
级联检测与图片保存
CascadeClassifier::load:从 XML 文件加载 Haar 或 LBP 级联分类器
1 | bool cv::CascadeClassifier::load(const cv::String& filename); |
filename:分类器 XML 文件路径返回值:加载成功返回
true,失败返回false分类器加载失败时不能继续调用检测逻辑
CascadeClassifier::detectMultiScale:在多个缩放尺度上检测目标
1 | void cv::CascadeClassifier::detectMultiScale(cv::InputArray image,std::vector<cv::Rect>& objects,double scaleFactor=1.1,int minNeighbors=3,int flags=0,cv::Size minSize=cv::Size(),cv::Size maxSize=cv::Size()); |
image:输入图像objects:输出检测框集合scaleFactor:相邻尺度之间的缩放比例,越接近1.0搜索越细但速度越慢minNeighbors:候选框需要获得的邻近支持数量,越大越严格,误检通常越少flags:旧版兼容参数,通常传0minSize:允许检测的最小目标尺寸maxSize:允许检测的最大目标尺寸imwrite:把图像编码并保存到磁盘
1 | bool cv::imwrite(const cv::String& filename,cv::InputArray img,const std::vector<int>& params=std::vector<int>()); |
filename:输出路径,文件扩展名决定编码格式img:需要保存的图像params:可选编码参数,例如 JPEG 质量或 PNG 压缩级别- 返回值:保存成功返回
true,失败返回false - 连续视频帧使用相同文件名时会不断覆盖旧图片
API Map
| 任务 | 主要 API | 输出 |
|---|---|---|
| 读取图片 | imread |
Mat |
| 读取视频/摄像头 | VideoCapture、read |
连续图像帧 |
| 颜色空间转换 | cvtColor |
灰度图或 HSV 图 |
| 平滑去噪 | GaussianBlur |
模糊图 |
| 边缘检测 | Canny |
二值边缘图 |
| 形态学处理 | dilate、erode |
修整后的二值图 |
| 颜色范围筛选 | inRange |
mask |
| 查找轮廓 | findContours |
vector<vector<Point>> |
| 轮廓分析 | contourArea、arcLength、approxPolyDP |
面积、周长、近似多边形 |
| 定位区域 | boundingRect、ROI |
检测框或裁剪图 |
| 透视矫正 | getPerspectiveTransform、warpPerspective |
拉正后的图像 |
| 传统目标检测 | CascadeClassifier、detectMultiScale |
vector<Rect> |
Stage Summary
- 使用
Mat表示图像,读取和显示图片、视频与摄像头画面 - 使用灰度、模糊、Canny、膨胀和腐蚀进行基础预处理
- 使用 HSV、
inRange和 trackbar 交互式寻找颜色阈值 - 使用轮廓面积、周长、多边形近似和外接矩形分析形状
- 使用四点透视变换完成卡片和文档矫正
- 使用 Haar 级联分类器完成人脸或车牌区域检测
- 使用 ROI 裁剪目标区域
- 将多个基础 API 组合成虚拟画板、文档扫描器和车牌检测项目
- 标题: OpenCV C++ 基础学习阶段总结
- 作者: xyzfrozen
- 创建于 : 2026-08-17 10:06:33
- 更新于 : 2026-08-23 18:47:13
- 链接: https://xyzfrozen.github.io/xyzfrozen/OpenCV-C-基础学习阶段总结/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。