文章目录
- 0.引言
- 1. 原始代码分析
- 2. 优化方案
- 3. 优化后的代码
- 4. 代码详细解读
0.引言
视频质量图像抖动检测已在C++基于opencv4的视频质量检测中有所介绍,本文将详细介绍其优化版本。
1. 原始代码分析
首先,我们来看图像抖动检测的原始代码:
#include <algorithm>
#include <cmath>
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <vector>namespace {
constexpr int kMinHessian = 400;
constexpr int kMinKeypoints = 20;
constexpr double kScaleWidth = 800.0;
constexpr double kInvalidReturn = -1.0;
constexpr double kDefaultMinDist = 100.0;
constexpr int kRoiYOffset = 5;
constexpr int kRoiHeightFactor = 3;
constexpr int kGoodMatchesCount = 50;
constexpr int kFilterThresholdFactor = 4;
constexpr int kFilterEndFactor = 3;
} // namespace/*** @brief 检测图像抖动的函数。* @param [in] srcImg 待检测的图像* @param [in] refImg 参考图像* @param [out] offsetX 待检测图像相对于参考图像在x轴上的偏移量* @param [out] offsetY 待检测图像相对于参考图像在y轴上的偏移量* @return 返回函数执行的状态* @retval 0 表示成功* @retval -1 表示失败*/
int JitterDetect(const cv::Mat& srcImg, const cv::Mat& refImg, double& offsetX, double& offsetY) {if (srcImg.empty() || refImg.empty()) {return -1;}cv::Mat img = srcImg.clone();cv::Mat imgRef = refImg.clone();cv::Rect roi(0, img.rows / kRoiYOffset, img.cols, img.rows * kRoiHeightFactor / kRoiYOffset);img = img(roi);imgRef = imgRef(roi);double scale = 1.0;cv::Mat imgScaled, imgRefScaled;if (img.cols > kScaleWidth) {scale = kScaleWidth / static_cast<double>(img.cols);cv::resize(img, imgScaled, cv::Size(static_cast<int>(kScaleWidth), static_cast<int>(scale * img.rows)));cv::resize(imgRef, imgRefScaled, cv::Size(static_cast<int>(kScaleWidth), static_cast<int>(scale * img.rows)));} else {imgScaled = img;imgRefScaled = imgRef;}cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(kMinHessian);std::vector<cv::KeyPoint> keypoints1, keypoints2;detector->detect(imgScaled, keypoints1);detector->detect(imgRefScaled, keypoints2);if (keypoints1.size() < kMinKeypoints || keypoints2.size() < kMinKeypoints) {return -1;}cv::Ptr<cv::xfeatures2d::SURF> extractor = cv::xfeatures2d::SURF::create();cv::Mat descriptors1, descriptors2;extractor->compute(imgScaled, keypoints1, descriptors1);extractor->compute(imgRefScaled, keypoints2, descriptors2);cv::FlannBasedMatcher matcher;std::vector<cv::DMatch> matches;matcher.match(descriptors1, descriptors2, matches);double minDist = kDefaultMinDist;for (const auto& match : matches) {if (match.distance < minDist) {minDist = match.distance;}}std::vector<cv::DMatch> goodMatches;std::vector<float> distances(matches.size());for (size_t i = 0; i < matches.size(); ++i) {distances[i] = matches[i].distance;}std::sort(distances.begin(), distances.end());double distFlag =(matches.size() < kGoodMatchesCount) ? distances[matches.size() - 1] : distances[kGoodMatchesCount - 1];for (size_t i = 0, cntK = 0; i < matches.size() && cntK < kGoodMatchesCount; ++i) {if (matches[i].distance <= distFlag) {goodMatches.push_back(matches[i]);++cntK;}}std::vector<float> moveX(goodMatches.size()), moveY(goodMatches.size());for (size_t i = 0; i < goodMatches.size(); ++i) {moveX[i] = std::abs(keypoints1[goodMatches[i].queryIdx].pt.x - keypoints2[goodMatches[i].trainIdx].pt.x);moveY[i] = std::abs(keypoints1[goodMatches[i].queryIdx].pt.y - keypoints2[goodMatches[i].trainIdx].pt.y);}std::sort(moveX.begin(), moveX.end());std::sort(moveY.begin(), moveY.end());for (size_t p = goodMatches.size() / kFilterThresholdFactor;p < goodMatches.size() * kFilterEndFactor / kFilterThresholdFactor; ++p) {offsetX += moveX[p];offsetY += moveY[p];}offsetX /= (goodMatches.size() / 2);offsetY /= (goodMatches.size() / 2);if (scale != 1.0) {offsetX *= scale;offsetY *= scale;}return 0;
}
以下是原始代码的核心步骤:
- 图像预处理:对输入图像和参考图像进行ROI裁剪和缩放。
- 特征检测与描述子计算:使用SURF算法检测关键点并计算描述子。
- 特征匹配:使用FLANN匹配器匹配描述子。
- 匹配点筛选:根据距离筛选好的匹配点。
- 偏移量计算:手动计算匹配点之间的位移,得到图像的偏移量。
2. 优化方案
我们对代码进行如下优化:
- 移除不必要的图像拷贝:避免使用
clone()
,直接在ROI裁剪后的图像上进行操作,减少内存占用和拷贝时间。 - 合并特征检测和描述子计算:使用
detectAndCompute
方法,将特征检测和描述子计算合并,提高效率。 - 简化特征匹配流程:通过排序匹配结果,直接选择距离最小的前N个匹配点,简化了匹配筛选过程。
- 采用鲁棒的变换估计方法:使用
cv::estimateAffinePartial2D
函数结合RANSAC算法,更准确地估计图像间的平移和旋转,增强鲁棒性。 - 直接提取平移量:从估计的仿射变换矩阵中直接获取偏移量,避免手动计算的复杂性。
3. 优化后的代码
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>namespace {
constexpr int kMinHessian = 400;
constexpr int kMinKeypoints = 20;
constexpr double kScaleWidth = 800.0;
constexpr int kRoiYOffset = 5;
constexpr int kRoiHeightFactor = 3;
} // namespace/*** @brief 检测图像抖动的函数。* @param [in] srcImg 待检测的图像* @param [in] refImg 参考图像* @param [out] offsetX 待检测图像相对于参考图像在x轴上的偏移量* @param [out] offsetY 待检测图像相对于参考图像在y轴上的偏移量* @return 返回函数执行的状态* @retval 0 表示成功* @retval -1 表示失败*/
int JitterDetect(const cv::Mat& srcImg, const cv::Mat& refImg, double& offsetX, double& offsetY) {if (srcImg.empty() || refImg.empty()) {return -1;}// 定义ROI区域int roiY = srcImg.rows / kRoiYOffset;int roiHeight = srcImg.rows * kRoiHeightFactor / kRoiYOffset;cv::Rect roi(0, roiY, srcImg.cols, roiHeight);// 裁剪图像到ROI区域cv::Mat img = srcImg(roi);cv::Mat imgRef = refImg(roi);// 如果图像宽度大于设定值,则缩放图像double scale = 1.0;if (img.cols > kScaleWidth) {scale = kScaleWidth / static_cast<double>(img.cols);cv::resize(img, img, cv::Size(), scale, scale);cv::resize(imgRef, imgRef, cv::Size(), scale, scale);}// 初始化特征检测器和描述子提取器cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(kMinHessian);// 检测并计算关键点和描述子std::vector<cv::KeyPoint> keypoints1, keypoints2;cv::Mat descriptors1, descriptors2;detector->detectAndCompute(img, cv::noArray(), keypoints1, descriptors1);detector->detectAndCompute(imgRef, cv::noArray(), keypoints2, descriptors2);if (keypoints1.size() < kMinKeypoints || keypoints2.size() < kMinKeypoints) {return -1;}// 使用FLANN匹配器匹配描述子cv::FlannBasedMatcher matcher;std::vector<cv::DMatch> matches;matcher.match(descriptors1, descriptors2, matches);if (matches.empty()) {return -1;}// 按距离排序匹配结果std::sort(matches.begin(), matches.end(), [](const cv::DMatch& a, const cv::DMatch& b) {return a.distance < b.distance;});// 选择前N个好的匹配const int numGoodMatches = std::min(50, static_cast<int>(matches.size()));std::vector<cv::DMatch> goodMatches(matches.begin(), matches.begin() + numGoodMatches);// 提取匹配的关键点坐标std::vector<cv::Point2f> points1, points2;for (const auto& match : goodMatches) {points1.push_back(keypoints1[match.queryIdx].pt);points2.push_back(keypoints2[match.trainIdx].pt);}// 使用RANSAC估计仿射变换矩阵cv::Mat inliers;cv::Mat affine = cv::estimateAffinePartial2D(points1, points2, inliers, cv::RANSAC);if (affine.empty()) {return -1;}// 提取平移量offsetX = affine.at<double>(0, 2);offsetY = affine.at<double>(1, 2);// 根据缩放比例调整偏移量if (scale != 1.0) {offsetX /= scale;offsetY /= scale;}return 0;
}
4. 代码详细解读
流程说明:
- 开始:函数
JitterDetect
开始执行。 - 检查输入图像是否为空:如果输入图像为空,返回错误。
- 定义ROI区域:根据预设的偏移量和比例,定义感兴趣区域(ROI)。
- 裁剪图像到ROI:将输入图像和参考图像裁剪到指定的ROI区域。
- 检查图像是否需要缩放:如果图像宽度大于设定的最大宽度,进行缩放。
- 检测并计算关键点和描述子:使用SURF算法检测关键点并计算描述子。
- 检查关键点数量是否足够:如果关键点数量不足,返回错误。
- 匹配描述子:使用FLANN匹配器匹配两个图像的描述子。
- 检查匹配结果是否为空:如果没有找到匹配,返回错误。
- 排序匹配结果:将匹配结果按距离从小到大排序。
- 选择好的匹配点:选择距离最小的前N个匹配点。
- 提取匹配的关键点坐标:从好的匹配中提取对应的关键点坐标。
- 估计仿射变换矩阵:使用RANSAC算法估计图像间的仿射变换。
- 检查仿射变换矩阵是否有效:如果估计失败,返回错误。
- 提取平移量:从仿射变换矩阵中获取偏移量。
- 调整偏移量:根据图像缩放比例,调整偏移量。
- 返回成功状态:函数执行成功,返回0。