opencv入门指南
OpenCV 入门指南 (C/C++):开启计算机视觉之旅 👁️🗨️
OpenCV (Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习软件库。它包含数千种优化的算法,为各种计算机视觉应用提供了丰富的工具集。无论你是想进行图像处理、物体检测、人脸识别,还是进行更复杂的视频分析,OpenCV 都是一个强大且灵活的选择。本指南将带你使用 C/C++ 语言入门 OpenCV。
📖 OpenCV 是什么?为什么选择它?
简单来说,OpenCV 是一套可以让你用编程方式“看”和“理解”图像与视频的工具箱。
为什么选择 OpenCV?
- 功能强大且全面:提供了从基本的图像读取、显示、保存,到复杂的图像处理(滤波、边缘检测、形态学操作)、特征提取、物体检测、视频分析、相机标定、3D重建,乃至机器学习模块。
- 跨平台:支持 Windows, Linux, macOS, Android, iOS 等主流操作系统。
- 高性能:许多算法都针对速度进行了优化,并且可以利用多核处理器。对于实时应用,性能至关重要。
- C/C++ 接口:虽然 OpenCV 也支持 Python、Java 等语言,但其核心是用 C++ 编写的,提供了高效的 C/C++ API。对于追求性能的应用,C++ 是首选。
- 庞大的社区和丰富的文档:遇到问题时,很容易找到解决方案和学习资源。
- 商业友好型许可 (BSD):允许你在商业产品中免费使用。
🛠️ 环境搭建:准备好你的第一个 OpenCV 程序
在开始编码之前,你需要先安装 OpenCV。安装过程因操作系统而异。
基本步骤:
-
下载 OpenCV:
- 访问 OpenCV 官方网站 ( https://opencv.org/releases/ ) 下载最新的稳定版本。通常你会下载源码包。
-
编译与安装:
- Windows:
- 你可以下载预编译的库 (pre-built libraries),解压后配置 Visual Studio 的包含目录 (Include Directories)、库目录 (Library Directories) 和链接器输入 (Linker Input)。
- 或者使用 CMake 配合 Visual Studio (或其他编译器如 MinGW) 从源码编译。这能让你更好地控制编译选项。
- Linux (Ubuntu/Debian 为例):
- 可以通过包管理器安装:
sudo apt update && sudo apt install libopencv-dev python3-opencv
(这通常会安装一个较稳定但可能不是最新的版本)。 - 推荐从源码编译安装,使用 CMake。这能确保你使用最新版本并可以自定义模块。你需要先安装必要的依赖项 (如
build-essential
,cmake
,git
,libgtk2.0-dev
,pkg-config
,libavcodec-dev
,libavformat-dev
,libswscale-dev
等)。
- 可以通过包管理器安装:
- macOS:
- 可以使用 Homebrew:
brew install opencv
。 - 同样也可以从源码编译。
- 可以使用 Homebrew:
- Windows:
-
配置你的 IDE/编译器:
-
你需要告诉你的编译器在哪里找到 OpenCV 的头文件 (
.hpp
,.h
) 和库文件 (.lib
,.dll
on Windows;.so
,.a
on Linux/macOS)。 -
Visual Studio:
- 项目属性 -> VC++ 目录 -> 包含目录:添加
path_to_opencv/build/include
。 - 项目属性 -> VC++ 目录 -> 库目录:添加
path_to_opencv/build/your_compiler_arch/your_vc_version/lib
(例如x64/vc16/lib
)。 - 项目属性 -> 链接器 -> 输入 -> 附加依赖项:添加需要的
.lib
文件名 (例如opencv_worldXYZ.lib
,其中 XYZ 是版本号)。调试版通常带 ‘d’ 后缀,如opencv_worldXYZd.lib
。
- 项目属性 -> VC++ 目录 -> 包含目录:添加
-
CMake (推荐):
CMake 是一个跨平台的构建系统,非常适合 C++ 项目。创建一个CMakeLists.txt
文件来管理你的项目编译:cmake_minimum_required(VERSION 3.10) project(MyOpenCVApp)set(CMAKE_CXX_STANDARD 11) # 或更高版本# 找到 OpenCV 包 find_package(OpenCV REQUIRED)# 包含 OpenCV 头文件目录 include_directories(${OpenCV_INCLUDE_DIRS})# 添加你的源文件 add_executable(MyOpenCVApp main.cpp)# 链接 OpenCV 库 target_link_libraries(MyOpenCVApp ${OpenCV_LIBS})
然后使用 CMake 生成构建文件 (例如 Makefile 或 Visual Studio 项目),再进行编译。
mkdir build cd build cmake .. make # 或者在 Visual Studio 中打开生成的 .sln 文件编译
-
环境变量 (尤其在 Windows 上):
确保包含 OpenCV bin
目录 (例如 path_to_opencv/build/your_compiler_arch/your_vc_version/bin
) 的路径已添加到系统的 PATH
环境变量中,这样程序运行时才能找到所需的 .dll
文件。
🖼️ 核心概念与第一个程序:加载、显示和保存图像
OpenCV 中最核心的数据结构之一是 cv::Mat
。它用于表示 n 维稠密数组,可以存储图像、特征向量、矩阵等。你可以把它想象成一个灵活的画布或数据容器。
我们的第一个程序:读取一张图片,显示它,然后保存它。
假设你有一张名为 input.jpg
的图片和你的 C++ 源文件 (例如 main.cpp
) 在同一个目录下。
// main.cpp
#include <opencv2/core.hpp> // 核心功能,如 cv::Mat
#include <opencv2/imgcodecs.hpp> // 图像读取和写入 (imread, imwrite)
#include <opencv2/highgui.hpp> // GUI 功能 (imshow, waitKey, namedWindow)
#include <iostream> // 用于控制台输出int main() {// 1. 读取图像// cv::imread(filename, flags)// - filename: 图像文件的路径// - flags:// - cv::IMREAD_COLOR: 以彩色模式加载图像 (默认)// - cv::IMREAD_GRAYSCALE: 以灰度模式加载图像// - cv::IMREAD_UNCHANGED: 加载包含 Alpha 通道的图像std::string imagePath = "input.jpg"; // 替换为你的图片路径cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR);// 2. 检查图像是否成功加载if (image.empty()) {std::cerr << "错误:无法加载图像 " << imagePath << std::endl;return -1; // 返回错误码}// 3. 显示图像// cv::namedWindow(windowName, flags) - 创建一个窗口 (可选,但推荐)// - windowName: 窗口的标题// - flags:// - cv::WINDOW_AUTOSIZE: 窗口大小根据图像自动调整 (默认)// - cv::WINDOW_NORMAL: 窗口可以被用户调整大小std::string windowName = "我的第一个 OpenCV 图像";cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);// cv::imshow(windowName, imageMatrix) - 在指定窗口中显示图像cv::imshow(windowName, image);// 4. 等待按键// cv::waitKey(delay) - 等待指定的毫秒数,如果 delay <= 0,则无限等待直到用户按键// 返回值为按键的 ASCII 码,如果没有按键且超时则返回 -1std::cout << "图像已显示。按任意键关闭窗口并保存图像..." << std::endl;cv::waitKey(0); // 等待用户按键// 5. 保存图像// cv::imwrite(filename, imageMatrix)std::string outputImagePath = "output.png"; // 可以保存为不同格式,如 .png, .bmp 等bool success = cv::imwrite(outputImagePath, image);if (success) {std::cout << "图像已成功保存到 " << outputImagePath << std::endl;} else {std::cerr << "错误:无法保存图像到 " << outputImagePath << std::endl;}// 6. 关闭所有 OpenCV 创建的窗口 (可选,程序结束时会自动关闭)cv::destroyAllWindows();return 0; // 程序成功结束
}
代码解释:
- 包含头文件:
opencv2/core.hpp
: 包含了cv::Mat
等核心数据结构的定义。opencv2/imgcodecs.hpp
: 包含了图像编解码函数,如cv::imread()
(读取) 和cv::imwrite()
(保存)。opencv2/highgui.hpp
: 包含了高级 GUI 函数,如cv::namedWindow()
(创建窗口)、cv::imshow()
(显示图像) 和cv::waitKey()
(等待按键)。
cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR);
- 创建一个
cv::Mat
对象image
。 cv::imread()
函数尝试从imagePath
指定的路径加载图像。cv::IMREAD_COLOR
表示以彩色模式加载。
- 创建一个
if (image.empty())
- 这是一个非常重要的错误检查。如果
cv::imread()
找不到文件或文件格式不支持,它会返回一个空的cv::Mat
对象。.empty()
方法可以检查cv::Mat
是否为空。
- 这是一个非常重要的错误检查。如果
cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);
- 创建一个名为 “我的第一个 OpenCV 图像” 的窗口。
cv::WINDOW_AUTOSIZE
使窗口大小自动适应图像。
- 创建一个名为 “我的第一个 OpenCV 图像” 的窗口。
cv::imshow(windowName, image);
- 在之前创建的窗口中显示
image
。
- 在之前创建的窗口中显示
cv::waitKey(0);
- 这是使图像窗口保持可见的关键。
cv::waitKey()
等待用户按键。参数0
表示无限期等待。如果没有这个函数,窗口会一闪而过。
- 这是使图像窗口保持可见的关键。
cv::imwrite(outputImagePath, image);
- 将
image
对象中的图像数据保存到名为output.png
的文件中。OpenCV 会根据文件扩展名自动选择编码格式。
- 将
cv::destroyAllWindows();
- 关闭所有由 OpenCV 创建的窗口。虽然程序结束时通常会自动清理,但这是一种良好的编程习惯。
编译和运行:
- 使用 CMake:
# 假设你的 CMakeLists.txt 和 main.cpp 在同一目录 mkdir build cd build cmake .. make # 或者 cmake --build . (在 Windows 上,如果生成的是 Visual Studio 项目,则用 VS 打开并编译) ./MyOpenCVApp # 运行程序
- 直接使用 g++ (Linux/macOS 示例,需要正确配置 pkg-config 或手动指定包含和库路径):
(对于g++ main.cpp -o MyOpenCVApp `pkg-config --cflags --libs opencv4` # opencv4 可能需要根据你的版本调整 ./MyOpenCVApp
pkg-config opencv4
不可用的情况,你需要手动指定-I
和-L
标志以及-l
链接库,例如:g++ main.cpp -o MyOpenCVApp -I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_highgui
)
🎨 基本图像操作
掌握了图像的读写和显示后,我们来看看一些基本的图像操作。
1. 获取图像属性
cv::Mat
对象包含很多关于图像的信息:
#include <opencv2/opencv.hpp> // 包含所有常用模块
#include <iostream>int main() {cv::Mat image = cv::imread("input.jpg");if (image.empty()) {std::cerr << "无法加载图像!" << std::endl;return -1;}// 获取图像尺寸int rows = image.rows; // 高度 (像素)int cols = image.cols; // 宽度 (像素)std::cout << "图像尺寸: " << cols << "x" << rows << std::endl;// 获取图像通道数int channels = image.channels();std::cout << "图像通道数: " << channels << std::endl;// 彩色图像通常有 3 个通道 (BGR),灰度图像有 1 个通道// 获取图像深度 (每个通道中像素值的类型)// CV_8U (8位无符号整数), CV_16S (16位有符号整数), CV_32F (32位浮点数) 等int depth = image.depth();std::cout << "图像深度类型: ";switch (depth) {case CV_8U: std::cout << "CV_8U (8-bit unsigned)"; break;case CV_8S: std::cout << "CV_8S (8-bit signed)"; break;case CV_16U: std::cout << "CV_16U (16-bit unsigned)"; break;case CV_16S: std::cout << "CV_16S (16-bit signed)"; break;case CV_32S: std::cout << "CV_32S (32-bit signed)"; break;case CV_32F: std::cout << "CV_32F (32-bit float)"; break;case CV_64F: std::cout << "CV_64F (64-bit float)"; break;default: std::cout << "未知"; break;}std::cout << std::endl;// 获取图像类型 (深度 + 通道数的组合)// 例如 CV_8UC3 表示 8位无符号整数,3通道int type = image.type();std::cout << "图像类型 (如 CV_8UC3): " << type << std::endl;// 你可以用 image.type() == CV_8UC3 来检查cv::imshow("属性演示", image);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
2. 颜色空间转换
最常见的转换是将彩色图像转换为灰度图像。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp> // 图像处理模块,包含颜色转换int main() {cv::Mat colorImage = cv::imread("input.jpg");if (colorImage.empty()) {std::cerr << "无法加载彩色图像!" << std::endl;return -1;}cv::Mat grayImage;// cv::cvtColor(sourceImage, destinationImage, conversionCode)cv::cvtColor(colorImage, grayImage, cv::COLOR_BGR2GRAY);// 注意:OpenCV 默认以 BGR 顺序加载彩色图像,而不是 RGBcv::imshow("彩色图像", colorImage);cv::imshow("灰度图像", grayImage);cv::waitKey(0);cv::imwrite("gray_output.jpg", grayImage);std::cout << "灰度图像已保存为 gray_output.jpg" << std::endl;cv::destroyAllWindows();return 0;
}
其他常见的颜色空间转换包括 COLOR_BGR2HSV
, COLOR_BGR2Lab
等。
3. 图像缩放 (Resizing)
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>int main() {cv::Mat originalImage = cv::imread("input.jpg");if (originalImage.empty()) {std::cerr << "无法加载图像!" << std::endl;return -1;}cv::Mat resizedImage;// 方法1: 指定目标尺寸cv::Size newSize(300, 200); // 宽 300, 高 200// cv::resize(source, destination, dsize, fx, fy, interpolation)// dsize: 目标尺寸// fx, fy: x 和 y 方向的缩放因子 (如果 dsize 非零,则忽略)// interpolation: 插值方法// - cv::INTER_LINEAR: 线性插值 (常用)// - cv::INTER_NEAREST: 最近邻插值// - cv::INTER_CUBIC: 双三次插值 (效果好但慢)cv::resize(originalImage, resizedImage, newSize, 0, 0, cv::INTER_LINEAR);cv::imshow("原始图像", originalImage);cv::imshow("缩放图像 (指定尺寸)", resizedImage);cv::waitKey(0);// 方法2: 指定缩放因子cv::Mat scaledImage;double scaleX = 0.5; // 缩小到50%宽度double scaleY = 0.5; // 缩小到50%高度cv::resize(originalImage, scaledImage, cv::Size(), scaleX, scaleY, cv::INTER_LINEAR);cv::imshow("缩放图像 (指定因子)", scaledImage);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
4. 图像模糊 (Blurring/Smoothing)
模糊可以用于降噪或作为某些算法的预处理步骤。高斯模糊是最常用的模糊方法之一。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>int main() {cv::Mat image = cv::imread("input.jpg");if (image.empty()) {std::cerr << "无法加载图像!" << std::endl;return -1;}cv::Mat blurredImage;// cv::GaussianBlur(source, destination, ksize, sigmaX, sigmaY, borderType)// ksize: 高斯核大小,必须是正奇数,如 cv::Size(5, 5)// sigmaX: X方向的高斯核标准差// sigmaY: Y方向的高斯核标准差 (如果为0,则从 sigmaX 计算;如果都为0,则从 ksize 计算)cv::GaussianBlur(image, blurredImage, cv::Size(15, 15), 0);cv::imshow("原始图像", image);cv::imshow("高斯模糊", blurredImage);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
5. 边缘检测 (Edge Detection)
Canny 边缘检测是一种流行的边缘检测算法。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>int main() {cv::Mat image = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); // Canny 通常在灰度图上操作if (image.empty()) {std::cerr << "无法加载图像!" << std::endl;return -1;}cv::Mat edges;// cv::Canny(source, destination, threshold1, threshold2, apertureSize, L2gradient)// threshold1: 第一个阈值 (低阈值)// threshold2: 第二个阈值 (高阈值)// apertureSize: Sobel 算子的孔径大小 (通常为 3)// L2gradient: 是否使用更精确的 L2 范数计算图像梯度幅值 (默认 false)cv::Canny(image, edges, 50, 150);cv::imshow("原始灰度图", image);cv::imshow("Canny 边缘", edges);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
📹 处理视频
OpenCV 同样可以轻松处理视频,无论是从文件读取还是从摄像头实时捕获。
1. 从文件读取视频
#include <opencv2/opencv.hpp>
#include <iostream>int main() {// 打开视频文件cv::VideoCapture cap("my_video.mp4"); // 替换为你的视频文件路径// 检查视频是否成功打开if (!cap.isOpened()) {std::cerr << "错误:无法打开视频文件!" << std::endl;return -1;}cv::Mat frame;std::string windowName = "视频播放";cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);while (true) {// 读取一帧// cap.read(frame) 或 cap >> frame;bool success = cap.read(frame);// 如果读取失败 (例如视频结束),则退出循环if (!success) {std::cout << "视频播放完毕或读取帧失败。" << std::endl;break;}// 在这里可以对 frame 进行处理,例如转灰度、边缘检测等// cv::cvtColor(frame, frame, cv::COLOR_BGR2GRAY);// 显示帧cv::imshow(windowName, frame);// 等待 25毫秒,如果按下 'q' 键或 ESC 键则退出// 视频的帧率通常是 25-30 fps,所以等待时间是 1000/fpschar key = (char)cv::waitKey(25);if (key == 'q' || key == 27) { // 27 是 ESC 键的 ASCII 码break;}}// 释放 VideoCapture 对象cap.release();// 关闭所有窗口cv::destroyAllWindows();return 0;
}
2. 从摄像头捕获视频
#include <opencv2/opencv.hpp>
#include <iostream>int main() {// 打开默认摄像头 (通常索引为 0)// 如果有多个摄像头,可以尝试 1, 2, ...cv::VideoCapture cap(0);if (!cap.isOpened()) {std::cerr << "错误:无法打开摄像头!" << std::endl;return -1;}// 可以设置摄像头的属性,例如帧的宽度和高度// cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);// cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);cv::Mat frame;std::string windowName = "摄像头实时画面";cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);std::cout << "按 'q' 或 ESC 键退出..." << std::endl;while (true) {cap >> frame; // 或者 cap.read(frame);if (frame.empty()) {std::cerr << "错误:捕获到空帧!" << std::endl;break;}// 对帧进行处理 (可选)// 例如,水平翻转图像,因为摄像头画面通常是镜像的cv::Mat flippedFrame;cv::flip(frame, flippedFrame, 1); // 1 表示水平翻转, 0 表示垂直, -1 表示都翻转cv::imshow(windowName, flippedFrame); // 显示翻转后的帧char key = (char)cv::waitKey(1); // 等待 1ms,几乎是实时if (key == 'q' || key == 27) {break;}}cap.release();cv::destroyAllWindows();return 0;
}
🖌️ 在图像上绘制图形和文本
OpenCV 提供了在图像上绘制点、线、矩形、圆形、文本等功能,这对于标注、调试和结果可视化非常有用。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp> // 绘图函数主要在这里int main() {// 创建一个黑色背景图像cv::Mat canvas = cv::Mat::zeros(cv::Size(500, 500), CV_8UC3); // 500x500, 3通道 (BGR)// 1. 绘制直线// cv::line(image, pt1, pt2, color, thickness, lineType, shift)cv::Point pt1(50, 50);cv::Point pt2(450, 50);cv::Scalar lineColor(0, 255, 0); // BGR 格式:绿色int thickness = 2;cv::line(canvas, pt1, pt2, lineColor, thickness);// 2. 绘制矩形// cv::rectangle(image, pt1, pt2, color, thickness, lineType, shift)// pt1: 左上角点, pt2: 右下角点// 或者 cv::rectangle(image, Rect, color, thickness, ...)cv::Rect rect(100, 100, 200, 150); // x, y, width, heightcv::Scalar rectColor(255, 0, 0); // 蓝色cv::rectangle(canvas, rect, rectColor, thickness);// 绘制填充矩形cv::rectangle(canvas, cv::Point(120,120), cv::Point(180,180), cv::Scalar(255,255,0), cv::FILLED);// 3. 绘制圆形// cv::circle(image, center, radius, color, thickness, lineType, shift)cv::Point center(350, 350);int radius = 50;cv::Scalar circleColor(0, 0, 255); // 红色cv::circle(canvas, center, radius, circleColor, thickness);// 绘制填充圆形cv::circle(canvas, cv::Point(350,150), 30, cv::Scalar(0,255,255), cv::FILLED);// 4. 绘制椭圆// cv::ellipse(image, center, axes, angle, startAngle, endAngle, color, thickness, ...)cv::Point ellipseCenter(150, 400);cv::Size axes(70, 30); // 长轴和短轴长度double angle = 45; // 旋转角度double startAngle = 0;double endAngle = 360; // 完整的椭圆cv::Scalar ellipseColor(255, 0, 255); // 紫色cv::ellipse(canvas, ellipseCenter, axes, angle, startAngle, endAngle, ellipseColor, thickness);// 5. 放置文本// cv::putText(image, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin)std::string text = "OpenCV Rocks!";cv::Point textOrg(50, 480); // 文本左下角起始点int fontFace = cv::FONT_HERSHEY_SIMPLEX;double fontScale = 1.0;cv::Scalar textColor(255, 255, 255); // 白色cv::putText(canvas, text, textOrg, fontFace, fontScale, textColor, thickness);cv::imshow("绘图演示", canvas);cv::waitKey(0);cv::destroyAllWindows();return 0;
}
🚀 接下来学什么?
恭喜你!你已经迈出了 OpenCV (C/C++) 学习的第一步。这只是冰山一角,OpenCV 的世界非常广阔。
建议的学习路径:
- 深入
cv::Mat
:理解其内存管理、ROI (Region of Interest)、拷贝和克隆的区别。 - 像素级操作:学习如何直接访问和修改图像的像素值 (使用
.at<type>(row, col)
或指针)。 - 更多图像处理技术:
- 形态学操作 (腐蚀、膨胀、开运算、闭运算)
- 直方图计算与均衡化
- 图像阈值化 (二值化)
- 模板匹配
- 霍夫变换 (检测直线和圆)
- 特征提取与匹配:SIFT, SURF, ORB, AKAZE 等特征点检测与描述。
- 物体检测:Haar 级联分类器 (用于人脸检测等),以及更现代的基于深度学习的方法 (OpenCV 的 DNN 模块可以加载预训练模型)。
- 机器学习模块
cv::ml
:支持向量机 (SVM)、K最近邻 (KNN) 等。 - 相机标定与3D重建:
calib3d
模块。 - 探索 OpenCV 的
contrib
模块:包含许多实验性或较新的功能。
重要资源:
- OpenCV 官方文档和教程: https://docs.opencv.org/ (这是你最好的朋友!)
- OpenCV Q&A 论坛: https://forum.opencv.org/
- 大量的在线教程和博客 (搜索特定功能,如 “OpenCV C++ tutorial blur image”)
实践是学习编程的最佳方式。尝试修改示例代码,用自己的图像和视频进行实验,并挑战一些小的计算机视觉项目。
祝你在计算机视觉的探索之旅中一切顺利!🚀