0.引言
视频质量图像清晰度检测已在C++基于opencv4的视频质量检测中有所介绍,本文将详细介绍其优化版本。
1. 原始算法实现
原始代码:
double sharpnessDetect(const cv::Mat& srcImg) {
const int kGaussianSize = 3; // 高斯模糊的核大小
cv::Mat grayImg;
if (srcImg.channels() != 1) {
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
} else {
grayImg = srcImg;
}
cv::Mat blurredImg;
cv::GaussianBlur(grayImg, blurredImg, cv::Size(kGaussianSize, kGaussianSize), 0);
uint64_t sumFver = 0;
uint64_t sumFhor = 0;
uint64_t sumVver = 0;
uint64_t sumVhor = 0;
double blurFactor = 0.0;
for (int r = 0; r < grayImg.rows; ++r) {
for (int c = 0; c < grayImg.cols; ++c) {
uint64_t diffFver = 0;
uint64_t diffFhor = 0;
uint64_t diffBver = 0;
uint64_t diffBhor = 0;
if (r != 0) {
diffFver = static_cast<uint64_t>(std::abs(grayImg.at<uchar>(r, c) - grayImg.at<uchar>(r - 1, c)));
}
if (c != 0) {
diffFhor = static_cast<uint64_t>(std::abs(grayImg.at<uchar>(r, c) - grayImg.at<uchar>(r, c - 1)));
}
if (r != 0) {
diffBver = static_cast<uint64_t>(std::abs(blurredImg.at<uchar>(r, c) - blurredImg.at<uchar>(r - 1, c)));
}
if (c != 0) {
diffBhor = static_cast<uint64_t>(std::abs(blurredImg.at<uchar>(r, c) - blurredImg.at<uchar>(r, c - 1)));
}
uint64_t verDiff = (diffFver > diffBver) ? (diffFver - diffBver) : 0;
uint64_t horDiff = (diffFhor > diffBhor) ? (diffFhor - diffBhor) : 0;
sumFver += diffFver;
sumFhor += diffFhor;
sumVver += verDiff;
sumVhor += horDiff;
}
}
double bFver = (static_cast<double>(sumFver - sumVver)) / (static_cast<double>(sumFver) + 1.0);
double bFhor = (static_cast<double>(sumFhor - sumVhor)) / (static_cast<double>(sumFhor) + 1.0);
blurFactor = (bFver > bFhor) ? bFver : bFhor;
return 1.0 - blurFactor;
}
以下是原始代码的主要步骤:
- 图像预处理:将输入图像转换为灰度图。
- 高斯模糊:对灰度图像进行高斯模糊处理,得到模糊图像。
- 梯度计算:通过遍历每个像素,计算原始图像和模糊图像在垂直和水平方向上的梯度差异。
- 模糊因子计算:根据梯度差异,计算模糊因子,进而评估图像的清晰度。
存在的问题:
- 效率低下:使用嵌套的
for
循环遍历每个像素,手动计算梯度,处理大尺寸图像时效率较低。
2. 优化思路
为了解决上述问题,我们对原始算法进行了以下优化:
- 向量化操作,避免显式循环:利用OpenCV的矩阵运算和函数,对整个图像进行批量处理,提高计算效率。
- 使用Sobel算子计算梯度:Sobel算子是常用的梯度计算方法,能够有效地提取图像的边缘信息。
3. 优化后的代码
以下是优化后的sharpnessDetect
函数:
double sharpnessDetect(const cv::Mat& srcImg) {
const int kGaussianSize = 3; // 高斯模糊的核大小
cv::Mat grayImg;
if (srcImg.channels() != 1) {
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
} else {
grayImg = srcImg;
}
// 对灰度图像进行高斯模糊
cv::Mat blurredImg;
cv::GaussianBlur(grayImg, blurredImg, cv::Size(kGaussianSize, kGaussianSize), 0);
// 计算原始图像和模糊图像的梯度
cv::Mat gradXOrig, gradYOrig;
cv::Mat gradXBlur, gradYBlur;
cv::Sobel(grayImg, gradXOrig, CV_64F, 1, 0, 3);
cv::Sobel(grayImg, gradYOrig, CV_64F, 0, 1, 3);
cv::Sobel(blurredImg, gradXBlur, CV_64F, 1, 0, 3);
cv::Sobel(blurredImg, gradYBlur, CV_64F, 0, 1, 3);
// 计算梯度的绝对值
cv::Mat absGradXOrig = cv::abs(gradXOrig);
cv::Mat absGradYOrig = cv::abs(gradYOrig);
cv::Mat absGradXBlur = cv::abs(gradXBlur);
cv::Mat absGradYBlur = cv::abs(gradYBlur);
// 计算梯度差的正值部分
cv::Mat diffX = absGradXOrig - absGradXBlur;
cv::Mat diffY = absGradYOrig - absGradYBlur;
diffX = cv::max(diffX, 0);
diffY = cv::max(diffY, 0);
// 计算梯度的总和
double sumFhor = cv::sum(absGradXOrig)[0];
double sumFver = cv::sum(absGradYOrig)[0];
// 计算梯度差的总和
double sumVhor = cv::sum(diffX)[0];
double sumVver = cv::sum(diffY)[0];
// 计算模糊因子
double bFver = (sumFver - sumVver) / (sumFver + 1e-6);
double bFhor = (sumFhor - sumVhor) / (sumFhor + 1e-6);
double blurFactor = std::max(bFver, bFhor);
// 返回清晰度得分
return 1.0 - blurFactor;
}
4. 代码详细解读
流程说明:
- 开始:函数
sharpnessDetect
开始执行。 - 检查图像通道数:判断输入图像是否为灰度图。
- 如果不是,转换为灰度图。
- 如果是,跳过转换。
- 高斯模糊:对灰度图像进行高斯模糊处理,得到模糊图像。
- 计算梯度:使用Sobel算子计算原始图像和模糊图像在x和y方向的梯度。
- 计算梯度的绝对值:对梯度矩阵取绝对值。
- 计算梯度差的正值部分:计算原始梯度和模糊梯度之间的差异,保留正值部分。
- 计算梯度的总和:分别计算原始梯度和差异梯度的总和。
- 计算模糊因子:根据梯度总和计算模糊因子。
- 返回结果:根据模糊因子计算清晰度得分,返回结果。
优化细节解读
使用Sobel算子计算梯度:
cv::Sobel
函数可以高效地计算图像在x和y方向的梯度,避免了手动计算相邻像素差异的繁琐过程。向量化操作:通过
cv::abs
、cv::sum
等函数,对整个矩阵进行操作,充分利用了底层的优化和并行计算能力。梯度差的正值部分:使用
cv::max
函数,将梯度差中的负值置零,保留正值部分,与原始代码的逻辑一致。防止除零错误:在计算模糊因子时,分母加上了一个很小的值
1e-6
,防止除以零的情况。数据类型选择:使用
CV_64F
(双精度浮点型)确保计算的精度,避免数据溢出和精度损失。