# libVCheck **Repository Path**: H-kernel/libvcheck ## Basic Information - **Project Name**: libVCheck - **Description**: C/C++实现的视频质量诊断库,支持亮度异常、噪音异常、抖动等检测 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-04-14 - **Last Updated**: 2026-04-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # libvcheck [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) libvcheck 是一个 C/C++ 视频质量检测库,支持 RTSP 直播流和 MP4 文件分析。可检测 10 类视频异常:亮度异常、对比度异常、色偏、清晰度不足、噪声过高、条纹、冻帧、抖动、遮挡和场景切换。 --- ## 目录 - [快速开始](#快速开始) - [依赖](#依赖) - [API 概览](#api-概览) - [检测算法原理](#检测算法原理) 1. [亮度检测 (Brightness)](#1-亮度检测-brightness) 2. [对比度检测 (Contrast)](#2-对比度检测-contrast) 3. [色偏检测 (Color Cast)](#3-色偏检测-color-cast) 4. [清晰度检测 (Sharpness)](#4-清晰度检测-sharpness) 5. [噪声检测 (Noise)](#5-噪声检测-noise) 6. [条纹检测 (Streak)](#6-条纹检测-streak) 7. [遮挡检测 (Occlusion)](#7-遮挡检测-occlusion) 8. [冻帧检测 (Freeze)](#8-冻帧检测-freeze) 9. [抖动检测 (Jitter)](#9-抖动检测-jitter) 10. [场景切换检测 (Scene Change)](#10-场景切换检测-scene-change) - [严重程度分级](#严重程度分级) - [架构概览](#架构概览) --- ## 快速开始 ```bash # Autotools 构建(生产环境) ./autogen.sh ./configure make sudo make install # 简化 Makefile(开发用,无需 autotools) make -f Makefile.simple make -f Makefile.simple check ``` --- ## 依赖 | 依赖 | 版本要求 | |------|---------| | FFmpeg | libavformat / libavcodec / libswscale / libavutil >= 58 | | OpenCV | 4.x | | 编译器 | C++17 | | 线程 | pthreads | --- ## API 概览 公共 API 使用 C 语言接口,以 `vcheck_` 为前缀,通过不透明句柄操作: ```c // 异步回调模式(适用于 RTSP 直播流) vcheck_context_t *ctx = vcheck_context_create(); vcheck_stream_t *st = vcheck_stream_create(ctx, url, callback, userdata); vcheck_stream_start(st); // ... vcheck_stream_stop(st); // 同步拉取模式(适用于 MP4 批处理) vcheck_result_t result; while (vcheck_stream_read_frame(st, &result) == 0) { // 处理 result } ``` 详细接口见 `include/libvcheck/vcheck.h`。 --- ## 检测算法原理 所有检测器继承自统一的 `Detector` 基类,由 `DetectorPipeline` 对每一帧依次调用。检测器分为两类: - **空间检测器(Spatial)**:仅分析当前帧,共 7 个 - **时序检测器(Temporal)**:依赖帧间历史,共 3 个 --- ### 1. 亮度检测 (Brightness) **类型**:空间检测 #### 图像原理 人眼对亮度的感知对应图像像素的平均灰度值。视频图像在 BGR 色彩空间中,每个像素由蓝(B)、绿(G)、红(R)三个通道组成,各通道取值范围为 0–255。将三通道均值作为帧的整体亮度指标,能快速反映画面过暗(欠曝)或过亮(过曝)的问题。 #### 算法步骤 1. 对当前帧调用 `cv::mean()`,分别获取 B、G、R 三通道的均值 $\mu_B,\mu_G,\mu_R$。 2. 计算整体亮度: $$\text{brightness} = \frac{\mu_B + \mu_G + \mu_R}{3}$$ 3. 与配置阈值比较: - 若 $\text{brightness} < \text{threshold}$:过暗,计算不足比率 $r = 1 - \frac{\text{brightness}}{\text{threshold}}$ - 若 $\text{brightness} > \text{threshold\_hi}$:过亮,计算超出比率 $r = \frac{\text{brightness} - \text{threshold\_hi}}{255 - \text{threshold\_hi}}$ #### 严重程度 | 比率 $r$ | 级别 | |---------|------| | $r > 0.7$ | HIGH | | $r > 0.4$ | MEDIUM | | 其他 | LOW | --- ### 2. 对比度检测 (Contrast) **类型**:空间检测 #### 图像原理 对比度反映图像中亮暗区域的差异程度。低对比度的画面看起来灰蒙蒙、缺乏层次感,通常由雾霾、镜头污染或信号劣化引起。统计学中,标准差是衡量数据离散程度的基本指标——将灰度图像的像素标准差作为对比度度量,是一种高效且鲁棒的方法。 #### 算法步骤 1. 将 BGR 帧转换为灰度图:`cv::cvtColor(frame, gray, CV_BGR2GRAY)` 2. 用 `cv::meanStdDev()` 计算灰度图的均值 $\mu$ 和标准差 $\sigma$: $$\sigma = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(p_i - \mu)^2}$$ 3. 以 $\sigma$ 作为对比度度量值,若 $\sigma < \text{threshold}$,则触发检测。 4. 计算比率 $r = \frac{\sigma}{\text{threshold}}$,判断严重程度。 #### 严重程度 | 比率 $r$ | 级别 | |---------|------| | $r < 0.3$ | HIGH | | $r < 0.6$ | MEDIUM | | 其他 | LOW | --- ### 3. 色偏检测 (Color Cast) **类型**:空间检测 #### 图像原理 正常画面的 RGB 三通道能量应大致均衡。当摄像头白平衡失调、光源色温异常或信号传输错误时,某一通道会显著偏高,造成画面整体偏红、偏绿或偏蓝。通过计算各通道均值相对于三通道平均值的最大偏差比率,可定量衡量色偏程度。 #### 算法步骤 1. 计算 B、G、R 三通道均值 $\mu_B,\mu_G,\mu_R$。 2. 计算三通道平均值: $$\bar{\mu} = \frac{\mu_B + \mu_G + \mu_R}{3}$$ 3. 若 $\bar{\mu} < 1.0$(近黑帧),跳过检测。 4. 计算各通道偏差: $$d_C = |\mu_C - \bar{\mu}|, \quad C \in \{B, G, R\}$$ 5. 计算色偏比率: $$\text{cast\_ratio} = \frac{\max(d_B, d_G, d_R)}{\bar{\mu}}$$ 6. 若 $\text{cast\_ratio} > \text{threshold}$,触发检测,并记录偏差最大的通道为主色调。 #### 严重程度 | 色偏比率 | 级别 | |---------|------| | $> 3 \times \text{threshold}$ | HIGH | | $> 2 \times \text{threshold}$ | MEDIUM | | 其他 | LOW | --- ### 4. 清晰度检测 (Sharpness) **类型**:空间检测 #### 图像原理 图像清晰度对应边缘的锐利程度。拉普拉斯算子(Laplacian)是二阶微分算子,对图像中的边缘和高频细节高度敏感:清晰图像经过拉普拉斯滤波后响应能量大(方差高),模糊图像响应能量小(方差低)。因此,**拉普拉斯响应的方差**是评估图像清晰度的经典指标。 **拉普拉斯核**(离散近似): $$L = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix}$$ #### 算法步骤 1. 将 BGR 帧转换为灰度图。 2. 应用拉普拉斯滤波:`cv::Laplacian(gray, lap, CV_64F)` 3. 计算滤波结果的均值 $\mu_L$ 和标准差 $\sigma_L$。 4. **清晰度 = 拉普拉斯方差**: $$\text{sharpness} = \sigma_L^2$$ 5. 若 $\text{sharpness} < \text{threshold}$,触发检测。 #### 严重程度 | 比率 $r = \frac{\text{sharpness}}{\text{threshold}}$ | 级别 | |------|------| | $r < 0.2$ | HIGH | | $r < 0.5$ | MEDIUM | | 其他 | LOW | --- ### 5. 噪声检测 (Noise) **类型**:空间检测 #### 图像原理 视频噪声通常表现为像素亮度的随机抖动(椒盐噪声、高斯噪声等)。高斯模糊可滤除高频噪声,因此原图与模糊图之差即为**高频残差**,其中主要包含噪声成分。MAD(中位绝对偏差,Median Absolute Deviation)相比标准差对异常值更鲁棒,适合噪声的稳健估计。 #### 算法步骤 1. 将帧转换为灰度图 $I$。 2. 对灰度图施加 3×3 高斯模糊:$I_s = \text{GaussianBlur}(I, 3 \times 3)$ 3. 计算高频残差:$D = |I - I_s|$ 4. 将 $D$ 所有像素展开为一维数组并排序,计算中值 $\text{med}$: $$\text{med} = \text{median}(\{D_i\})$$ 5. 对每个像素计算绝对偏差并排序,取中值得到 MAD: $$\text{MAD} = \text{median}(\{|D_i - \text{med}|\})$$ 6. 若 $\text{MAD} > \text{threshold}$,触发检测。 #### 严重程度 | 比率 $r = \frac{\text{MAD}}{\text{threshold}}$ | 级别 | |------|------| | $r > 3.0$ | HIGH | | $r > 2.0$ | MEDIUM | | 其他 | LOW | --- ### 6. 条纹检测 (Streak) **类型**:空间检测 #### 图像原理 条纹(Streak)是指画面中出现的规则性横向或纵向线条,通常由传感器行/列损坏、信号传输干扰或解码错误引起。条纹的特征是**相邻行(或列)之间的梯度差呈强相关性**——正常图像的行间梯度差是随机的,而条纹图像的行间梯度差在空间上高度重复。**滞后-1 自相关系数**(Lag-1 Autocorrelation)正是衡量序列"前后相关"程度的统计量: $$\rho_1 = \frac{\sum_{i=2}^{N}(x_i - \bar{x})(x_{i-1} - \bar{x})}{\sum_{i=1}^{N}(x_i - \bar{x})^2}$$ #### 算法步骤 1. 将帧转换为灰度图。 2. **水平梯度分析**: - 对每一列 $j$,计算该列与前一列的逐行差值的均值,得到列差序列 $\{h_j\}$。 - 计算 $\{h_j\}$ 的滞后-1 自相关系数 $\rho_h$。 3. **垂直梯度分析**: - 对每一行 $i$,计算该行与前一行的逐列差值的均值,得到行差序列 $\{v_i\}$。 - 计算 $\{v_i\}$ 的滞后-1 自相关系数 $\rho_v$。 4. 取最大绝对值:$\text{max\_autocorr} = \max(|\rho_h|, |\rho_v|)$ 5. 若 $\text{max\_autocorr} > \text{threshold}$,触发检测;$|\rho_h| > |\rho_v|$ 时判定为垂直条纹,否则为水平条纹。 #### 严重程度 | 自相关值 | 级别 | |---------|------| | $> 0.95$ | HIGH | | $> 0.85$ | MEDIUM | | 其他 | LOW | --- ### 7. 遮挡检测 (Occlusion) **类型**:空间检测 #### 图像原理 遮挡(Occlusion)指摄像头镜头或画面被物体遮盖,表现为大面积纯色区域(黑屏、白屏、纯色贴片等)。其共同特征是**边缘密度极低**——正常画面包含丰富的轮廓和纹理,遮挡画面则几乎没有边缘。Canny 算法是经典的边缘检测方法,通过双阈值滞后处理可稳健地提取真实边缘。在确认边缘稀疏后,进一步用 HSV 色彩空间分析遮挡类型。 #### 算法步骤 1. 将帧转换为灰度图。 2. 运行 Canny 边缘检测(低阈值 50,高阈值 150):`cv::Canny(gray, edges, 50, 150)` 3. 计算边缘密度: $$\text{edge\_ratio} = \frac{\text{countNonZero(edges)}}{W \times H}$$ 4. 若 $\text{edge\_ratio} \geq \text{threshold}$,无遮挡,返回。 5. 将帧转换为 HSV 色彩空间,计算均值(S 分量归一化到 0–1,V 分量归一化到 0–1)。 6. 根据 HSV 均值判断遮挡类型: | 条件 | 类型 | |------|------| | $V < 0.1$ 且 $S < 0.15$ | 黑屏遮挡 | | $V > 0.9$ 且 $S < 0.15$ | 白屏遮挡 | | $S > 0.7$ 且 $\text{edge\_ratio} < 0.5 \times \text{threshold}$ | 纯色遮挡 | | $\text{edge\_ratio} < 0.3 \times \text{threshold}$ | 均匀遮挡 | #### 严重程度 | 边缘密度比率 | 级别 | |---------|------| | $< 0.1 \times \text{threshold}$ | HIGH | | $< 0.5 \times \text{threshold}$ | MEDIUM | | 其他 | LOW | --- ### 8. 冻帧检测 (Freeze) **类型**:时序检测 #### 图像原理 冻帧(Freeze)指视频画面静止不动,通常由网络丢包、编码器故障或服务端卡顿引起。连续帧之间若内容相同,其像素差的均方误差(MSE)应趋近于零。单帧 MSE 过低不足为凭(正常静止场景也可能出现),需要**持续多帧**保持低 MSE 才能判定为冻帧。 **MSE 计算**: $$\text{MSE} = \frac{1}{N_c \cdot W \cdot H} \sum_{c}\sum_{i,j}(I_{\text{cur}}^c(i,j) - I_{\text{prev}}^c(i,j))^2$$ #### 算法步骤 1. 需要帧缓冲中至少有 2 帧。 2. 计算当前帧与上一帧的 MSE。 3. 若 $\text{MSE} < \text{threshold}$:累加连续低 MSE 帧计数器 $n$。 4. 若 $\text{MSE} \geq \text{threshold}$:重置 $n = 0$,不触发。 5. 设需要的持续帧数 $W = \max(\text{window\_size}, 15)$,若 $n \geq W$,触发检测。 #### 严重程度 | 连续帧数 $n$ | 级别 | |---------|------| | $n > 3W$ | HIGH | | $n > 2W$ | MEDIUM | | 其他 | LOW | **状态管理**:`consecutive_low_mse_` 计数器在检测器实例生命周期内持久保存,跨帧累计。 --- ### 9. 抖动检测 (Jitter) **类型**:时序检测 #### 图像原理 视频抖动(Jitter)指画面发生持续的小幅位移,可能来自摄像头机械振动、PTZ 云台不稳或传输延迟抖动。**相位相关法(Phase Correlation)**是频域帧间配准的经典算法:利用傅里叶变换的平移定理,两帧之间的平移量对应互功率谱的相位差,通过逆变换后的峰值位置即可精确估计位移量,计算效率高且不受亮度变化影响。 **相位相关原理**: 若 $f_2(x,y) = f_1(x-\Delta x, y-\Delta y)$,则: $$\frac{F_1(u,v) \cdot \overline{F_2(u,v)}}{|F_1(u,v) \cdot \overline{F_2(u,v)}|} = e^{j2\pi(u\Delta x + v\Delta y)}$$ 逆变换后在 $(\Delta x, \Delta y)$ 处出现冲激峰,即位移量。 #### 算法步骤 1. 需要帧缓冲中至少有 2 帧。 2. **预筛选**:计算帧间 MSE,若 $\text{MSE} > 50.0$(场景切换等剧烈变化),跳过并清空历史;抖动通常为小幅位移,大差异帧不适合相位相关。 3. 将当前帧和上一帧转为灰度浮点图(CV_32FC1)。 4. 调用 `cv::phaseCorrelate()` 得到亚像素位移向量 $(\Delta x, \Delta y)$。 5. 计算位移幅度:$d = \sqrt{\Delta x^2 + \Delta y^2}$;若 $d > 50$ 像素则视为噪声丢弃。 6. 将 $d$ 加入长度为 $W = \max(\text{window\_size}, 30)$ 的滑动窗口历史。 7. 至少积累 3 个样本后进行稳健统计: - 排序后取**中值位移** $\tilde{d}$ - 计算 MAD:$\text{MAD} = \text{median}(\{|d_i - \tilde{d}|\})$ 8. 若 $\tilde{d} > \text{threshold}$ 且 $\text{MAD} \leq 0.8 \times \tilde{d}$(稳定持续抖动,非随机跳变),触发检测。 #### 严重程度 | 中值位移 $\tilde{d}$ | 级别 | |---------|------| | $> 4 \times \text{threshold}$ | HIGH | | $> 2 \times \text{threshold}$ | MEDIUM | | 其他 | LOW | **状态管理**:`shift_history_` 滑动窗口跨帧保存,场景切换或大位移时自动重置。 --- ### 10. 场景切换检测 (Scene Change) **类型**:时序检测 #### 图像原理 场景切换(Scene Change)指相邻帧之间画面内容发生突变(硬切)或渐变(淡入淡出、划变)。颜色直方图是描述图像整体色彩分布的全局特征,对平移、旋转等几何变化不敏感,适合场景级比较。**HSV 色彩空间**将色调(H)与亮度(V)分离,使用 H-S 二维联合直方图可同时捕获色相和饱和度的分布变化,对光照强度变化具有一定鲁棒性。**卡方距离(Chi-Squared Distance)**是比较直方图的常用度量: $$\chi^2 = \sum_{i}\frac{(H_1(i) - H_2(i))^2}{H_1(i) + H_2(i) + \varepsilon}$$ #### 算法步骤 1. 需要帧缓冲中至少有 2 帧。 2. 将当前帧和上一帧均转换为 HSV 色彩空间。 3. 分别计算二维 H-S 联合直方图: - H 通道:30 个区间,范围 [0, 180] - S 通道:32 个区间,范围 [0, 256] 4. 对两个直方图分别归一化。 5. 计算卡方距离:`cv::compareHist(hist1, hist2, HISTCMP_CHISQR)` 6. 若 $\chi^2 > \text{threshold}$,触发检测: - $\chi^2 > 3 \times \text{threshold}$:判定为硬切(Hard cut) - 否则:判定为渐变转场(Gradual transition) #### 严重程度 | 卡方距离 | 级别 | |---------|------| | $> 5 \times \text{threshold}$ | HIGH | | $> 2 \times \text{threshold}$ | MEDIUM | | 其他 | LOW | --- ## 严重程度分级 所有检测器共享三级严重程度: | 级别 | 常量 | 含义 | |------|------|------| | 低 | `VCHECK_SEVERITY_LOW` | 轻微异常,可能为噪声 | | 中 | `VCHECK_SEVERITY_MEDIUM` | 明显异常,建议关注 | | 高 | `VCHECK_SEVERITY_HIGH` | 严重异常,需立即处理 | 每个检测器根据偏离阈值的程度,用比率或绝对值映射到对应级别,具体规则见各节。 --- ## 架构概览 ``` VideoInput (FFmpeg) │ readFrame() → BGR24 ▼ FrameBuffer (std::deque) │ push() / at(-1) 访问前帧 ▼ DetectorPipeline ├─ DetectorBrightness ├─ DetectorContrast ├─ DetectorColorCast ├─ DetectorSharpness ├─ DetectorNoise ├─ DetectorStreak ├─ DetectorOcclusion ├─ DetectorFreeze ← 时序,维护帧间计数器 ├─ DetectorJitter ← 时序,维护位移历史窗口 └─ DetectorSceneChange ← 时序,比较相邻帧直方图 │ ▼ vcheck_result_t(C 结构体,无堆分配) │ ▼ callback / vcheck_stream_read_frame() ``` **线程模型**:每个流一个独立的解码+分析线程,通过 `atomic` 停止,`mutex`+`condition_variable` 暂停/恢复。流之间无共享可变状态。回调函数中禁止调用 `vcheck_stream_stop()`(会导致死锁)。 --- ## 许可证 请参见仓库根目录下的 LICENSE 文件。