文章目录
0.引言
视频质量遮挡检测已在C++基于opencv4的视频质量检测中有所介绍,本文将详细介绍其优化版本。
1. 原始代码分析
首先,我们来看遮挡检测的原始代码:
#include <opencv2/opencv.hpp>
#include <vector>
/**
* @brief 检测图像中的遮挡情况。
* @param [in] srcImg 输入的图像
* @return 返回一个double类型的数值,范围为0到1。数值越接近1,表示图像中的遮挡程度越高。
*/
double blockDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return -1.0; // 如果输入图像为空,返回-1表示错误
}
cv::Mat grayImg, edges;
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(grayImg, grayImg, cv::Size(3, 3), 0);
cv::Canny(grayImg, edges, 0, 0);
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
// 初始轮廓检测
cv::findContours(edges, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
int initialContourCount = static_cast<int>(hierarchy.size());
// 细化后的轮廓检测
cv::Canny(grayImg, edges, 0, 15);
cv::findContours(edges, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
int refinedContourCount = static_cast<int>(hierarchy.size());
// 防止除以零的情况
if (initialContourCount == 0) {
initialContourCount = 1;
}
double occlusionLevel = 1.0 - static_cast<double>(refinedContourCount) / static_cast<double>(initialContourCount);
return occlusionLevel;
}
1.1 存在的问题
-
Canny边缘检测的阈值设置不合理:在Canny函数中,阈值设为
0
和0
,以及0
和15
,这可能导致边缘检测结果不可靠。 -
错误处理不够明确:当输入图像为空时,返回
-1.0
,但这个值可能与正常的遮挡程度值混淆。
2. 优化方案
针对上述问题,我们对代码进行如下优化:
-
自适应阈值设置:根据图像的特性动态设置Canny边缘检测的阈值,提高边缘检测的可靠性。
-
提高代码可读性:增加详细的注释,使用更具描述性的变量名,提升代码的可读性和可维护性。
-
算法改进:使用Sobel算子计算梯度,基于边缘密度来判断遮挡程度,获得更准确的结果。
3. 优化后的代码
#include <opencv2/opencv.hpp>
#include <vector>
/**
* @brief 检测图像中的遮挡情况。
* @param srcImg 输入的图像
* @return 如果成功,返回一个介于0到1之间的double类型值,值越接近1表示遮挡程度越高;
* 如果输入图像为空,返回-1.0。
*/
double occlusionDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
// 输入图像为空
return -1.0;
}
// 将图像转换为灰度图
cv::Mat grayImg;
if (srcImg.channels() == 3) {
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
} else {
grayImg = srcImg.clone();
}
// 对灰度图进行高斯模糊,减少噪声
cv::GaussianBlur(grayImg, grayImg, cv::Size(5, 5), 0);
// 使用Sobel算子计算梯度
cv::Mat gradX, gradY;
cv::Sobel(grayImg, gradX, CV_64F, 1, 0, 3);
cv::Sobel(grayImg, gradY, CV_64F, 0, 1, 3);
// 计算梯度幅值和方向
cv::Mat magnitude, angle;
cv::cartToPolar(gradX, gradY, magnitude, angle, true);
// 对梯度幅值进行阈值化,得到边缘图
double maxVal;
cv::minMaxLoc(magnitude, nullptr, &maxVal);
cv::Mat edges;
cv::threshold(magnitude, edges, 0.1 * maxVal, 255, cv::THRESH_BINARY);
// 计算边缘密度
double edgeDensity = cv::countNonZero(edges) / static_cast<double>(edges.total());
// 根据边缘密度估计遮挡程度(假设遮挡区域边缘密度较低)
double occlusionLevel = 1.0 - edgeDensity;
// 将结果限定在0到1之间
occlusionLevel = std::clamp(occlusionLevel, 0.0, 1.0);
return occlusionLevel;
}
4. 代码详细解读
4.1. 输入检查
if (srcImg.empty()) {
return std::nullopt;
}
- 目的:确保输入的图像有效。
- 说明:如果输入图像为空,函数返回
std::nullopt
,明确表示错误状态。
4.2. 图像预处理
cv::Mat grayImg;
if (srcImg.channels() == 3) {
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
} else {
grayImg = srcImg.clone();
}
- 目的:将彩色图像转换为灰度图像。
- 说明:灰度图像降低了计算复杂度,适用于后续的梯度和边缘检测。
4.3. 高斯模糊
cv::GaussianBlur(grayImg, grayImg, cv::Size(5, 5), 0);
- 目的:平滑图像,减少噪声对梯度计算的影响。
- 参数解释:
cv::Size(5, 5)
:高斯核的大小,可根据需要调整。0
:高斯核在x方向的标准差,设为0表示根据核大小自动计算。
4.4. 梯度计算
cv::Mat gradX, gradY;
cv::Sobel(grayImg, gradX, CV_64F, 1, 0, 3);
cv::Sobel(grayImg, gradY, CV_64F, 0, 1, 3);
- 目的:计算图像在x和y方向的梯度。
- 参数解释:
CV_64F
:使用64位浮点型,确保梯度值的精度。1, 0
和0, 1
:指定导数的阶数,分别计算x和y方向的一阶导数。3
:Sobel核的大小。
4.5. 计算梯度幅值和方向
cv::Mat magnitude, angle;
cv::cartToPolar(gradX, gradY, magnitude, angle, true);
- 目的:将梯度的x和y分量转换为极坐标形式,得到梯度的幅值和方向。
- 参数解释:
true
:将角度值转换为度数(0-360),否则为弧度。
4.6. 边缘检测
double maxVal;
cv::minMaxLoc(magnitude, nullptr, &maxVal);
cv::Mat edges;
cv::threshold(magnitude, edges, 0.1 * maxVal, 255, cv::THRESH_BINARY);
- 目的:通过阈值化梯度幅值,提取边缘。
- 步骤:
- 使用
cv::minMaxLoc
获取梯度幅值的最大值maxVal
。 - 设定阈值为
0.1 * maxVal
,将高于此阈值的像素设为255(白色),其余设为0(黑色)。
- 使用
4.7. 计算边缘密度
double edgeDensity = cv::countNonZero(edges) / static_cast<double>(edges.total());
- 目的:计算边缘像素占总像素的比例。
- 说明:边缘密度反映了图像中边缘信息的丰富程度。
4.8. 估计遮挡程度
double occlusionLevel = 1.0 - edgeDensity;
- 目的:根据边缘密度估计遮挡程度。
- 假设:遮挡区域的边缘密度较低,因此边缘密度越小,遮挡程度越高。
4.9. 限定结果范围
occlusionLevel = std::clamp(occlusionLevel, 0.0, 1.0);
- 目的:确保遮挡程度在有效范围内。
4.10. 返回结果
return occlusionLevel;
- 说明:返回的
occlusionLevel
为double
类型,范围在0到1之间。 - 无遮挡图像:
occlusionLevel
值接近于0,表示遮挡程度低。 - 遮挡图像:
occlusionLevel
值接近于1,表示遮挡程度高。