一.图像放缩与插值------cv::resize
cv::resize 是 OpenCV 中用来改变图像尺寸(放大或缩小)的函数。你可以指定目标图像的宽高,也可以给定缩放比例,还能选择不同的插值算法来控制缩放时的像素计算方式。
cpp
void cv::resize(
InputArray src, // 输入图像
OutputArray dst, // 输出图像(尺寸由后面的参数决定)
Size dsize, // 目标图像大小 (width, height)
double fx = 0, // 水平方向缩放因子(当 dsize 为 (0,0) 时使用)
double fy = 0, // 垂直方向缩放因子
int interpolation = INTER_LINEAR // 插值方法
);
参数解释
- src:原始图像。
- dst:缩放后的图像,会按照给定的尺寸或比例重新分配内存。
- dsize:目标尺寸,类型是 cv::Size(width, height)。如果你明确知道想要的宽高,就直接填这里。如果 dsize = Size(),那么就会根据 fx 和 fy 自动计算目标尺寸。
- fx, fy:水平、垂直的缩放系数。例如 fx=0.5 表示宽度变为原来一半,fy=2 表示高度变为原来的两倍。只有当 dsize 为Size()时,这两个参数才起作用。
- interpolation:插值方式,决定缩放时如何填充新像素的值。默认是 INTER_LINEAR(双线性插值),适合大多数情况。
常用的插值方法有:
-
最近邻插值(INTER_NEAREST):直接取离目标像素最近的原始像素的值,速度最快,但放大后会有明显锯齿,适合二值图像或需要保留硬边缘的场合。
-
双线性插值(INTER_LINEAR):取目标像素周围 2x2 邻域的像素值进行加权平均,结果平滑,是大多数缩放任务(尤其是放大)的默认方法。
-
双三次插值(INTER_CUBIC):取周围 4x4 邻域的像素进行三次样条插值,结果比双线性更平滑,但计算量更大,适合高质量放大。
-
区域插值(INTER_AREA):缩小图像时使用,通过计算像素区域内的均值来得到新像素,可以有效避免混叠(波纹)现象,适合缩小照片或制作图像金字塔。
-
Lanczos 插值(INTER_LANCZOS4):基于 Lanczos 核(8x8 邻域)的高质量插值,能保留更多细节,但速度最慢,适用于对精度要求极高的图像缩放。
其中,最近邻和双线性最常用,区域插值推荐用于缩小,双三次和 Lanczos 用于高质量放大。
一般规则:
- 缩小图像 → INTER_AREA 效果更好。
- 放大图像 → INTER_LINEAR 或 INTER_CUBIC。
- 速度第一 → INTER_NEAREST。
我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
// 读取图像文件 "test.jpeg",返回 cv::Mat 对象(默认 BGR 三通道,8位)
cv::Mat img = cv::imread("test.jpeg");
// 方法1:直接指定目标图像的宽度和高度(300x200)
cv::Mat resized1;
// 参数:源图,目标图,目标尺寸(宽=300,高=200)
// 未指定插值方法,默认使用双线性插值(INTER_LINEAR)
cv::resize(img, resized1, cv::Size(300, 200));
// 方法2:通过缩放因子指定缩放比例(不使用插值参数,将使用默认 INTER_LINEAR)
cv::Mat resized2;
// 第三个参数 dsize = Size() 表示根据 fx,fy 自动计算目标尺寸
// fx=0.5 表示宽度变为原来的一半,fy=0.5 表示高度变为原来的一半
// 此处未写第六个参数,自动采用 INTER_LINEAR
cv::resize(img, resized2, cv::Size(), 0.5, 0.5);
// 方法3:与方法2相同,但显式指定了双线性插值(INTER_LINEAR)
cv::Mat resized3;
// 效果同 resized2,但明确写出插值方法,代码可读性更好
cv::resize(img, resized3, cv::Size(), 0.5, 0.5, cv::INTER_LINEAR);
// 方法4:缩小图像,使用 INTER_AREA 插值,有助于避免波纹效应(aliasing)
cv::Mat resized4;
// 直接指定目标尺寸 320x240,fx,fy 不再需要(填 0 或忽略)
// INTER_AREA 在图像缩小时通常能保留更好的局部信息
cv::resize(img, resized4, cv::Size(320, 240), 0, 0, cv::INTER_AREA);
// 显示原始图像以及所有缩放后的结果
cv::imshow("Original", img);
cv::imshow("Resized1", resized1);
cv::imshow("Resized2", resized2);
cv::imshow("Resized3", resized3);
cv::imshow("Resized4", resized4);
// 等待任意按键后关闭窗口
cv::waitKey(0);
return 0;
}

非常厉害了。
二.图像翻转------cv::flip
cv::flip 是 OpenCV 中用于翻转图像(镜像)的函数。它可以沿着水平轴、垂直轴或同时两个轴进行翻转。
cpp
void cv::flip(InputArray src, OutputArray dst, int flipCode);
src:输入图像。
dst:输出图像(大小和类型与 src 相同)。
flipCode:翻转模式的控制码,有三个取值:
- **0:**绕 x 轴翻转(垂直翻转,上下颠倒)。图像的第一行变成最后一行,最后一行变成第一行。
- **正数(如 1):**绕 y 轴翻转(水平翻转,左右镜像)。图像的第一列变成最后一列,最后一列变成第一列。
- **负数(如 -1):**同时绕 x 轴和 y 轴翻转(相当于旋转 180 度)。水平和垂直翻转的组合。
我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::imread("test.jpg");
if (img.empty()) return -1;
cv::Mat horizontal, vertical, both;
cv::flip(img, horizontal, 1); // 水平翻转(左右镜像)
cv::flip(img, vertical, 0); // 垂直翻转(上下颠倒)
cv::flip(img, both, -1); // 同时翻转(旋转180度)
cv::imshow("原图", img);
cv::imshow("水平翻转", horizontal);
cv::imshow("垂直翻转", vertical);
cv::imshow("180度翻转", both);
cv::waitKey(0);
return 0;
}

三.图像旋转
OpenCV 中实现图像旋转主要有两种方式:按 90° 倍数的简单旋转 和任意角度的旋转。
3.1.cv::rotate - 简单旋转90/180/270度
这是 OpenCV 3.0 之后加入的函数,专门用于旋转 90°、180° 或 270°。
cpp
void cv::rotate(InputArray src, OutputArray dst, int rotateCode);
rotateCode 可选值:
- cv::ROTATE_90_CLOCKWISE:顺时针旋转 90°
- cv::ROTATE_180:顺时针旋转 180°
- cv::ROTATE_90_COUNTERCLOCKWISE:逆时针旋转 90°(等价于顺时针 270°)
我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::imread("test.jpg");
if (img.empty()) return -1;
cv::Mat rot90, rot180, rot270;
// 顺时针旋转 90 度
cv::rotate(img, rot90, cv::ROTATE_90_CLOCKWISE);
// 旋转 180 度
cv::rotate(img, rot180, cv::ROTATE_180);
// 顺时针旋转 270 度(等价于逆时针 90 度)
cv::rotate(img, rot270, cv::ROTATE_90_COUNTERCLOCKWISE);
cv::imshow("原图", img);
cv::imshow("顺时针90度", rot90);
cv::imshow("180度", rot180);
cv::imshow("顺时针270度", rot270);
cv::waitKey(0);
return 0;
}

3.2.任意角度旋转 ------ 仿射变换 (cv::warpAffine + cv::getRotationMatrix2D)
为什么要用仿射变换?
你想把一张图片旋转一个非 90° 倍数的角度,比如 35°。90°、180°、270° 的旋转很简单,就是把行列重新排列一下(cv::rotate 干的事)。但 35° 旋转时,原来的像素点会落在"半像素"的位置上,图像边界也变成了倾斜的,原来的矩形图像要变成更大的矩形才能完全装下。这种变换无法通过简单的行列交换实现,必须用更通用的"仿射变换"。
仿射变换是什么?
你可以想象一张图片是一张纸,你按住一个点(旋转中心),把纸转动一个角度。转动后,纸上的每个点都移动到了新位置。这种"线性变换 + 平移"就是仿射变换。在 OpenCV 里,你需要告诉它三件事:
- 绕哪个点旋转(旋转中心)。
- 转多少度(正数逆时针,负数顺时针)。
- 要不要同时放大或缩小(缩放因子,通常为 1)。
实现步骤(思想层面)
计算变换矩阵
OpenCV 提供了一个函数 cv::getRotationMatrix2D,你只要给它"中心点、角度、缩放",它就会给你一个 2×3 的矩阵。这个矩阵里面包含了旋转和缩放的数学公式,你不用自己推导。
应用变换:然后用 cv::warpAffine 函数,把原图和那个矩阵扔进去,它就会根据矩阵计算出新图像的每个像素应该取原图的哪个位置,生成旋转后的图像。
话不多说,我们直接看例子
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 读取图像
cv::Mat img = cv::imread("test.jpg");
if (img.empty()) {
std::cout << "无法读取 test.png,请检查文件路径" << std::endl;
return -1;
}
// 2. 定义旋转参数
cv::Point2f center(img.cols / 2.0f, img.rows / 2.0f); // 旋转中心:图像中心
double angle = 35.0; // 旋转角度(正值 = 逆时针)
double scale = 1.0; // 缩放比例(1 = 不缩放)
// 3. 生成旋转矩阵(2x3 仿射矩阵)
cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, scale);
// 4. 执行仿射变换(输出图像尺寸与原图相同,因此边缘会被裁剪)
cv::Mat rotated;
cv::warpAffine(img, rotated, rot_mat, img.size());
// 5. 显示原图和旋转后的图
cv::imshow("原始图像", img);
cv::imshow("旋转35度(原尺寸,有裁剪)", rotated);
cv::waitKey(0);
return 0;
}

可以看到,当你旋转一个矩形图片时,旋转后的矩形四个角会超出原来的边界。如果你还按原图的宽高创建输出图像,超出部分就会被剪掉,就像把一张照片放进一个比它小的相框,边缘没了。
解决方法:
你需要计算旋转后整个图片所占的"新矩形"的尺寸。
OpenCV 提供了一个简单的工具**:用 cv::RotatedRect 来算出外接矩形。**其实你可以用几何方法算出四个角旋转后的最左、最右、最上、最下,然后算出新的宽高,并调整变换矩阵里的平移量,让图片居中显示。
- 如果你希望旋转后图像完整显示(不裁剪),可以使用下面的代码
- 它会自动计算旋转后完整图像所需的新尺寸,并调整变换矩阵使图像居中。
在 OpenCV 中,cv::RotatedRect 是一个表示旋转矩形的类,它由三个参数定义:**中心点、矩形的尺寸(宽和高)、旋转角度。**你可以用它来计算一个旋转矩形在水平轴对齐时的"外接矩形"(即能完整包住这个旋转矩形的最小水平矩形)。
在图像旋转的场景中,我们想要知道:如果把原始图像(一个矩形)绕中心旋转某个角度后,旋转后的图像所占的"新矩形"有多大(不丢失任何像素),就可以用 cv::RotatedRect 来描述旋转后的图像边界,然后调用 boundingRect() 得到这个外接矩形的宽高,作为输出图像的尺寸。
使用方法
1.构造一个 cv::RotatedRect 对象,需要提供三个信息:
- 中心点:cv::Point2f
- 矩形的宽和高:cv::Size2f(通常是原图的宽高)
- 旋转角度:浮点数(单位:度,正值表示逆时针)
2.调用 boundingRect() 方法,返回一个 cv::Rect,它是这个旋转矩形在水平方向上的最小外接矩形(即能完全包含旋转矩形的、边与坐标轴平行的矩形)。
3.利用外接矩形的宽高作为新图像的尺寸,并调整旋转矩阵的平移分量,使旋转后的图像居中显示。也就是旋转完图像后,把它挪到新画布的中间,不要偏到角落去。
想象一下:
-
你把一张照片放在桌子中间,绕中心旋转 35 度。照片的四个角会伸到桌子外面去(就像原尺寸画布装不下,会被裁剪)。
-
**现在你换了一张更大的桌子(新画布),能把整张照片放下。但照片还是按照原来的位置放的话,可能会偏到一边,不在大桌子的正中央。**就比如说,原来是在(2,2)为旋转中心进行旋转的,图像中心就是(2,2),现在外接矩形变大了,中心变成了(3,3),那么我的图像也要跟着移动到(3,3)去,才能完整显示我们的这个图像。
-
那两行代码就是"把照片往中间挪一挪"的动作:计算需要往右移多少、往下移多少,让照片刚好在大桌子的正中央。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("test.jpg");
if (img.empty()) { std::cout << "无法读取 test.png" << std::endl; return -1; }
cv::Point2f center(img.cols / 2.0f, img.rows / 2.0f);
double angle = 35.0;
double scale = 1.0;
// 生成旋转矩阵
cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, scale);
// 计算旋转后完整图像的新尺寸
cv::Rect bbox = cv::RotatedRect(center, img.size(), angle).boundingRect();
// 调整旋转矩阵中的平移部分,使得图像居中
rot_mat.at<double>(0, 2) += bbox.width / 2.0 - center.x;//rot_mat.at<double>(0, 2)是旋转中心的x坐标
rot_mat.at<double>(1, 2) += bbox.height / 2.0 - center.y;//rot_mat.at<double>(1, 2)是旋转中心的x坐标
// 执行仿射变换,输出尺寸为新矩形的大小
cv::Mat rotated;
cv::warpAffine(img, rotated, rot_mat, bbox.size());
cv::imshow("原始图像", img);
cv::imshow("旋转35度(完整显示)", rotated);
cv::waitKey(0);
return 0;
}

三.视频文件摄像头使用
在 OpenCV 中,无论是读取本地视频文件还是从摄像头获取实时画面,主要都依赖于 cv::VideoCapture 类。下面我会分别讲解这两种场景的用法,并给出完整的 C++ 代码示例。
核心类:cv::VideoCapture
VideoCapture 提供了统一的接口来处理不同的视频源:
读取视频文件:构造函数或 open() 方法传入文件路径。
打开摄像头:传入摄像头 ID(通常 0 表示默认摄像头,如果有多个摄像头可依次用 1、2...)。
常用方法:
- isOpened():检查是否成功打开。
- read(frame) 或 operator>>:读取下一帧图像到 cv::Mat。
- get(propId) / set(propId, value):获取/设置视频属性(宽度、高度、帧率、当前帧位置等)。
- release():释放资源。
- 最简单的摄像头实时显示
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::VideoCapture cap(0); // 打开默认摄像头
if (!cap.isOpened())
{
return -1; // 打开失败则退出
}
cv::Mat frame;
while (true)
{
cap >> frame; // 抓取一帧,等价于cap.read(frame)
if (frame.empty())
{
break; // 防止意外空帧
}
cv::imshow("Camera", frame); // 显示
if (cv::waitKey(1) == 27) break; // 按 ESC 退出
}
cap.release();//释放资源
return 0;
}
- 最简单的视频文件播放
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::VideoCapture cap("304019.mov"); // 改成你的视频路径
if (!cap.isOpened())
{
return -1;
}
cv::Mat frame;
while (true)
{
cap >> frame;
if (frame.empty())
{
break; // 视频播放完毕
}
cv::imshow("Video", frame);
if (cv::waitKey(33) == 27) break; // ESC 退出, 33ms ≈ 30fps
}
cap.release();//释放资源
return 0;
}
非常厉害!!!
获取/设置视频属性
- 获取视频属性
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::VideoCapture cap("304019.mov");
if (!cap.isOpened()) return -1;
// 获取各种属性
double width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
double height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = cap.get(cv::CAP_PROP_FPS);
double frameCount = cap.get(cv::CAP_PROP_FRAME_COUNT);
std::cout << "分辨率: " << width << "x" << height << std::endl;
std::cout << "帧率: " << fps << std::endl;
std::cout << "总帧数: " << frameCount << std::endl;
return 0;
}

- 设置视频属性(例如修改分辨率)
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::VideoCapture cap(0); // 打开摄像头
if (!cap.isOpened()) return -1;
// 尝试设置分辨率为 640x480
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
// 读取一帧,检查实际设置后的分辨率
cv::Mat frame;
cap >> frame;
std::cout << "实际宽度: " << frame.cols << std::endl;
std::cout << "实际高度: " << frame.rows << std::endl;
return 0;
}
注意:set() 只是"请求",实际是否生效取决于摄像头驱动或视频编解码器。建议设置后通过 get() 或直接查看帧尺寸来确认。
- 跳转到视频的特定位置
cpp
// 跳转到第 100 帧
cap.set(cv::CAP_PROP_POS_FRAMES, 100);
// 或者跳转到 30 秒处(单位:毫秒)
cap.set(cv::CAP_PROP_POS_MSEC, 30000);
以下按条目列出 OpenCV 中 VideoCapture 常用的属性 ID:
- CAP_PROP_FRAME_WIDTH:帧宽度(像素)
- CAP_PROP_FRAME_HEIGHT:帧高度(像素)
- CAP_PROP_FPS:帧率(每秒帧数)
- CAP_PROP_FRAME_COUNT:总帧数(仅对视频文件有效)
- CAP_PROP_POS_FRAMES:当前帧位置(从 0 开始计数的帧索引)
- CAP_PROP_POS_MSEC:当前播放位置(毫秒)
- CAP_PROP_BRIGHTNESS:摄像头亮度(仅对摄像头有效)
- CAP_PROP_CONTRAST:摄像头对比度
- CAP_PROP_SATURATION:摄像头饱和度
- CAP_PROP_HUE:摄像头色调
- CAP_PROP_GAIN:摄像头增益
- CAP_PROP_EXPOSURE:摄像头曝光度
- CAP_PROP_FOURCC:视频编码格式(四字符代码)
- CAP_PROP_BACKEND:当前使用的后端(如 V4L2、DSHOW 等)
四.视频处理与保存
cv::VideoWriter 是 OpenCV 中用来将图像帧序列写入视频文件的类。
它的使用流程与 cv::VideoCapture 对称:一个负责读,一个负责写。
主要用途
- 从摄像头或处理后的图像生成视频文件(如 MP4、AVI、MOV 等)。
- 保存实时处理的结果(如边缘检测、目标跟踪后的画面)。
cpp
cv::VideoWriter writer(
const string& filename, // 输出文件名(如 "output.avi")
int fourcc, // 编码器四字符代码
double fps, // 帧率
Size frameSize, // 每帧的宽度和高度
bool isColor = true // 是否彩色(true=彩色,false=灰度)
);
核心成员函数
- open(filename, fourcc, fps, frameSize, isColor):重新打开或修改输出文件。
- isOpened():检查是否成功初始化(例如编码器是否可用、路径是否可写)。
- write(frame) 或 writer << frame:将 cv::Mat 帧写入文件。帧的尺寸和类型必须与构造时设置的完全一致。
- release():关闭文件并释放资源(析构函数会自动调用)。
编码格式(fourcc)
fourcc 是一个 4 字节字符,用于指定编码器。常见写法:
cpp
cv::VideoWriter::fourcc('M','J','P','G') // Motion JPEG,扩展名常用 .avi
cv::VideoWriter::fourcc('X','V','I','D') // XVID,.avi
cv::VideoWriter::fourcc('H','2','6','4') // H.264,.mp4
cv::VideoWriter::fourcc('a','v','c','1') // H.264 另一种写法,.mp4
如果你不确定系统支持哪些编码器,可以尝试 MJPG,兼容性最好。
我们看看一个简单的例子
cpp
#include <opencv2/opencv.hpp>
int main()
{
// 打开默认摄像头(ID=0)
cv::VideoCapture cap(0);
// 创建视频写入器
// 参数:输出文件名,编码格式(MJPG),帧率25,画面尺寸640x480
cv::VideoWriter writer("output.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 25, cv::Size(640, 480));
cv::Mat frame; // 存放每帧图像
while (cap.read(frame)) // 循环读取摄像头帧,读到空帧时退出
{
writer.write(frame); // 将当前帧写入视频文件
cv::imshow("frame", frame); // 显示画面
if (cv::waitKey(1) == 27) // 等待1毫秒,按ESC键(ASCII码27)退出
break;
}
// 程序结束,cap和writer会自动释放资源
return 0;
}
这个就非常简单啊!!!
只不过:cv::VideoWriter 只保存视频画面,不包含音频。
如果您需要保存带声音的视频,可以使用 FFmpeg 命令行合并音视频(最简单),先用 OpenCV 保存无声视频文件 video.avi,再用 FFmpeg 把原始音频(例如从摄像头麦克风或单独录音文件)合并进去