影像增強演算法總結

walletiger發表於2020-12-15

需要影像增強的原因:

  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 ! 

 

 

 

 

 

相關文章