C++基于opencv4的视频质量检测

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;
}

  • 9
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值