需要图像增强的原因:
1 图像噪点过大,影响感观、影响计算机对图像特征的提取
2 图像因为光线环境等造成整体对比度不足或局部过暗、过曝。细节损失
3 图像白平衡系数未校准造成图像偏色
4 图像因采集时镜头失焦等问题造成的模糊
5 图像由于运动速度过快 (采集一帧时间内发生了剧烈运动),形成运动模糊
6 图像因为 sensor 感光等问题造成 色彩饱和度不足
7 图像分辨率太低,放大后的细节缺失
下面逐一介绍
一 噪声抑制
形成噪声的原因很多,比如相机拍摄时曝光不够(夜间拍摄),或信号传输收到了干扰(如老的无线电视台信号),最终形成画面很多失真的黑白或彩色杂点。 按噪声的分布分高斯噪声, 椒盐(随机的白噪声)等。 按噪声对画质的影响分为加性噪声,或乘性噪声等。 现实远远比我举例的复杂 。。
光线不足相机采集的噪声
老的无线电视机信号噪声
老胶卷电影中的噪点
为更好的处理噪声,我们先来人为的增加图像噪声
1.1 增加椒盐噪声
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI (3.141592653)
int HG_AddSaltNoise(unsigned char *plane, int w, int h, int pitch , double pcnt)
{
double x;
uint8_t *p_pix;
int skip;
int col , row;
srandom(time(NULL));
p_pix = plane;
skip = pitch - w;
for(row = 0 ; row < h ; ++row , p_pix += skip){
for(col = 0 ; col < w ; ++col , ++p_pix){
x = random() * 1.0f / RAND_MAX;
if (x < pcnt / 2)
*p_pix = 0;
else if (x > (1 - pcnt / 2))
*p_pix = 255;
}
}
return 0;
}
1.2 增加高斯噪声
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI (3.141592653)
int HG_AddGuassianNoise(unsigned char *plane, int w, int h, int pitch , uint8_t u , double delta)
{
/*
Box-Muller transform to generate guassian distribution
guassian(0 , 1):
x1 = random(0 ~ 1)
x2 = random(0 ~ 1)
______ __
z1 = \/-2lnx1 * cos(2 * || * x2)
______ __
z2 = \/-2lnx1 * sin(2 * || * x2)
guassian(u ,delta) :
______ __
z1 = \/-2lnx1 * cos(2 * || * x2) * delta + u
______ __
z2 = \/-2lnx1 * sin(2 * || * x2) * delta + v
*/
double x1 , x2;
uint8_t *p_pix;
int skip;
int col , row;
int16_t val;
srandom(time(NULL));
p_pix = plane;
skip = pitch - w;
for(row = 0 ; row < h ; ++row , p_pix += skip){
for(col = 0 ; col < w ; ++col , ++p_pix){
if (col % 2 == 0) {
x1 = random() * 1.0f / RAND_MAX;
x2 = random() * 1.0f / RAND_MAX;
val = sqrt(-2 * log(x1)) * cos( 2 * PI * x2) * delta + u;
}else {
x1 = random() * 1.0f / RAND_MAX;
x2 = random() * 1.0f / RAND_MAX;
val = sqrt(-2 * log(x1)) * sin( 2 * PI * x2) * delta + u;
}
val += *p_pix;
if (val < 0)
val = 0;
if (val > 255)
val = 255;
*p_pix = val;
}
}
return 0;
}
1.3 传统图像噪声去除方法
简单介绍两种滤波器算法 " 高斯滤波" 和 "中值滤波 " , 都是在图像空域 利用中心点周围某半径内图像的特征替换原图像的值 。 如同样 5x5 的滤波器直径:
1.3.1 高斯滤波
5x5高斯滤波核 (归一化分母 273)
A 根据标准二维正态分布的概率我们先计算出 5x5 高斯滤波核 (略) 如上图:
B 把图像每个像素点与高斯核中心点重合
C 原图像与高斯核重合的两个5x5 矩阵对应元素相乘求和,再除以 归一化分母 273 得到的值 替换原图像位置的值
注:空域滤波算法基本都是这个思路, 用一个卷积核 跟原图像中心邻域位置对应点 卷积运算求和
对于边界点可以把原图像扩展半径 r 个像素再做计算 (5x5 滤波器半径为2)
效果上 高斯滤波的加权平均对图像起到模糊作用,科学地平均了噪点。实际图像的像素值除了在边缘区域,大部分是平坦的,渐变的,不会像噪声一样突变。 很多机器学习算法对图像的预处理都有先对图像高斯模糊一下,再输入到神经网或分类器中训练。利用高斯滤波能滤掉噪声突变的特性减少对图像特征提取的干扰。
高斯滤波器的科学性 可以看下 DOG (difference of guassian)算法的描述, 不同大小核的高斯卷积运算 可以近似表达人的视觉神经对物体由远及近不同尺度的视觉感受。
高斯滤波的实现及效果不再阐述(网上很多).
1.3.2 中值 滤波
中值滤波采用中心点周围像素的中值来替换原位置像素, 是一种非线性的处理方法。同样利用了图像大部分区域比较平坦的特征。 比较取巧的避开了突变的噪声。 对于随机的椒盐噪声处理效果比高斯滤波好。当然也有改进的自适应中值滤波器
网上实现也很多这里不在介绍 。
这两种算法都平均了噪声但没有很好的保留图像细节。
1.3.3 opencv 里 高斯滤波和中值滤波
import cv2
img=cv2.imread('/home/hugo/dark.png')
cv2.imshow('orig', img)
blur=cv2.GaussianBlur(img,(5,5),0)
cv2.imshow('GaussianBlur',blur)
blur2=cv2.medianBlur(img,5)
cv2.imshow('mediumBlur', blur2)
#blur3 = cv2.bilateralFilter(src=img, d=0, sigmaColor=40, sigmaSpace=10)
#cv2.imshow('bilateralFilter', blur3)
cv2.waitKey(0)
cv2.destroyAllWindows()
可以看到对于随机噪声较多的马路,中值滤波效果较好。 但中值滤波后形状纹理细节失真是比高斯滤波较严重的。
1.4 保留细节的降噪算法 - 双边滤波
引用网友的一张图片:
双边滤波由高斯滤波器演化而来, 修正高斯滤波器的权值 , 当邻域点与中心点像素值 较近 则在距离权值 上叠加较高的乘法权重,否则叠加较低的乘法权重。(实现略 )
后来发现这种保边滤波器可以用来美颜磨皮,去脂肪粒,去斑 。。
opencv 里双边滤波:
blur3 = cv2.bilateralFilter(src=img, d=0, sigmaColor=40, sigmaSpace=10)
1.5 保留细节的降噪算法 - 引导滤波
可以看下网友公式的推导。
https://blog.csdn.net/weixin_43194305/article/details/88959183
guided filter 有很多用处, 使用图像自身作为引导图像可以很好的反映当前点在附近点梯度方向的贡献。近似还原图像的真实性。
另外可以用作暗通道去雾 、视频超采样等
以下是我实现的引导滤波降噪算法:
引导滤波在 i7-3632QM CPU @ 2.20GHz 可以到 720p@150fps
#pragma once
/*
引导滤波降噪算法,原理来源于何凯明的 <Guided Image FIlter> , http://kaiminghe.com/eccv10/
引导滤波是一种局部线程滤波模型,用于降噪时功能与双边滤波类似(保边降噪),效果要比双波滤波略好。
理论速度不受滤边窗口大小影响,大窗口时速度远远快于双边滤波。
本算法实现针对降噪场景做了优化,小sensor 滤波半径(r <= 15) 在 Intel(R) Core(TM) i7-3632QM CPU @ 2.20GHz 主机系统ubuntu16.04 64bit,
处理 720p yuv 一帧数据 单线程平均 6~7ms, cv::ximgproc::guidedFilter() 需要90ms.
实时视频yuv处理时,仅处理y通道。
假定输出图像Q与与引导图像I满足局部线性模型,
Q(i) = I(i) * a + b
基于Q与输入P图像最小误差求得得到原作者公式:
A = Cov(I, P) / (Delta(I, r) + lamda)
B = Mean(P, r) - A .* Mean(I, r)
Q = Mean(A, r) .* I + Mean(B, r)
用于图像平滑的引导图像I == 输入图像P,得到如下简化公式:
Q = Mean(A, r) .* I + Mean(B, r)
A = Delta(I, r) / (Delta(I, r) + lamda)
B = Mean(I, r) - A .* Mean(I, r)
其中:
(1) Q 为输出图像(二维矩阵)
(2) Mean(A, r) 表示二维矩阵 A 以r 为半径的均值矩阵
(3) .* 每个对应元素依次相乘
(4) lamda 平滑因子,越大越平滑(lamda越大平坦区域 A 越小, B 越接近均值, Q 越接近B即均值)
算法包含两种实现:
算法1 先计算 Mean(I), Delta(I), Mean(A), Mean(B),再算出Q
算法2 Mean(I), Delta(I), Mean(A), Mean(B) 计算过程做了合并,省去了四个矩阵的转存。仅开辟 (2*r+1) * width
的 A、B ring buffer, 内存空间开销小,速度比较稳定
算法速度:
64bit windows , 64bit linux 要快于 32bit 平台 ,前者720p 一帧 i7约 6ms, 后者约 8ms
大半径(r>15)用了64位乘法,某些32位平台速度会有减慢
*/
#define HG_GUIDED_DENOISE_FILTER_R_MAX (127)
#define HG_GUIDED_DENOISE_FILTER_IMG_W_MAX (4096)
typedef void * HG_FILTER_HANDLE;
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief hg_guided_denoise_filter_create
* @param w
* @param h
* @param r
* @param lamda
* @return
*/
HG_FILTER_HANDLE hg_guided_denoise_filter_create(int w , int h , int r , float lamda /*0 ~ 1*/);/*r 最大15*/
/**
* @brief hg_guided_denoise_filter_process
* @param h
* @param pIP
* @param pitch_ip
* @param pQ
* @param pitch_q
* @return
*/
int hg_guided_denoise_filter_process(HG_FILTER_HANDLE h , unsigned char * pIP, int pitch_ip, unsigned char *pQ, int pitch_q);
/**
* @brief hg_guided_denoise_filter_destroy
* @param h
*/
void hg_guided_denoise_filter_destroy(HG_FILTER_HANDLE h);
/**
* @brief hg_guided_denoise_filter2_create
* @param w
* @param h
* @param r
* @param lamda
* @return
*/
HG_FILTER_HANDLE hg_guided_denoise_filter2_create(int w , int h , int r , float lamda);/*r 最大HG_GUIDED_DENOISE_FILTER_R_MAX=127*/
/**
* @brief hg_guided_denoise_filter2_process
* @param h
* @param pIP input buf
* @param pitch_ip
* @param pQ output buf, pQ can't be the same buffer with pIP
* @param pitch_q
* @return
*/
int hg_guided_denoise_filter2_process(HG_FILTER_HANDLE h , unsigned char * pIP, int pitch_ip, unsigned char *pQ, int pitch_q);
/**
* @brief hg_guided_denoise_filter2_destroy
* @param h
*/
void hg_guided_denoise_filter2_destroy(HG_FILTER_HANDLE h);
#ifdef __cplusplus
}
#endif
https://github.com/walletiger/hg_img_denoise/blob/main/src/hg_guided_denoise.c
1.6 保留细节的降噪算法 - 局部均方差滤波算法 (速度较快)
比较简单快速的 保边滤波算法, 效果跟 guided filter 差不多。
算法原理:
https://blog.csdn.net/lz0499/article/details/77148182
以下是我的实现:
算法做了一定优化。 局部均方差滤波 在 i7-3632QM CPU @ 2.20GHz 上可以 到 720p@200fps . 在 hi3798 的 arm neno 优化版本可以到 720p@60fps
https://github.com/walletiger/hg_img_denoise/blob/main/src/hg_local_variance_denoise.c
降噪算法还有很多,BM3D 据说是效果最好的,值得研究。
1.7 视频降噪算法
以上算法在静态图片处理上有着不错的表现,但实时视频里 噪声不光是 空域的,时域上也存在,比如实际摄像头采集视频时相邻帧的噪声分布存在不同的分布,
2D 的降噪算法无法很好解决时域噪声,并且逐帧的2D降噪处理效果可能会出现闪烁。
实时视频场景里问题会相对复杂一些, 相邻帧除了噪声分布存在随机性, 物体的运动也对滤波造成难度。
推荐一个可以实时运行的开源 3D 图像降噪算法:
hqdn3d (high quality denoise 3-dimensional)
https://github.com/walletiger/hg_img_denoise/blob/main/3rd/hqdn3d.h
使用 hqdn3d 来处理视频 yuv 图像:
int level0 = 4, level1 = 8, level2 = 8, level3 = 8;
if (!m_h_denoise)
m_h_denoise = hqdn3d_create(pYuvDataOut.width, pYuvDataOut.height, level0, level1, level2, level3);
hqdn3d_process_Y(m_h_denoise, pYuvDataOut.plane[0], pYuvDataOut.pitch[0], m_YUVDenoise.plane[0], m_YUVDenoise.pitch[0]);
hqdn3d_process_U(m_h_denoise, pYuvDataOut.plane[1], pYuvDataOut.pitch[1], m_YUVDenoise.plane[1], m_YUVDenoise.pitch[1]);
hqdn3d_process_V(m_h_denoise, pYuvDataOut.plane[2], pYuvDataOut.pitch[2], m_YUVDenoise.plane[2], m_YUVDenoise.pitch[2]);
二 对比度
2.1 整体对比度调整
对比度高的图像 颜色分布接近0~ 255 的整体范围, 看起来比较透亮。对比度低的图像颜色通道的像素值可能之分布在比较窄的区域,如 60 ~ 100 , 图像细节感较差, 不通透。如阴天或雾霾天的感觉。
阴天照片通过自动色阶调整
对比度调整有多种方法, 如自动 色阶(对比度) , 直方图均衡化等
YUV 图像可以只处理 Y (亮度)通道,因为人眼对亮度感受更明显。 RGB 图像可以三个通道分别处理。
自动色阶原理 是先 统计图像通道里最暗或最亮的像素值, 可以 cut 掉最高或对低的某百分比的值, cut 之后的最大最小 线性拉伸到 0 ~ 255 (yuv 建议 16~235). 当然可以不线性拉伸, 可以通过 gamma 调整 选择提升暗处对比度还是亮处对比度。 建立一个 pixel map , 在由原图像 通过 pixel map 算出新的像素值。
自动色阶比较简单, 算法细节不在介绍, 代码实现可以看这里:
https://github.com/walletiger/hg_img_denoise/blob/main/include/hg_clahe_priv.h
PixMap_AutoLevel 实现
2.1.2 直方图均衡化
同样是拉高对比度的思路, 只不过 不是由最小值,最大值拉到 0~ 255 ,而是由 到某个像素值v 的 累积概率密度 Psv * 255 作为 pixel map 的映射值。
如 图像通道里到 像素值 128 的 累积概率密度 ( 0 ~ 128 像素值占总像素值的比例) 的 30% , pixmap[128] = 255 * 0.3
直方图用来统计每个像素值的概率密度。
算法也比较简单 , 实现可以看:
https://github.com/walletiger/hg_img_denoise/blob/main/include/hg_clahe_priv.h
MapPixelHistEq 的函数实现
2.2 自适应 自方图均衡化 CLAHE
有的图像整体对比度较高, 但 存在局部区域 过曝 或过暗的情况, 其实这些区域的 图像还是有很多细节,只是人眼感受不到。
CLAHE 因此而生, 可以把图像分成很多局部的块,每个块使用 HistEQ 或 autolevel , 然后在交界处做个 pixmap 的平滑处理。
场景比如 晚上在办公室, 手机摄像头对着天花板, 电灯处 图像容易过曝, 电灯附近图像容易过暗(没有光线照射)。。
引用网友的一副航天图像 CLAHE (右处理效果):
代码实现:
https://github.com/walletiger/hg_img_denoise/blob/main/include/hg_clahe.h
https://github.com/walletiger/hg_img_denoise/blob/main/src/hg_clahe_gray8.c
https://github.com/walletiger/hg_img_denoise/blob/main/src/hg_clahe_i420.c
2.3 去雾算法
2.2 介绍的对比度增强算法可以很好的起到去雾效果。但最著名的算法还是 何凯明的暗通道 算法。
以下是我的对比结果,可以看到暗通道先验可以把雾天局部每一处细节处理的很好。 (代码等整理出来可独立编译版本再发)
暗通道假定 大气光气存在一个先验的亮度 A , 图像真实亮度 L + A = 采集后的亮度 Y , 通过 guided filter 可以计算出 先验 A, 从而还原真实图像 L。
当然还原后 图像亮度会发暗,需要进一步处理。这篇文章讲的很细致,推荐看下。
https://www.cnblogs.com/Imageshop/p/3281703.html
三 白平衡
相机的感光器件(sensor) 在不同的光线环境下 感受的色温与人眼是有差异的, 如室内环境和户外 如果 sensor ISP 使用相同的颜色调整参数必然会有一个场景颜色与人眼观察结果偏色。 所谓的白平衡其实是要调节 R, G, B 三原色的 偏差系数。 以满足不同场景下 “正常”的采集颜色校正结果。 好在现在的相机都有自动白平衡功能。 用户不用关心。以前见过过某个镜头模组厂家对每一批次的镜头都要在灯箱环境(各种环境光)校正白平衡参数,再发货到他们的用户使用。
四 图像失焦 & 运动模糊
4.1 边缘增强
轻微的失焦可以通过 laplace 滤波 起到边缘增强作用,引用为网友的一附图所示:
4.2 去卷积,盲去卷积
除了解决失焦问题,还可以有效的去除运动模糊
:) 还是引用网友的照片为例:
以下博文对 去卷积,盲去卷积有很好的介绍。
https://yongqi.blog.csdn.net/article/details/107420886
五 色彩饱和度
色彩饱和度 体现为色彩的纯度, 纯度越高,画面越鲜亮,越杂,画面越黯淡。
通常调整色彩饱和度会转换到 HSV 域。 算法比较成熟, 以下算法摘自开源工程 VLC , 可以实现实时效果。
https://github.com/walletiger/hg_img_denoise/blob/main/3rd/video_adjust.h
六 超分辨率
超分辨率算法实现小图像放大后重建出具备更高分辨率清晰度的算法。 如下效果图:
已经发展出 RAISR SRCNN, FSRCNN, ESPCN、引导滤波等各种算法。
引用网友的文章做个介绍:
https://blog.csdn.net/sinat_39372048/article/details/81628945
https://zhuanlan.zhihu.com/p/30883940
https://blog.csdn.net/walletiger/article/details/109740040
使用 opencv 调用 fsrcnn, edsr, espcn 算法:
https://zhuanlan.zhihu.com/p/306634888
经实测 espcn 算法 2.2G 320x180 放大两被 cpu 单核 可以到 80fps !