文章目录
C++基于OpenCV的视频质量检测原理来源
请参考原作者的文章,本文引用了实现的思想说明:原文链接
系列文章如下:
视频质量诊断----亮度异常检测
视频质量诊断----色度异常检测
视频质量诊断----雪花噪声检测
视频质量诊断----条纹噪声检测
视频质量诊断----模糊检测
视频质量诊断----信号丢失检测
视频质量诊断----遮挡检测
视频质量诊断----画面冻结检测
视频质量诊断----PTZ云台运动检测
视频质量诊断----画面抖动检测
1. 遮挡检测
1.1 遮挡检测定义
遮挡检测一般是摄像头被异物遮挡,呈现出整个场景或某一部分场景看不到的情况。被遮挡住的部分一般都呈偏黑色。
1.2 原理
将彩色图像二值化,偏黑的部分为前景,其他部分为背景。对前景进行连通区域检测,求得最大连通区域面积。该面积与整幅图像面积的比较即为遮挡率。
1.3 C++实现代码
#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 blockEffect = 1.0 - static_cast<double>(refinedContourCount) / static_cast<double>(initialContourCount);
return blockEffect;
}
2. 信号丢失检测
2.1 信号丢失检测定义
信号丢失检测也称无信号检测,一般当DVR/NVR某些通道没接上摄像头时,会显示黑屏无信号。而IPC无信号时无法返回任何图像信息,也就无法通过图像算法检测到。
2.2 原理
将彩色图像二值化,偏黑的部分为前景,其他部分为背景。对前景进行连通区域检测,求得最大连通区域面积。该面积与整幅图像面积的比较即为信号丢失率。
2.3 C++实现代码
#include <cstdint>
#include <opencv2/opencv.hpp>
/**
* @brief 检测图片是否为无信号时传输过来的图片。
* @param [in] srcImg 输入的图片
* @return 返回一个double类型的数值,范围为0到1。数值越接近1,越有可能是无信号时传输过来的图片。
*/
double signalDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return 0.0;
}
int32_t width = srcImg.cols;
int32_t height = srcImg.rows;
// 缩小图像尺寸,直到宽度和高度不大于352和288
while (width > 352 && height > 288) {
width >>= 1;
height >>= 1;
}
cv::Mat resizedImg, grayImg;
cv::resize(srcImg, resizedImg, cv::Size(width, height));
cv::cvtColor(resizedImg, grayImg, cv::COLOR_BGR2GRAY);
// 计算高斯模糊尺寸
int32_t blurWidth = static_cast<int32_t>(0.2 * grayImg.cols);
if (blurWidth % 2 == 0) {
blurWidth += 1;
}
int32_t blurHeight = static_cast<int32_t>(0.3 * grayImg.rows);
if (blurHeight % 2 == 0) {
blurHeight += 1;
}
// 应用高斯模糊和Canny边缘检测
cv::GaussianBlur(grayImg, grayImg, cv::Size(21, 21), 0);
cv::Canny(grayImg, grayImg, 0, 0);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(grayImg, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
double sum = static_cast<double>(contours.size());
double imgArea = static_cast<double>(grayImg.cols * grayImg.rows);
double res = (sum * sum) / imgArea;
res *= res;
double result = 1.0 - (res / 100.0 > 1.0 ? 1.0 : res / 100.0);
return result;
}
3. 亮度异常检测
3.1 亮度异常检测定义
亮度异常检测一般包括偏暗检测和偏亮检测,也有称过暗过亮检测。这算法简单,只需要一帧图像的亮度值作为判断就行。
3.2 原理
将彩色图像转化为灰度图像。求图像的平均灰度值G(整幅或ROI区域),该值就是图像的亮度值。定义阈值A,B。当G ∈ [0, A] 认为图像偏暗,当G ∈ [B, 255] 认为图像偏亮。
3.3 C++实现代码
#include <cstdint>
#include <opencv2/opencv.hpp>
/**
* @brief 检测图片的亮度。
* @param [in] srcImg 输入的图片
* @return 返回一个double类型的数值,范围为0到1。数值越接近1,说明图片越亮;反之越暗。
*/
double brightnessDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return -1.0;
}
cv::Mat labImg;
cv::cvtColor(srcImg, labImg, cv::COLOR_BGR2Lab);
double brightness = 0.0;
const int32_t width = srcImg.cols;
const int32_t height = srcImg.rows;
for (int32_t r = 0; r < height; ++r) {
for (int32_t c = 0; c < width; ++c) {
uint8_t luminance = labImg.at<cv::Vec3b>(r, c)[0];
brightness += static_cast<double>(luminance) / 2.55 / static_cast<double>(width * height);
}
}
return brightness / 100.0;
}
4. 雪花噪声检测
4.1 雪花噪声定义
雪花噪声即椒盐噪声,以前黑白电视常见的噪声现象。
4.2 原理
准备0°,45°,90°,135°四个方向的卷积模板。用图像先和四个模板做卷积,用四个卷积绝对值最小值Min来检测噪声点。求灰度图gray与其中值滤波图median。判断噪声点:fabs(median - gray) > 10 && min > 0.1。噪声点占整幅图像的比例即为雪花噪声率。
4.3 C++实现代码
#include <cmath>
#include <cstdint>
#include <opencv2/opencv.hpp>
/**
* @brief 检测图片中的雪花噪点数。
* @param [in] srcImg 输入的图片
* @return 返回一个double类型的数值,范围为0到1。数值越接近1,说明雪花噪点越多;反之越少。
*/
double snowNoiseDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return -1.0;
}
cv::Mat grayImg;
if (srcImg.channels() == 1) {
grayImg = srcImg;
} else {
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
}
cv::Mat filteredImg;
cv::Mat kernel = (cv::Mat_<double>(3, 3) << 1, 2, 1, 2, 4, 2, 1, 2, 1);
kernel /= 16.0;
cv::filter2D(grayImg, filteredImg, grayImg.depth(), kernel, cv::Point(-1, -1));
double signalPower = 0.0;
double noisePower = 0.0;
for (int r = 0; r < grayImg.rows; ++r) {
for (int c = 0; c < grayImg.cols; ++c) {
int filteredPixel = filteredImg.at<uchar>(r, c);
int originalPixel = grayImg.at<uchar>(r, c);
signalPower += static_cast<double>(originalPixel) * static_cast<double>(originalPixel) / 255.0 / 255.0;
noisePower += static_cast<double>(originalPixel - filteredPixel) *
static_cast<double>(originalPixel - filteredPixel) / 255.0 / 255.0;
}
}
if (signalPower < 1.0) {
signalPower = 1.0;
}
if (noisePower < 1.0) {
noisePower = 1.0;
}
double snr = 20.0 * std::log10(signalPower / noisePower);
return snr;
}
5. 模糊检测(清晰度检测)
5.1 模糊定义
模糊一般是摄像头焦距没调好造成的画面模糊。
5.2 原理
把图像分割成N*M的区域。求每个区域的对比度:(max - min) / max。求总的平均对比度即为模糊率。
5.3 C++实现代码
#include <cstdint>
#include <opencv2/opencv.hpp>
/**
* @brief 该函数用于检测图片的清晰度。
* @param [in] srcImg 输入的图片
* @return 返回一个double类型的数值,范围为0到1。数值越接近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;
}
6. 画面抖动检测
6.1 抖动定义
当摄像头立杆不稳或因车辆引起地面振动时,视频画面就会发生抖动。
6.2 原理
每隔N帧取一帧。对取到的每帧进行特征点提取。对检测的相邻两帧进行特征点匹配。得到匹配矩阵,当匹配矩阵大于A时认为这两帧画面有抖动。当抖动帧数大于B时认为画面发生抖动。
6.3 C++实现代码
#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;
}
7. 摄像机移动检测
7.1 抖动定义
PTZ云台运动检测是通过配合云台运动的功能检测云台运动是否正常。
7.2 原理
取云台运动前N帧图像,进行背景建模,得到运动前背景A。
设备发送云台运动指令,让云台进行运动,改变场景。
取云台运动后N帧图像,进行背景建模,得到运动后背景B。
对比A,B颜色直方图的相似度,大于K时认为PTZ云台运动有故障。
7.3 C++实现代码
#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 kGoodMatchesFactor = 20;
constexpr int kDirectionThreshold = 2;
} // namespace
/**
* @brief 检测图像位移的函数
* @param [in] srcImg 待检测的图像
* @param [in] srcImgRef 参考图像
* @param [out] offsetX 水平方向的位移
* @param [out] offsetY 垂直方向的位移
* @return 匹配到的关键点对中的最小距离,值越小表示两个关键点越相似
*/
double CameraMoveDetect(const cv::Mat& srcImg, const cv::Mat& srcImgRef, double& offsetX, double& offsetY) {
if (srcImg.empty() || srcImgRef.empty()) {
return kInvalidReturn;
}
cv::Mat img = srcImg.clone();
cv::Mat imgRef = srcImgRef.clone();
cv::Rect roi(0, img.rows / kRoiYOffset, img.cols, img.rows * kRoiHeightFactor / kRoiYOffset);
img = img(roi);
imgRef = imgRef(roi);
cv::Mat imgScaled, imgRefScaled;
double scale = 1.0;
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;
}
auto 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) {
offsetX = 0;
offsetY = 0;
return kInvalidReturn;
}
auto 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::sort(matches.begin(), matches.end(),
[](const cv::DMatch& a, const cv::DMatch& b) { return a.distance < b.distance; });
std::vector<cv::DMatch> goodMatches(matches.begin(),
matches.begin() + std::min(matches.size(), matches.size() / kGoodMatchesFactor));
std::vector<float> moveX(goodMatches.size()), moveY(goodMatches.size());
int xPos = 0, xNeg = 0, xZero = 0;
int yPos = 0, yNeg = 0, yZero = 0;
for (size_t i = 0; i < goodMatches.size(); ++i) {
moveX[i] = keypoints1[goodMatches[i].queryIdx].pt.x - keypoints2[goodMatches[i].trainIdx].pt.x;
moveY[i] = keypoints1[goodMatches[i].queryIdx].pt.y - keypoints2[goodMatches[i].trainIdx].pt.y;
if (moveX[i] > 0) {
++xPos;
} else if (moveX[i] < 0) {
++xNeg;
} else {
++xZero;
}
if (moveY[i] > 0) {
++yPos;
} else if (moveY[i] < 0) {
++yNeg;
} else {
++yZero;
}
}
int maxX = std::max({xPos, xNeg, xZero});
int maxY = std::max({yPos, yNeg, yZero});
if (maxX <= goodMatches.size() / kDirectionThreshold || maxY <= goodMatches.size() / kDirectionThreshold) {
return kInvalidReturn;
}
std::sort(moveX.begin(), moveX.end());
std::sort(moveY.begin(), moveY.end());
offsetX = 0;
offsetY = 0;
if (xPos == maxX) {
offsetX = std::accumulate(moveX.begin() + xZero + xNeg, moveX.begin() + xZero + xNeg + xPos / 2, 0.0);
} else if (xNeg == maxX) {
offsetX = std::accumulate(moveX.begin(), moveX.begin() + xNeg / 2, 0.0,
[](double sum, double val) { return sum + std::abs(val); });
}
if (yPos == maxY) {
offsetY = std::accumulate(moveY.begin() + yZero + yNeg, moveY.begin() + yZero + yNeg + yPos / 2, 0.0);
} else if (yNeg == maxY) {
offsetY = std::accumulate(moveY.begin(), moveY.begin() + yNeg / 2, 0.0,
[](double sum, double val) { return sum + std::abs(val); });
}
offsetX /= (maxX * 2);
offsetY /= (maxY * 2);
if (scale != 1.0) {
offsetX *= scale;
offsetY *= scale;
}
return minDist;
}
8. 画面冻结检测
8.1 画面冻结定义
PTZ云台运动检测是通过配合云台运动的功能检测云台运动是否正常。
8.2 原理
-
捕获前N帧作为背景A。
-
发送云台运动指令,改变场景。
-
捕获后N帧作为背景B。
-
计算背景A和B的颜色直方图,并计算相似度。
-
判断相似度是否小于阈值K,若是则判定为云台运动有故障
8.3 C++ 实现代码
/**
* @brief 检测画面冻结的函数
* @param[in] srcImg 输入的图像帧
* @return 如果检测到画面冻结返回true,否则返回false
*/
bool ScreenFreezeDetection(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return true; // 直接返回true表示检测故障
}
cv::Mat backgroundA, backgroundB;
// 1. 获取云台运动前的背景A
static int frameCount = 0;
if (frameCount < NUM_FRAMES) {
if (frameCount == 0) {
backgroundA = cv::Mat::zeros(srcImg.size(), srcImg.type());
}
cv::accumulate(srcImg, backgroundA);
++frameCount;
if (frameCount == NUM_FRAMES) {
backgroundA /= NUM_FRAMES;
}
return false;
}
// 2. 发送云台运动指令,改变场景
// 3. 获取云台运动后的背景B
if (frameCount < 2 * NUM_FRAMES) {
int currentFrameIndex = frameCount - NUM_FRAMES;
if (currentFrameIndex == 0) {
backgroundB = cv::Mat::zeros(srcImg.size(), srcImg.type());
}
cv::accumulate(srcImg, backgroundB);
++frameCount;
if (frameCount == 2 * NUM_FRAMES) {
backgroundB /= NUM_FRAMES;
}
return false;
}
// 4. 计算背景A和背景B的颜色直方图
cv::Mat histA, histB;
int histSize = 256;
float range[] = {0, 256};
const float* histRange = {range};
cv::calcHist(&backgroundA, 1, 0, cv::Mat(), histA, 1, &histSize, &histRange, true, false);
cv::calcHist(&backgroundB, 1, 0, cv::Mat(), histB, 1, &histSize, &histRange, true, false);
// 5. 计算直方图的相似度(使用相关性比较方法)
double histSimilarity = cv::compareHist(histA, histB, cv::HISTCMP_CORREL);
// 6. 判断相似度是否小于阈值,如果小于则认为画面冻结
if (histSimilarity > HIST_SIM_THRESHOLD) {
return true;
} else {
return false;
}
}
9 条纹检测
9.1 条纹定义
条纹噪声是带条状的噪声。
9.2 原理
-
将输入图像转换到YCrCb色彩空间,并提取Cb分量。
-
对Cb分量进行离散傅里叶变换(DFT)。
-
计算频谱图中的幅度,并应用阈值化操作得到二值图像。
-
统计二值图像中亮点的数量,输出亮点数量供调试。
9.3 C++代码实现
#include <opencv2/opencv.hpp>
namespace {
constexpr int DFT_THRESHOLD = 100; // 异常亮点阈值
}
/**
* @brief 条纹检测函数
* @param[in] srcImg 彩色输入图像
* @return true表示检测到条纹,false表示未检测到
*/
bool StripeDetection(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return false;
}
cv::Mat ycrcb;
cv::cvtColor(srcImg, ycrcb, cv::COLOR_BGR2YCrCb);
std::vector<cv::Mat> channels;
cv::split(ycrcb, channels);
cv::Mat cb = channels[1];
cv::Mat dftImage;
cv::dft(cb, dftImage, cv::DFT_COMPLEX_OUTPUT);
cv::Mat magnitude;
cv::split(dftImage, channels);
cv::magnitude(channels[0], channels[1], magnitude);
cv::Mat magnitudeThreshold;
cv::threshold(magnitude, magnitudeThreshold, DFT_THRESHOLD, 255, cv::THRESH_BINARY);
int numBrightPoints = cv::countNonZero(magnitudeThreshold);
return numBrightPoints > DFT_THRESHOLD;
}
10. 色度异常检测
10.1 定义
色度异常检测一般称为偏色检测。 即图像为某一范围颜色值分布过多而导致图像整体偏色的情况。
10.2 原理
- 将输入图像转换到HSV色彩空间,并提取H分量(色相)。
- 计算H分量的直方图,用于分析颜色分布。
- 计算直方图中最大bin的值,并将其归一化到图像总像素数,得到偏色值。
- 返回偏色值,范围在0到1之间。偏色值越接近1,表示图像偏色越严重;越接近0,表示图像颜色分布越均匀。
10.3 C++实现代码
#include <opencv2/opencv.hpp>
/**
* @brief 色度异常检测函数,用于检测图像的偏色情况
* @param[in] srcImg 输入的彩色图像
* @return 返回偏色值,范围在0到1之间。偏色值越接近1,表示图像偏色越严重;越接近0,表示图像颜色分布越均匀。
*/
double ChromaticAberrationDetect(const cv::Mat& srcImg) {
if (srcImg.empty()) {
return -1.0;
}
cv::Mat hsvImg;
cv::cvtColor(srcImg, hsvImg, cv::COLOR_BGR2HSV);
std::vector<cv::Mat> channels;
cv::split(hsvImg, channels);
cv::Mat hue = channels[0];
int histSize = 256;
float range[] = {0, 256};
const float* histRange = {range};
bool uniform = true;
bool accumulate = false;
cv::Mat hist;
cv::calcHist(&hue, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);
double maxVal = 0;
cv::minMaxLoc(hist, 0, &maxVal);
double aberrationValue = maxVal / (srcImg.rows * srcImg.cols);
return aberrationValue;
}