OpenCV 是一个广泛使用的计算机视觉库,而 C++17 提供了许多新特性可以增强 OpenCV 的使用体验。本文将详细介绍如何在 C++17 环境下高效地使用 OpenCV 库,包括新特性的应用和最佳实践。
一、环境准备
1. 安装 OpenCV 和 C++17 支持
确保你的开发环境支持 C++17 并安装了 OpenCV:
# 在 Ubuntu 上安装 OpenCV
sudo apt-get install libopencv-dev
# 在 Windows 上可以使用 vcpkg 或直接下载 OpenCV 安装包
2. 配置编译器支持 C++17
在 CMake 中配置 C++17:
cmake_minimum_required(VERSION 3.10)
project(OpenCV_Cpp17)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenCV REQUIRED)
add_executable(OpenCV_Cpp17 main.cpp)
target_link_libraries(OpenCV_Cpp17 ${OpenCV_LIBS})
二、C++17 新特性在 OpenCV 中的应用
1. 结构化绑定(Structured Bindings)处理 OpenCV 数据
OpenCV 中经常需要处理 cv::Point
、cv::Size
、cv::Rect
等结构体,结构化绑定可以简化代码:
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Point pt(10, 20);
auto [x, y] = pt; // 结构化绑定
std::cout << "Point: ("<< x << ", "<< y << ")" << std::endl;
cv::Size sz(640, 480);
auto [width, height] = sz;
std::cout << "Size: " << width << "x" << height << std::endl;
return 0;
}
2. if 和 switch 中的初始化语句简化 OpenCV 操作
在图像处理中经常需要检查图像是否加载成功:
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// if 中的初始化语句
if (cv::Mat img = cv::imread("image.jpg"); !img.empty()) {
std::cout << "Image loaded successfully" << std::endl;
cv::imshow("Image", img);
cv::waitKey(0);
} else {
std::cerr << "Failed to load image" << std::endl;
}
return 0;
}
3. 内联变量(Inline Variables)定义常用参数
在 OpenCV 应用中经常需要使用一些常量参数:
// 在头文件中定义
inline const cv::Scalar RED(0, 0, 255);
inline const cv::Scalar GREEN(0, 255, 0);
inline const cv::Scalar BLUE(255, 0, 0);
// 在源文件中使用
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img(300, 300, CV_8UC3, cv::Scalar(0, 0, 0));
// 使用内联变量
cv::circle(img, cv::Point(150, 150), 50, RED, -1);
cv::rectangle(img, cv::Point(50, 50), cv::Point(250, 250), GREEN, 2);
cv::imshow("Image", img);
cv::waitKey(0);
return 0;
}
4. 折叠表达式(Fold Expressions)处理 OpenCV 矩阵运算
在图像处理中经常需要对矩阵进行批量操作:
#include <opencv2/opencv.hpp>
#include <iostream>
template<typename... Args>
cv::Mat sumMatrices(Args... args) {
cv::Mat result = (args + ...); // 折叠表达式
return result;
}
int main() {
cv::Mat mat1 = (cv::Mat_<double>(2, 2) << 1, 2, 3, 4);
cv::Mat mat2 = (cv::Mat_<double>(2, 2) << 5, 6, 7, 8);
cv::Mat mat3 = (cv::Mat_<double>(2, 2) << 9, 10, 11, 12);
cv::Mat sum = sumMatrices(mat1, mat2, mat3);
std::cout << "Sum of matrices:" << std::endl << sum << std::endl;
return 0;
}
5. std::optional
处理可能失败的 OpenCV 操作
在图像处理中很多操作可能失败,使用 std::optional
可以更安全地处理:
#include <opencv2/opencv.hpp>
#include <optional>
#include <iostream>
std::optional<cv::Mat> loadImage(const std::string& path) {
cv::Mat img = cv::imread(path);
if (img.empty()) {
return std::nullopt;
}
return img;
}
int main() {
auto img = loadImage("image.jpg");
if (img) {
std::cout << "Image loaded successfully" << std::endl;
cv::imshow("Image", *img);
cv::waitKey(0);
} else {
std::cerr << "Failed to load image" << std::endl;
}
return 0;
}
6. std::variant
处理不同类型的 OpenCV 数据
在图像处理中可能需要处理不同类型的数据(如图像、特征点、描述符等):
#include <opencv2/opencv.hpp>
#include <variant>
#include <iostream>
using ImageData = std::variant<cv::Mat, std::vector<cv::KeyPoint>, cv::Mat>;
void processImageData(const ImageData& data) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, cv::Mat>) {
std::cout << "Processing image with size " << arg.size() << std::endl;
} else if constexpr (std::is_same_v<T, std::vector<cv::KeyPoint>>) {
std::cout << "Processing " << arg.size() << " keypoints" << std::endl;
}
}, data);
}
int main() {
cv::Mat img = cv::imread("image.jpg");
std::vector<cv::KeyPoint> keypoints;
processImageData(img);
processImageData(keypoints);
return 0;
}
7. std::string_view
处理 OpenCV 文件路径
在图像处理中经常需要处理文件路径,使用 std::string_view
可以提高性能:
#include <opencv2/opencv.hpp>
#include <string_view>
#include <iostream>
void loadImage(std::string_view path) {
cv::Mat img = cv::imread(std::string(path));
if (img.empty()) {
std::cerr << "Failed to load image from " << path << std::endl;
return;
}
std::cout << "Image loaded successfully from " << path << std::endl;
cv::imshow("Image", img);
cv::waitKey(0);
}
int main() {
loadImage("image.jpg");
return 0;
}
三、OpenCV 核心功能与 C++17 结合
1. 图像加载与显示
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// if 中的初始化语句检查图像是否加载成功
if (cv::Mat img = cv::imread("image.jpg"); !img.empty()) {
std::cout << "Image size: " << img.size() << std::endl;
// 使用结构化绑定获取图像尺寸
auto [width, height] = std::make_pair(img.cols, img.rows);
std::cout << "Width: " << width << ", Height: " << height << std::endl;
cv::imshow("Image", img);
cv::waitKey(0);
} else {
std::cerr << "Failed to load image" << std::endl;
}
return 0;
}
2. 图像处理操作
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {
std::cerr << "Failed to load image" << std::endl;
return -1;
}
// 使用折叠表达式处理多个图像操作
auto processImage = [](cv::Mat& img) {
cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(img, img, cv::Size(5, 5), 0);
cv::Canny(img, img, 50, 150);
};
processImage(img);
cv::imshow("Processed Image", img);
cv::waitKey(0);
return 0;
}
3. 特征检测与匹配
#include <opencv2/opencv.hpp>
#include <optional>
#include <iostream>
std::optional<std::vector<cv::KeyPoint>> detectFeatures(const cv::Mat& img) {
cv::Ptr<cv::ORB> orb = cv::ORB::create();
std::vector<cv::KeyPoint> keypoints;
orb->detect(img, keypoints);
if (keypoints.empty()) {
return std::nullopt;
}
return keypoints;
}
int main() {
cv::Mat img = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
if (!img.data) {
std::cerr << "Failed to load image" << std::endl;
return -1;
}
auto keypoints = detectFeatures(img);
if (keypoints) {
std::cout << "Detected " << keypoints->size() << " keypoints" << std::endl;
cv::Mat img_with_keypoints;
cv::drawKeypoints(img, *keypoints, img_with_keypoints);
cv::imshow("Keypoints", img_with_keypoints);
cv::waitKey(0);
} else {
std::cerr << "No keypoints detected" << std::endl;
}
return 0;
}
四、性能优化技巧
1. 使用 std::string_view
减少字符串拷贝
在处理大量文件路径时:
#include <opencv2/opencv.hpp>
#include <string_view>
#include <vector>
void processImages(const std::vector<std::string_view>& paths) {
for (const auto& path : paths) {
cv::Mat img = cv::imread(std::string(path));
if (!img.empty()) {
// 处理图像
}
}
}
int main() {
std::vector<std::string_view> image_paths = {
"image1.jpg",
"image2.jpg",
"image3.jpg"
};
processImages(image_paths);
return 0;
}
2. 使用 std::optional
避免不必要的计算
在图像处理管道中:
#include <opencv2/opencv.hpp>
#include <optional>
#include <iostream>
std::optional<cv::Mat> preprocessImage(const cv::Mat& img) {
// 检查图像是否需要预处理
if (img.channels() == 3) {
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
return gray;
}
return std::nullopt;
}
int main() {
cv::Mat img = cv::imread("image.jpg");
if (!img.data) {
std::cerr << "Failed to load image" << std::endl;
return -1;
}
auto processed = preprocessImage(img);
if (processed) {
std::cout << "Image preprocessed" << std::endl;
// 使用处理后的图像
} else {
std::cout << "No preprocessing needed" << std::endl;
// 使用原始图像
}
return 0;
}
3. 使用并行算法加速图像处理
#include <opencv2/opencv.hpp>
#include <execution>
#include <iostream>
int main() {
cv::Mat img = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
if (!img.data) {
std::cerr << "Failed to load image" << std::endl;
return -1;
}
// 并行计算图像直方图
cv::Mat hist;
int histSize = 256;
float range[] = {0, 256};
const float* histRange = {range};
cv::calcHist(std::execution::par,
std::vector<cv::Mat>{img},
1,
std::vector<int>{0},
cv::Mat(),
hist,
1,
std::vector<int>{histSize},
&histRange);
std::cout << "Histogram calculated" << std::endl;
return 0;
}
五、最佳实践
- 使用
std::optional
处理可能失败的 OpenCV 操作:如图像加载、特征检测等 - 使用
std::variant
处理不同类型的图像数据:如原始图像、特征点、描述符等 - 使用
std::string_view
处理文件路径:减少不必要的字符串拷贝 - 使用结构化绑定简化 OpenCV 数据访问:如
cv::Point
、cv::Size
等 - 使用折叠表达式简化矩阵运算:如多个图像的批量操作
- 使用并行算法加速计算密集型任务:如图像直方图计算、特征匹配等
- 使用内联变量定义常用参数:如颜色、尺寸等常量
六、完整示例:图像处理管道
#include <opencv2/opencv.hpp>
#include <optional>
#include <variant>
#include <string_view>
#include <execution>
#include <iostream>
// 使用 std::variant 定义图像处理管道中的数据类型
using ImageData = std::variant<cv::Mat, std::vector<cv::KeyPoint>, cv::Mat>;
// 使用 std::optional 处理可能失败的图像加载
std::optional<cv::Mat> loadImage(std::string_view path) {
cv::Mat img = cv::imread(std::string(path));
if (img.empty()) {
return std::nullopt;
}
return img;
}
// 特征检测函数
std::optional<std::vector<cv::KeyPoint>> detectFeatures(const cv::Mat& img) {
cv::Ptr<cv::ORB> orb = cv::ORB::create();
std::vector<cv::KeyPoint> keypoints;
orb->detect(img, keypoints);
if (keypoints.empty()) {
return std::nullopt;
}
return keypoints;
}
// 图像处理管道
ImageData processImagePipeline(std::string_view path) {
auto img = loadImage(path);
if (!img) {
std::cerr << "Failed to load image" << std::endl;
return cv::Mat(); // 返回空图像表示失败
}
// 转换为灰度
cv::Mat gray;
cv::cvtColor(*img, gray, cv::COLOR_BGR2GRAY);
// 检测特征
auto keypoints = detectFeatures(gray);
if (keypoints) {
// 绘制特征点
cv::Mat img_with_keypoints;
cv::drawKeypoints(gray, *keypoints, img_with_keypoints);
// 使用并行算法计算直方图
cv::Mat hist;
int histSize = 256;
float range[] = {0, 256};
const float* histRange = {range};
cv::calcHist(std::execution::par,
std::vector<cv::Mat>{img_with_keypoints},
1,
std::vector<int>{0},
cv::Mat(),
hist,
1,
std::vector<int>{histSize},
&histRange);
return img_with_keypoints; // 返回处理后的图像
} else {
return gray; // 返回灰度图像
}
}
int main() {
auto result = processImagePipeline("image.jpg");
if (std::holds_alternative<cv::Mat>(result)) {
cv::Mat final_img = std::get<cv::Mat>(result);
cv::imshow("Result", final_img);
cv::waitKey(0);
} else {
std::cerr << "Processing failed" << std::endl;
}
return 0;
}