4. 图像清晰度评测:拉普拉斯方差、Brenner梯度、Tenengrad梯度、SMD(灰度方差)等算法原理与实现
图像清晰度评测,说白了就是判断一张图是「清楚」还是「模糊」。
我做了这么多年图像质量评测,发现很多同学一上来就上深度学习模型,其实没必要。传统方法在工业场景下又快又准,尤其是对焦、缺陷检测这些任务,传统梯度算法依然是首选。
今天我们就来聊聊四种最经典的清晰度评价算法:拉普拉斯方差、Brenner梯度、Tenengrad梯度、SMD(灰度方差)。
核心思想:清晰图像边缘锐利,像素间变化剧烈;模糊图像边缘平滑,像素间变化平缓。所有算法都在量化这个「变化程度」。
4.1 拉普拉斯方差(Laplacian Variance)
拉普拉斯算子是一个二阶微分算子。它不关心图像的整体亮度,只关注像素值的变化率。
为什么用二阶?因为一阶微分只能检测到边缘的存在,而二阶微分能区分「边缘的强度」。清晰图像的边缘更「陡峭」,二阶响应更大。
我的经验:拉普拉斯方差是我在工业检测项目中最常用的方法。它简单、稳定,对大多数场景都有效。但要注意——它对噪声非常敏感。我曾经在一个低光照场景下用它做自动对焦,结果噪声被放大成了「伪边缘」,导致对焦失败。
算法步骤:
- 将图像转为灰度图
- 用拉普拉斯核(通常是3x3)做卷积
- 计算卷积结果的方差
拉普拉斯核:
| 0 | 1 | 0 |
| 1 | -4 | 1 |
| 0 | 1 | 0 |
import cv2
import numpy as np
def laplacian_variance(image):
"""
拉普拉斯方差清晰度评价
值越大,图像越清晰
"""
# 转灰度
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 拉普拉斯卷积
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
# 计算方差
variance = laplacian.var()
return variance
# 使用示例
img = cv2.imread('test.jpg')
score = laplacian_variance(img)
print(f'清晰度评分: {score:.2f}')
避坑指南:我曾经在纹理非常少的图像(比如纯色背景)上使用拉普拉斯方差,结果方差接近0,误判为模糊。后来我加了一个预处理步骤——先做直方图均衡化,增强对比度,效果好了很多。
4.2 Brenner梯度
Brenner梯度是最简单的清晰度评价方法之一。它只计算水平方向上相隔两个像素的灰度差平方。
你想想看,为什么是相隔两个像素?因为相邻像素的差异可能太小,相隔两个像素能放大边缘信号,同时抑制一些随机噪声。
def brenner_gradient(image):
"""
Brenner梯度清晰度评价
计算水平方向相隔2像素的灰度差平方和
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = gray.astype(np.float32)
# 水平方向相隔2像素的差分
diff = gray[:, 2:] - gray[:, :-2]
# 平方和
score = np.sum(diff ** 2)
return score
特点:
- 计算量极小,适合嵌入式设备
- 只考虑水平方向,对垂直纹理不敏感
- 抗噪性一般
我的习惯:在做实时对焦系统时,我通常先用Brenner梯度做粗调,再用拉普拉斯方差做精调。这样既保证了速度,又保证了精度。
4.3 Tenengrad梯度
Tenengrad梯度使用Sobel算子计算水平和垂直方向的梯度。它比Brenner更全面,因为考虑了双方向。
说白了,Sobel算子就是带权重的差分。它给中心像素更高的权重,边缘像素更低的权重,这样能更好地抑制噪声。
def tenengrad_gradient(image):
"""
Tenengrad梯度清晰度评价
使用Sobel算子计算梯度幅值平方和
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = gray.astype(np.float32)
# Sobel x和y方向
gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# 梯度幅值
mag = np.sqrt(gx ** 2 + gy ** 2)
# 平方和作为清晰度评分
score = np.sum(mag ** 2)
return score
| 算法 | 计算量 | 抗噪性 | 适用场景 |
|---|---|---|---|
| 拉普拉斯方差 | 中 | 弱 | 纹理丰富场景 |
| Brenner梯度 | 小 | 中 | 快速对焦 |
| Tenengrad梯度 | 大 | 强 | 通用场景 |
| SMD灰度方差 | 小 | 弱 | 简单场景 |
重要提示:Tenengrad梯度虽然效果好,但计算量大。我在一个4K视频的实时处理项目中,不得不把它降采样到720p再计算,否则帧率上不去。
4.4 SMD(灰度方差)
SMD全称是Sum of Modulus of Difference,也就是灰度差绝对值之和。它计算每个像素与相邻像素的灰度差绝对值,然后求和。
为什么用绝对值而不是平方?因为平方会放大大的差异,而绝对值对所有差异一视同仁。这在某些场景下更合理。
def smd_gradient(image):
"""
SMD灰度方差清晰度评价
计算相邻像素灰度差绝对值之和
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = gray.astype(np.float32)
# 水平方向差分绝对值
diff_h = np.abs(gray[:, 1:] - gray[:, :-1])
# 垂直方向差分绝对值
diff_v = np.abs(gray[1:, :] - gray[:-1, :])
# 求和
score = np.sum(diff_h) + np.sum(diff_v)
return score
避坑指南:我曾经在一个项目中用SMD做清晰度评价,结果发现评分总是偏高。排查了半天,发现是图像有轻微的条纹噪声,SMD把噪声也当成了「清晰度」。后来我加了一个中值滤波预处理,问题就解决了。
4.5 四种算法对比总结
嗯,到这里四种算法都讲完了。我做个简单的总结:
- 追求速度:选Brenner或SMD,计算量小,适合嵌入式
- 追求精度:选Tenengrad,抗噪性好,结果稳定
- 追求平衡:选拉普拉斯方差,简单实用,但要注意噪声
- 特殊场景:纹理极少的图像,先做预处理再评价
我记得有一次,一个同事问我:「为什么我用拉普拉斯方差评价一张模糊图,得分反而比清晰图高?」我一看,原来那张「模糊图」其实是运动模糊,但图像本身纹理非常丰富,拉普拉斯方差对纹理敏感,反而给出了高分。所以,没有万能的算法,理解算法的原理和局限,才能用好它。
我的建议:在实际项目中,不要只用一种算法。我会同时计算2-3种指标,然后综合判断。比如拉普拉斯方差+Brenner+Tenengrad,三个指标都高,那基本可以确定图像是清晰的。
公众号:蓝海资料掘金营,微信deep3321