C++ OpenCV 机器学习与深度学习
我将为你详细介绍在 C++ 中使用 OpenCV 的机器学习和深度学习功能,重点涵盖 OpenCV 的 ml 模块(传统机器学习)和 dnn 模块(深度学习),并提供实际可运行的代码示例。内容基于 OpenCV 4.x,假设你已配置好开发环境。以下将包括核心概念、关键功能、示例代码和优化建议,适合从基础到高级的应用。
1. 机器学习(ml 模块)
OpenCV 的 ml 模块提供了多种传统机器学习算法(如 SVM、决策树、KNN 等),适合图像分类、目标检测等任务。这些算法通常结合图像特征(如 HOG、SIFT)使用。
1.1 核心功能
- 支持的算法:
- SVM(支持向量机):用于分类或回归。
- KNN(K 近邻):简单有效的分类算法。
- 决策树/随机森林:适合多类分类。
- ANN(人工神经网络):基础的多层感知器。
- 数据格式:
- 输入:
Mat矩阵,行表示样本,列表示特征。 - 标签:
Mat矩阵,存储类别标签或回归值。 - 用途:图像分类、特征分类(如人脸/非人脸)。
1.2 示例代码:SVM 分类
以下示例展示如何使用 SVM 进行二分类(例如区分两类图像特征)。
include
include
include
using namespace cv;
using namespace cv::ml;
using namespace std;
int main() {
// 构造示例数据(2 类,每类 100 个样本,2D 特征)
Mat samples(200, 2, CV_32F);
Mat labels(200, 1, CV_32S);
randn(samples(Range(0, 100), Range::all()), Scalar(1, 1), Scalar(0.5, 0.5)); // 类 1
randn(samples(Range(100, 200), Range::all()), Scalar(3, 3), Scalar(0.5, 0.5)); // 类 2
labels(Range(0, 100), Range::all()) = 0; // 类 1 标签
labels(Range(100, 200), Range::all()) = 1; // 类 2 标签
// 创建并训练 SVM
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC); // 分类 SVM
svm->setKernel(SVM::LINEAR); // 线性核
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
Ptr<TrainData> trainData = TrainData::create(samples, ROW_SAMPLE, labels);
svm->train(trainData);
// 测试新样本
Mat testSample = (Mat_<float>(1, 2) << 1.2, 1.3);
float prediction = svm->predict(testSample);
cout << "测试样本预测类别: " << prediction << endl;
// 可视化分类边界(仅限 2D 特征)
Mat image(400, 400, CV_8UC3, Scalar(255, 255, 255));
for (int y = 0; y < image.rows; y++) {
for (int x = 0; x < image.cols; x++) {
Mat sample = (Mat_<float>(1, 2) << x * 5.0 / image.cols, y * 5.0 / image.rows);
float pred = svm->predict(sample);
image.at<Vec3b>(y, x) = pred == 0 ? Vec3b(200, 200, 255) : Vec3b(255, 200, 200);
}
}
// 绘制训练样本
for (int i = 0; i < samples.rows; i++) {
int x = samples.at<float>(i, 0) * image.cols / 5;
int y = samples.at<float>(i, 1) * image.rows / 5;
circle(image, Point(x, y), 3, labels.at<int>(i) == 0 ? Scalar(0, 0, 255) : Scalar(255, 0, 0), -1);
}
imshow("SVM Classification", image);
imwrite("svm_result.jpg", image);
waitKey(0);
destroyAllWindows();
return 0;
}
说明
- 数据准备:
samples存储特征(2D 点),labels存储类别标签。 - SVM 配置:使用线性核,适合简单分类任务;可尝试 RBF 核(
SVM::RBF)处理非线性数据。 - 可视化:将分类结果绘制为彩色区域,样本点用圆形标记。
- 用途:分类手写数字、图像特征等。
1.3 结合图像特征
实际应用中,通常提取图像特征(如 HOG、SIFT)作为 SVM 输入。例如,使用 HOG 描述子分类图像:
include
include
include
include
using namespace cv;
using namespace cv::ml;
using namespace std;
int main() {
// 初始化 HOG 描述子
HOGDescriptor hog(Size(64, 64), Size(16, 16), Size(8, 8), Size(8, 8), 9);
// 加载示例图像(正样本和负样本)
Mat pos_img = imread("positive.jpg", IMREAD_GRAYSCALE);
Mat neg_img = imread("negative.jpg", IMREAD_GRAYSCALE);
resize(pos_img, pos_img, Size(64, 64));
resize(neg_img, neg_img, Size(64, 64));
// 计算 HOG 特征
vector<float> pos_desc, neg_desc;
hog.compute(pos_img, pos_desc);
hog.compute(neg_img, neg_desc);
// 构造训练数据
Mat samples(2, pos_desc.size(), CV_32F);
Mat labels(2, 1, CV_32S);
for (size_t i = 0; i < pos_desc.size(); i++) {
samples.at<float>(0, i) = pos_desc[i];
samples.at<float>(1, i) = neg_desc[i];
}
labels.at<int>(0, 0) = 1; // 正样本
labels.at<int>(1, 0) = 0; // 负样本
// 训练 SVM
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::RBF);
svm->train(TrainData::create(samples, ROW_SAMPLE, labels));
// 测试新图像
Mat test_img = imread("test.jpg", IMREAD_GRAYSCALE);
resize(test_img, test_img, Size(64, 64));
vector<float> test_desc;
hog.compute(test_img, test_desc);
Mat test_sample(1, test_desc.size(), CV_32F, test_desc.data());
float prediction = svm->predict(test_sample);
cout << "测试图像预测类别: " << (prediction == 1 ? "正样本" : "负样本") << endl;
return 0;
}
说明
- HOG 描述子:适合检测固定大小的对象(如行人)。
- 训练数据:实际应用需准备大量正负样本。
- 用途:行人检测、物体分类。
2. 深度学习(dnn 模块)
OpenCV 的 dnn 模块支持加载预训练的深度学习模型(如 TensorFlow、PyTorch、ONNX),用于目标检测、图像分类、分割等任务。
2.1 核心功能
- 支持框架:TensorFlow、PyTorch、Caffe、ONNX 等。
- 常见模型:
- 分类:ResNet、MobileNet。
- 检测:YOLO、SSD。
- 分割:Mask R-CNN、DeepLab。
- 关键类:
cv::dnn::Net用于加载和推理模型。 - 用途:实时目标检测、语义分割。
2.2 示例代码:YOLO 目标检测
以下示例使用 YOLOv5(ONNX 格式)进行目标检测:
include
include
include
include
using namespace cv;
using namespace cv::dnn;
using namespace std;
int main() {
// 加载 YOLOv5 模型
string model_path = “yolov5s.onnx”; // 替换为你的 ONNX 模型路径
Net net = readNetFromONNX(model_path);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU); // 可改为 DNN_TARGET_CUDA
// 加载类别名称
vector<string> classes;
ifstream ifs("coco.names"); // 替换为你的类别文件
string line;
while (getline(ifs, line)) classes.push_back(line);
// 读取图像
Mat img = imread("image.jpg");
if (img.empty()) {
cout << "无法加载图像!" << endl;
return -1;
}
// 准备输入
Mat blob = blobFromImage(img, 1.0 / 255, Size(640, 640), Scalar(0, 0, 0), true, false);
net.setInput(blob);
// 前向推理
Mat output = net.forward();
// 处理输出(YOLOv5 输出格式)
float confThreshold = 0.5;
float nmsThreshold = 0.4;
vector<int> classIds;
vector<float> confidences;
vector<Rect> boxes;
int rows = output.size[1];
float* data = (float*)output.data;
for (int i = 0; i < rows; i++) {
float confidence = data[4];
if (confidence > confThreshold) {
float* classes_scores = data + 5;
Mat scores(1, classes.size(), CV_32F, classes_scores);
Point classIdPoint;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &classIdPoint);
if (max_class_score > confThreshold) {
float x = data[0], y = data[1], w = data[2], h = data[3];
int left = static_cast<int>((x - w / 2) * img.cols / 640);
int top = static_cast<int>((y - h / 2) * img.rows / 640);
int width = static_cast<int>(w * img.cols / 640);
int height = static_cast<int>(h * img.rows / 640);
boxes.push_back(Rect(left, top, width, height));
confidences.push_back(confidence);
classIds.push_back(classIdPoint.x);
}
}
data += output.size[2];
}
// 非极大值抑制
vector<int> indices;
NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
// 绘制结果
for (int idx : indices) {
Rect box = boxes[idx];
rectangle(img, box, Scalar(0, 255, 0), 2);
string label = format("%s: %.2f", classes[classIds[idx]].c_str(), confidences[idx]);
putText(img, label, Point(box.x, box.y - 10), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 2);
}
// 显示和保存
imshow("YOLO Detection", img);
imwrite("yolo_output.jpg", img);
waitKey(0);
destroyAllWindows();
return 0;
}
说明
- 模型准备:需要 YOLOv5 的 ONNX 模型和 COCO 类别文件(
coco.names)。 - 输入预处理:
blobFromImage将图像标准化为模型输入。 - 输出解析:YOLO 输出包含边界框坐标、置信度和类别分数,需手动解析。
- NMS:非极大值抑制去除冗余框。
- 硬件加速:可切换到
DNN_BACKEND_CUDA加速推理(需编译支持 CUDA)。
2.3 视频中的深度学习
将 YOLO 应用于视频流:
include
include
include
include
using namespace cv;
using namespace cv::dnn;
using namespace std;
int main() {
// 加载 YOLOv5 模型
string model_path = “yolov5s.onnx”;
Net net = readNetFromONNX(model_path);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
// 加载类别
vector<string> classes;
ifstream ifs("coco.names");
string line;
while (getline(ifs, line)) classes.push_back(line);
// 打开视频
VideoCapture cap(0); // 使用摄像头
if (!cap.isOpened()) {
cout << "无法打开摄像头!" << endl;
return -1;
}
Mat frame;
while (true) {
cap >> frame;
if (frame.empty()) break;
// 准备输入
Mat blob = blobFromImage(frame, 1.0 / 255, Size(640, 640), Scalar(0, 0, 0), true, false);
net.setInput(blob);
// 前向推理
Mat output = net.forward();
// 处理输出
float confThreshold = 0.5;
float nmsThreshold = 0.4;
vector<int> classIds;
vector<float> confidences;
vector<Rect> boxes;
int rows = output.size[1];
float* data = (float*)output.data;
for (int i = 0; i < rows; i++) {
float confidence = data[4];
if (confidence > confThreshold) {
float* classes_scores = data + 5;
Mat scores(1, classes.size(), CV_32F, classes_scores);
Point classIdPoint;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &classIdPoint);
if (max_class_score > confThreshold) {
float x = data[0], y = data[1], w = data[2], h = data[3];
int left = static_cast<int>((x - w / 2) * frame.cols / 640);
int top = static_cast<int>((y - h / 2) * frame.rows / 640);
int width = static_cast<int>(w * frame.cols / 640);
int height = static_cast<int>(h * frame.rows / 640);
boxes.push_back(Rect(left, top, width, height));
confidences.push_back(confidence);
classIds.push_back(classIdPoint.x);
}
}
data += output.size[2];
}
// 非极大值抑制
vector<int> indices;
NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
// 绘制结果
for (int idx : indices) {
Rect box = boxes[idx];
rectangle(frame, box, Scalar(0, 255, 0), 2);
string label = format("%s: %.2f", classes[classIds[idx]].c_str(), confidences[idx]);
putText(frame, label, Point(box.x, box.y - 10), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 2);
}
// 显示
imshow("YOLO Video", frame);
if (waitKey(30) == 27) break;
}
cap.release();
destroyAllWindows();
return 0;
}
说明
- 实时性:视频处理需优化推理速度,建议降低分辨率或使用轻量模型(如 YOLOv5s)。
- 输出保存:可结合
VideoWriter保存检测结果。
3. 优化技巧
- 机器学习:
- 特征选择:使用 HOG、SIFT 或深度学习提取的特征提高分类精度。
- 数据增强:通过旋转、翻转等增加训练数据多样性。
- 交叉验证:使用
TrainData::shuffle和split进行模型验证。 - 深度学习:
- 硬件加速:使用
DNN_BACKEND_CUDA和DNN_TARGET_CUDA(需 CUDA 支持的 OpenCV)。 - 模型优化:选择轻量模型(如 MobileNet SSD)或量化模型以提高速度。
- 批量处理:对视频帧批量推理以减少开销。
- 性能提升:
- 降低输入分辨率(如 320×320 而不是 640×640)。
- 使用多线程或 OpenCV 的
parallel_for_。 - 跳帧处理以减少计算量。
4. 注意事项
- 环境配置:
- 确保安装
opencv-contrib以支持xfeatures2d(如 SIFT)。 - 深度学习需准备模型文件和类别文件。
- 模型格式:
- ONNX 格式通用性强,推荐使用。
- 确保模型输入尺寸与
blobFromImage一致。 - 数据准备:
- 机器学习需大量标注数据。
- 深度学习需预训练模型和正确的预处理步骤。
5. 进阶学习建议
- 模型训练:使用 Python(PyTorch/TensorFlow)训练模型,导出 ONNX 格式给 OpenCV 使用。
- 高级任务:
- 分割:尝试 DeepLabv3 或 Mask R-CNN。
- 跟踪:结合深度学习检测与 OpenCV 的
Tracker类。 - 混合方法:将传统特征(HOG/SIFT)与深度学习特征结合,提升鲁棒性。
- 参考文档:
- OpenCV ML:https://docs.opencv.org/4.x/d0/df8/classcv_1_1ml_1_1SVM.html
- OpenCV DNN:https://docs.opencv.org/4.x/d6/d0f/classcv_1_1dnn_1_1Net.html
如果你需要针对特定任务(如人脸识别、语义分割)或更复杂的实现(如多目标跟踪),请告诉我,我可以提供更详细的代码和指导!