当前位置: 首页 > news >正文

OpenCV 学习探秘之四:从角点检测,SIFT/SURF/ORB特征提取,目标检测与识别,Haar级联分类人脸检测,再到机器学习等接口的全面实战应用与解析

书接上回,前面介绍了一些基本应用,本篇则着重介绍一些比较复杂的应用。

五、特征提取与描述

5.1 角点检测:Harris 角点和 Shi-Tomasi 角点

5.1.1 Harris 角点检测:cornerHarris 函数

Harris 角点检测是一种经典的角点检测算法,基于图像灰度的二阶矩矩阵。

//Harris角点检测
void MainWindow::on_bt15_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_GRAYSCALE);//这里要以灰度模式读取图像if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// Harris角点检测Mat dst, dst_norm, dst_norm_scaled;dst = Mat::zeros(image.size(), CV_32FC1);// 检测角点cornerHarris(image, dst, 2, 3, 0.04);// 归一化normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());convertScaleAbs(dst_norm, dst_norm_scaled);// 在原图上绘制角点for (int j = 0; j < dst_norm.rows; j++) {for (int i = 0; i < dst_norm.cols; i++) {if ((int)dst_norm.at<float>(j, i) > 100) {circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0);}}}// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Dsc_Harris").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);imshow(QString("Dsc_Harris").toStdString(), dst_norm_scaled);waitKey(0);return;
}

运行结果如下:
在这里插入图片描述

5.1.2 Shi-Tomasi 角点检测:goodFeaturesToTrack 函数

Shi-Tomasi 角点检测是 Harris 角点检测的改进版本,更适合于特征点跟踪。

//Shi-Tomasi角点检测
void MainWindow::on_bt16_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"E:/test/QtTest/WidgetTest/TestOpencv/bin/debug64/Out/OutImg/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_GRAYSCALE);//这里要以灰度模式读取图像if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// Shi-Tomasi角点检测vector<Point2f> corners;int maxCorners = 100;double qualityLevel = 0.01;double minDistance = 10;goodFeaturesToTrack(image, corners, maxCorners, qualityLevel, minDistance);// 在原图上绘制角点Mat output = image.clone();cvtColor(output, output, COLOR_GRAY2BGR);for (size_t i = 0; i < corners.size(); i++) {circle(output, corners[i], 5, Scalar(0, 0, 255), 2);}// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Dsc_Tomasi").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);imshow(QString("Dsc_Tomasi").toStdString(), output);waitKey(0);return;
}

运行结果如下:

在这里插入图片描述

5.2 SIFT 和 SURF 特征

SIFT(尺度不变特征变换)和 SURF(加速稳健特征)是两种经典的尺度不变特征提取算法。

5.2.1 SIFT 特征提取
//SIFT 特征提取
void MainWindow::on_bt17_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"E:/test/QtTest/WidgetTest/TestOpencv/bin/debug64/Out/OutImg/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_GRAYSCALE);//这里要以灰度模式读取图像if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 创建SIFT检测器Ptr<SIFT> sift = SIFT::create();// 检测关键点和计算描述符vector<KeyPoint> keypoints;Mat descriptors;sift->detectAndCompute(image, noArray(), keypoints, descriptors);// 在原图上绘制关键点Mat output;drawKeypoints(image, keypoints, output, Scalar::all(-1), DrawMatchesFlags::DEFAULT);// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Dsc_SIFT").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);imshow(QString("Dsc_SIFT").toStdString(), output);waitKey(0);return;
}

运行结果如下:
在这里插入图片描述

5.2.2 SURF 特征提取
//SURF 特征提取
void MainWindow::on_bt18_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"E:/test/QtTest/WidgetTest/TestOpencv/bin/debug64/Out/OutImg/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_GRAYSCALE);//这里要以灰度模式读取图像if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 创建SURF检测器double hessianThreshold = 400;//注意,这里直接执行时会崩溃,不要慌,代码没问题。//这是由于SURF(加速稳健特征)算法已被移至opencv_contrib模块,并受到专利保护,因此直接使用会导致程序崩溃//SURF 属于非免费算法,OpenCV 4.x 默认不包含该模块,需手动编译带opencv_contrib模块的opencv库Ptr<SURF> surf = SURF::create(hessianThreshold);// 检测关键点和计算描述符vector<KeyPoint> keypoints;Mat descriptors;surf->detectAndCompute(image, noArray(), keypoints, descriptors);// 在原图上绘制关键点Mat output;drawKeypoints(image, keypoints, output, Scalar::all(-1), DrawMatchesFlags::DEFAULT);// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Dsc_SURF").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);imshow(QString("Dsc_SURF").toStdString(), output);waitKey(0);return;
}

由于我的opencv库没有带opencv_contrib模块,故而出现如下报错
在这里插入图片描述
重新使用带opencv_contrib模块的opencv库,如下,运行正常。
在这里插入图片描述

5.3 ORB 特征

ORB(Oriented FAST and Rotated BRIEF)是一种高效的特征提取算法,结合了 FAST 角点检测和 BRIEF 描述符。

//ORB 特征提取
void MainWindow::on_bt19_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_GRAYSCALE);//这里要以灰度模式读取图像if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 创建ORB检测器Ptr<ORB> orb = ORB::create();// 检测关键点和计算描述符vector<KeyPoint> keypoints;Mat descriptors;orb->detectAndCompute(image, noArray(), keypoints, descriptors);// 在原图上绘制关键点Mat output;drawKeypoints(image, keypoints, output, Scalar::all(-1), DrawMatchesFlags::DEFAULT);// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Dsc_ORB").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);imshow(QString("Dsc_ORB").toStdString(), output);waitKey(0);return;
}

运行结果如下:
在这里插入图片描述

六、目标检测与识别

6.1 Haar 级联分类器:人脸检测

Haar 级联分类器是一种基于机器学习的目标检测方法,常用于人脸检测。
Haar级联分类器这些xml文件在哪呢,就按opencv的安装目录下的etc文件夹,拷贝到测试程序生成目录,引入进来。

//Haar 级联分类
void MainWindow::on_bt20_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_COLOR);if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 加载分类器,分类器在opencv安装目录下的etc文件夹,拷贝到测试程序生成目录,引入进来QString strXmlPath = QCoreApplication::applicationDirPath() + QString("/etc/haarcascades/haarcascade_frontalface_default.xml");CascadeClassifier face_cascade;if (!face_cascade.load(strXmlPath.toStdString())) {qDebug()  << QString("无法加载分类器文件!");return;}// 转换为灰度图Mat gray;cvtColor(image, gray, COLOR_BGR2GRAY);equalizeHist(gray, gray);// 检测人脸vector<Rect> faces;face_cascade.detectMultiScale(gray, faces, 1.1, 3, 0, Size(30, 30));// 在原图上绘制检测结果for (size_t i = 0; i < faces.size(); i++) {rectangle(image, faces[i], Scalar(255, 0, 0), 2);}// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);//namedWindow(QString("Dsc_Haar").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);//imshow(QString("Dsc_Haar").toStdString(), output);waitKey(0);return;
}

用我们的测试图片发现,识别很差:
在这里插入图片描述
我们还是换成人物图片进行测试,就以经典的Lena头像,进行测试,发现完美找到人脸部分

在这里插入图片描述
我们在随机换一个人像图片测试,也能正确找到人脸
在这里插入图片描述

6.2 HOG+SVM:行人检测

HOG(方向梯度直方图)结合 SVM(支持向量机)是一种经典的行人检测方法。

//HOG+SVM 行人检测
void MainWindow::on_bt21_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_COLOR);if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 创建HOG描述符和SVM检测器HOGDescriptor hog;hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());// 检测行人vector<Rect> found;vector<double> weights;hog.detectMultiScale(image, found, weights, 0, Size(8, 8), Size(32, 32), 1.05, 2);// 在原图上绘制检测结果for (size_t i = 0; i < found.size(); i++) {rectangle(image, found[i], Scalar(0, 255, 0), 2);}// 显示原始图像和处理后的图像namedWindow(QString("Src").toStdString(), WINDOW_AUTOSIZE);//namedWindow(QString("Dsc_Haar").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Src").toStdString(), image);//imshow(QString("Dsc_Haar").toStdString(), output);waitKey(0);return;
}

找了一个真实的街道行人图片进行测试,基本将所有行人都识别了出来,结果如下:
在这里插入图片描述

七、视频处理

7.1 视频读取与显示

//视频读取显示
void MainWindow::on_bt22_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("视频文件(*.mp4);"));// 读取视频VideoCapture cap(fileName.toStdString());if (!cap.isOpened()) {qDebug() << QString("无法打开视频文件!");return;}// 获取视频帧率和尺寸double fps = cap.get(CAP_PROP_FPS);int width = cap.get(CAP_PROP_FRAME_WIDTH);int height = cap.get(CAP_PROP_FRAME_HEIGHT);qDebug() << QString("视频帧率: ") << fps;qDebug() << QString("视频尺寸: ") << width << "x" << height;// 创建窗口namedWindow(QString("VideoPlay").toStdString(), WINDOW_AUTOSIZE);// 逐帧读取并显示视频Mat frame;while (true) {// 读取一帧cap >> frame;// 检查是否读取到帧if (frame.empty()) {qDebug() << QString("视频播放完毕!");break;}// 显示帧imshow(QString("VideoPlay").toStdString(), frame);// 等待按键事件,控制播放速度if (waitKey(25) >= 0) {break;}}// 释放资源cap.release();destroyAllWindows();return;
}

这是视频开启效果:
在这里插入图片描述

7.2 视频写入

现在使用VideoWriter 在测试一下opencv对视频的写操作。

//视频写入
void MainWindow::on_bt23_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("视频文件(*.mp4);"));// 读取视频VideoCapture cap(fileName.toStdString());if (!cap.isOpened()) {qDebug() << QString("无法打开视频文件!");return;}// 获取视频帧率和尺寸double fps = cap.get(CAP_PROP_FPS);int width = cap.get(CAP_PROP_FRAME_WIDTH);int height = cap.get(CAP_PROP_FRAME_HEIGHT);// 创建视频写入器VideoWriter writer(QString("output.avi").toStdString(), VideoWriter::fourcc('M', 'J', 'P', 'G'), fps, Size(width, height));// 检查写入器是否成功创建if (!writer.isOpened()) {qDebug() << QString("无法创建视频写入器!");return;}// 逐帧处理并写入视频Mat frame;while (true) {// 读取一帧cap >> frame;// 检查是否读取到帧if (frame.empty()) {qDebug() << QString("视频处理完毕!");break;}// 在这里可以对帧进行处理,例如转换为灰度图Mat gray;cvtColor(frame, gray, COLOR_BGR2GRAY);cvtColor(gray, frame, COLOR_GRAY2BGR);  // 转回BGR以保持通道数一致// 写入帧writer.write(frame);// 显示帧imshow("VideoOpt", frame);// 等待按键事件if (waitKey(25) >= 0) {break;}}// 释放资源cap.release();writer.release();destroyAllWindows();return;
}

这是运行后,生成的新的灰度视频:
在这里插入图片描述

7.3 背景减除

背景减除是视频分析中的常用技术,用于从视频中分离出前景物体。

//背景减除
void MainWindow::on_bt24_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"E:/test/QtTest/WidgetTest/TestOpencv/bin/debug64/Out/OutImg/",QString("视频文件(*.mp4);"));// 读取视频VideoCapture cap(fileName.toStdString());if (!cap.isOpened()) {qDebug() << QString("无法打开视频文件!");return;}// 创建背景减除器Ptr<BackgroundSubtractor> bg_subtractor = createBackgroundSubtractorMOG2();// 创建窗口namedWindow(QString("Src_Video").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("ForeMask").toStdString(), WINDOW_AUTOSIZE);namedWindow(QString("Foreground").toStdString(), WINDOW_AUTOSIZE);// 逐帧处理视频Mat frame, fg_mask, fg;while (true) {// 读取一帧cap >> frame;// 检查是否读取到帧if (frame.empty()) {qDebug() << QString("视频处理完毕!");break;}// 应用背景减除bg_subtractor->apply(frame, fg_mask);// 对前景掩码进行形态学操作,减少噪声Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));morphologyEx(fg_mask, fg_mask, MORPH_OPEN, kernel);morphologyEx(fg_mask, fg_mask, MORPH_CLOSE, kernel);// 从原始帧中提取前景fg = Mat::zeros(frame.size(), frame.type());frame.copyTo(fg, fg_mask);// 显示结果imshow(QString("Src_Video").toStdString(), frame);imshow(QString("ForeMask").toStdString(), fg_mask);imshow(QString("Foreground").toStdString(), fg);// 等待按键事件if (waitKey(30) >= 0) {break;}}// 释放资源cap.release();destroyAllWindows();return;
}

再看背景减除后的效果:
在这里插入图片描述

八、机器学习与深度学习

8.1 K近邻算法:KNN

K 近邻算法是一种简单而有效的分类和回归算法。

using namespace cv::ml;
//K近邻算法
void MainWindow::on_bt25_clicked()
{// 生成训练数据int num_samples = 100;Mat train_data(num_samples, 2, CV_32F);Mat labels(num_samples, 1, CV_32S);// 随机生成两类数据RNG rng(12345);for (int i = 0; i < num_samples / 2; i++) {// 第一类数据(标签为0)train_data.at<float>(i, 0) = rng.uniform(0, 50);train_data.at<float>(i, 1) = rng.uniform(0, 50);labels.at<int>(i) = 0;}for (int i = num_samples / 2; i < num_samples; i++) {// 第二类数据(标签为1)train_data.at<float>(i, 0) = rng.uniform(50, 100);train_data.at<float>(i, 1) = rng.uniform(50, 100);labels.at<int>(i) = 1;}// 创建KNN模型Ptr<KNearest> knn = KNearest::create();knn->setDefaultK(3);knn->setIsClassifier(true);// 训练模型Ptr<TrainData> trainData = TrainData::create(train_data, ROW_SAMPLE, labels);knn->train(trainData);// 测试数据Mat test_sample(1, 2, CV_32F);test_sample.at<float>(0, 0) = 87;test_sample.at<float>(0, 1) = 69;// 预测float result = knn->predict(test_sample);qDebug() << QString("测试样本:") << test_sample.at<float>(0, 0) << QString(",") << test_sample.at<float>(0, 1);qDebug() << QString("预测结果:") << result;test_sample.at<float>(0, 0) = 13;test_sample.at<float>(0, 1) = 24;// 预测result = knn->predict(test_sample);qDebug() << QString("测试样本:") << test_sample.at<float>(0, 0) << QString(",") << test_sample.at<float>(0, 1);qDebug() << QString("预测结果:") << result;}

执行结果如下:
在这里插入图片描述

8.2 支持向量机:SVM

支持向量机是一种强大的分类和回归算法。

//SVM支持向量机
void MainWindow::on_bt26_clicked()
{// 生成训练数据int num_samples = 100;Mat train_data(num_samples, 2, CV_32F);Mat labels(num_samples, 1, CV_32S);// 随机生成两类数据RNG rng(12345);for (int i = 0; i < num_samples / 2; i++) {// 第一类数据(标签为-1)train_data.at<float>(i, 0) = rng.uniform(0, 50);train_data.at<float>(i, 1) = rng.uniform(0, 50);labels.at<int>(i) = -1;}for (int i = num_samples / 2; i < num_samples; i++) {// 第二类数据(标签为1)train_data.at<float>(i, 0) = rng.uniform(50, 100);train_data.at<float>(i, 1) = rng.uniform(50, 100);labels.at<int>(i) = 1;}// 创建SVM模型Ptr<SVM> svm = SVM::create();svm->setType(SVM::C_SVC);svm->setKernel(SVM::LINEAR);svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));// 训练模型Ptr<TrainData> trainData = TrainData::create(train_data, ROW_SAMPLE, labels);svm->train(trainData);// 测试数据Mat test_sample(1, 2, CV_32F);test_sample.at<float>(0, 0) = 25;test_sample.at<float>(0, 1) = 25;// 预测float result = svm->predict(test_sample);qDebug() << QString("测试样本:") << test_sample.at<float>(0, 0) << QString(",") << test_sample.at<float>(0, 1);qDebug() << QString("预测结果:") << result;
}

执行结果如下:
在这里插入图片描述

8.3 深度学习:DNN 模块

OpenCV 的 DNN 模块支持加载和运行预训练的深度学习模型。所以我们要把dnn模块头文件和命名空间引入进来。
res10_300x300_ssd_iter_140000.caffemodel和deploy.prototxt是 OpenCV 官方提供的基于 SSD(Single Shot MultiBox Detector)的人脸检测模型。

#include <opencv2/dnn.hpp>
using namespace cv::dnn;  // 或使用cv::dnn::命名空间前缀

加载预训练的模型和配置文件,这些文件需要自己去找,我用的网上下载的模型,拷贝到测试程序生成目录,引入进来。


//DNN深度学习
void MainWindow::on_bt27_clicked()
{QString fileName = QFileDialog::getOpenFileName(this,QString("文件对话框"),"/",QString("图片(*.jpg *.png);"));// 读取图像Mat image = imread(fileName.toStdString(), IMREAD_COLOR);if (image.empty()) {qDebug()  << QString("无法读取图像!");return;}// 加载预训练的模型和配置文件,需要自己去找//QString model_bin = QCoreApplication::applicationDirPath() + QString("/dnn/face_detector/opencv_face_detector_uint8.pb");QString model_bin = QCoreApplication::applicationDirPath() + QString("/dnn/face_detector/res10_300x300_ssd_iter_140000_fp16.caffemodel");QString config_text = QCoreApplication::applicationDirPath() + QString("/dnn/face_detector/deploy.prototxt");// 加载网络Net net = readNetFromCaffe(config_text.toStdString(), model_bin.toStdString());// 检查网络是否加载成功if (net.empty()) {qDebug() << QString("无法加载网络!");return;}// 设置计算后端(可选)net.setPreferableBackend(DNN_BACKEND_OPENCV);net.setPreferableTarget(DNN_TARGET_CPU);// 准备输入数据Mat inputBlob = blobFromImage(image, 1.0, Size(300, 300), Scalar(104.0, 177.0, 123.0), false, false);net.setInput(inputBlob, "data");// 前向传播//Mat detection = net.forward("detection_out");Mat detection = net.forward();// 检查输出有效性if (detection.empty()) {qDebug() << QString("错误:模型推理返回空结果!");return ;}// 安全获取输出维度int detections_size = detection.total() / detection.channels();Mat detectionMat(1, detections_size, CV_32F, detection.ptr<float>());//Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());// 处理检测结果float confidence_threshold = 0.5;for (int i = 0; i < detectionMat.rows; i++) {float confidence = detectionMat.at<float>(i, 2);if (confidence > confidence_threshold) {int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * image.cols);int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * image.rows);int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * image.cols);int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * image.rows);// 绘制边界框rectangle(image, Point(x1, y1), Point(x2, y2), Scalar(0, 255, 0), 2);// 添加置信度文本String label = format("Face: %.2f%%", confidence * 100);int baseLine = 0;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);rectangle(image, Point(x1, y1 - labelSize.height),Point(x1 + labelSize.width, y1 + baseLine),Scalar(255, 255, 255), FILLED);putText(image, label, Point(x1, y1),FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));}}// 显示结果namedWindow(QString("Result").toStdString(), WINDOW_AUTOSIZE);imshow(QString("Result").toStdString(), image);waitKey(0);return;
}

用单个人物图像测试准确度比较高,如果是街拍人群的图像,识别差异很大。

在这里插入图片描述

九、三维重建与立体视觉

9.1 相机标定:calibrateCamera 函数

  • 相机标定是计算机视觉中的基础任务,是三维重建的基础,用于确定相机的内部参数(如焦距、主点、畸变系数)和外部参数(相机在世界坐标系中的位置和姿态)。这些参数对于 3D 重建、目标定位、AR/VR 等应用至关重要。
  • OpenCV 采用张正友标定法(Zhang’s Method),通过拍摄多幅已知模式(如棋盘格)的图像,计算相机参数。核心原理基于针孔相机模型和畸变模型。
  • 通过 OpenCV 的标定工具,可以准确获取相机的内参、外参和畸变系数,从而实现图像去畸变、3D 重建、目标定位等高级应用。

代码示例如下:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>using namespace cv;
using namespace std;int main() {// 准备标定板的世界坐标点vector<vector<Point3f>> object_points;vector<vector<Point2f>> image_points;// 标定板内角点的行数和列数int board_width = 9;int board_height = 6;// 每个方格的大小(单位:毫米)float square_size = 25.0;// 准备标定板上角点的世界坐标 (0,0,0), (1,0,0), (2,0,0) ...vector<Point3f> obj;for (int i = 0; i < board_height; i++) {for (int j = 0; j < board_width; j++) {obj.push_back(Point3f(j * square_size, i * square_size, 0));}}// 读取标定图像vector<String> filenames;glob("calibration_images/*.jpg", filenames);// 检测标定板角点for (size_t i = 0; i < filenames.size(); i++) {Mat image = imread(filenames[i], IMREAD_COLOR);if (image.empty()) {cout << "无法读取图像: " << filenames[i] << endl;continue;}vector<Point2f> corners;//使用findChessboardCorners检测棋盘格角点,并通过cornerSubPix提高角点精度bool found = findChessboardCorners(image, Size(board_width, board_height), corners);if (found) {// 亚像素级角点检测Mat gray;cvtColor(image, gray, COLOR_BGR2GRAY);cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.1));// 绘制角点drawChessboardCorners(image, Size(board_width, board_height), corners, found);// 保存角点和对应的世界坐标image_points.push_back(corners);object_points.push_back(obj);// 显示结果imshow("角点检测", image);waitKey(500);}}destroyAllWindows();

由于手上没有摄像头,在此不做实际测验,只确保代码逻辑正确及编译通过。

http://www.dtcms.com/a/304129.html

相关文章:

  • MySQL(配置)——MariaDB使用
  • 2025北京师范大学数学分析考研试题
  • 15-C语言:第15~16天笔记
  • FreeRTOS在中断上下文中设置事件组,调度很慢的的解决方法
  • 智慧工地系统:科技如何重塑建筑现场?
  • macOS “Sploitlight“漏洞曝光:攻击者可窃取Apple Intelligence缓存数据
  • 用动态的观点看加锁
  • 新升级超值型系列32位单片机MM32G0005
  • C++类中动态内存分配注意手册
  • python新手,学习计划
  • 每日一题7.29
  • 当贝纯净版_海信ip811n海思mv320处理器安卓4.42及9.0主板优盘免拆刷机固件及教程
  • [Token]ALGM: 基于自适应局部-全局token合并的简单视觉Transformer用于高效语义分割, CVPR2024
  • 安卓逆向2-安卓刷机和获取root权限和安装LSPosed框架
  • 博物馆 VR 导览:图形渲染算法+智能讲解技术算法实现及优化
  • 想要批量提取视频背景音乐?FFmpeg 和转换器都安排上
  • 机器学习、深度学习与数据挖掘:三大技术领域的深度解析
  • centos7安装Docker
  • 机器学习、深度学习与数据挖掘:核心技术差异、应用场景与工程实践指南
  • Docker学习相关视频笔记(二)
  • Linux 系统启动与 GRUB2 核心操作指南
  • 7月29日星期二今日早报简报微语报早读
  • Ubuntu上开通Samba网络共享
  • Ubuntu22.04系统安装,Nvidia显卡驱动安装问题
  • RHCE综合项目:分布式LNMP私有博客服务部署
  • Ubuntu25.04轻量虚拟机Multipass使用Shell脚本自动创建并启动不同版本Ubuntu并复制文件
  • ubuntu22.04 安装 petalinux 2021.1
  • 【prompt】Lyra 提示词深度研究
  • Apache Ignite 关于 **负载均衡(Load Balancing)** 的详细介绍
  • 信创国产Linux操作系统汇总:从桌面到服务器,百花齐放