【openCV】图像缩放,翻转,旋转,视频文件/摄像头读取/保存

一.图像放缩与插值------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 来算出外接矩形。**其实你可以用几何方法算出四个角旋转后的最左、最右、最上、最下,然后算出新的宽高,并调整变换矩阵里的平移量,让图片居中显示。

  1. 如果你希望旋转后图像完整显示(不裁剪),可以使用下面的代码
  2. 它会自动计算旋转后完整图像所需的新尺寸,并调整变换矩阵使图像居中。

在 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():释放资源。
  1. 最简单的摄像头实时显示
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;
}
  1. 最简单的视频文件播放
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;
}

非常厉害!!!

获取/设置视频属性

  1. 获取视频属性
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;
}
  1. 设置视频属性(例如修改分辨率)
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() 或直接查看帧尺寸来确认。

  1. 跳转到视频的特定位置
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 把原始音频(例如从摄像头麦克风或单独录音文件)合并进去

相关推荐
IronMurphy9 小时前
AI Agent 学习笔记 Day 1:大模型基础、API 调用与 Prompt 工程
人工智能·笔记·学习
ZHW_AI课题组9 小时前
基于PCA与HOG特征融合的热轧钢带缺陷检测
人工智能·python·机器学习
MediaTea9 小时前
DL:扩散模型的基本原理与 PyTorch 实现
人工智能·pytorch·python·深度学习·机器学习
janeysj9 小时前
Jupyter和LangSmith——AI Agent开发调试监控工具
ide·人工智能·jupyter
ishangy9 小时前
AI视觉赋能智慧矿山:新一代安全防控体系解决方案
人工智能·边缘计算·ai视觉·智慧矿山·ai视觉监测·智能防控
CeshirenTester9 小时前
大厂校招变了:AI 能力正在进入笔试和面试
人工智能·面试·职场和发展
hughnz9 小时前
AI驱动自动化和智能体AI-加速钻头创新
运维·人工智能·自动化
薛定猫AI9 小时前
【深度解析】AI Coding 模型竞速:从 Claude Mythos 安全编码到 GPT-5.6 传闻,如何落地代码审查智能体
人工智能·gpt·安全
ZengLiangYi9 小时前
从零实现 Embedding 服务:文本转向量
人工智能·后端