# 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
[](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 文件。