一、前言
上一篇我們講解了OpenCV圖像模糊中的線性濾波。本篇主要向大家介紹下非線性濾波。按慣例,先來看下效果圖。


二、線性與非線性
上一篇中使用“卷積算子計(jì)算都是線性操作,所以又叫線性濾波”簡(jiǎn)單描述了線性濾波概念。下面我們?cè)敿?xì)了解下線性濾波與非線性濾波。
數(shù)學(xué)角度
數(shù)學(xué)里,一般說的線性,是說的線性映射:
線性 = 齊次性 + 可加性
齊次性: f(ax)=af(x)
可加性: f(x+y)=f(x)+f(y)
非線性就是這兩條至少之一不成立.
圖像角度
線性濾波:兩個(gè)信號(hào)之和的響應(yīng)和他們各自響應(yīng)之和相等(可加性)。換句話說,每個(gè)像素的輸出值是一些輸入像素的加權(quán)和。
非線性濾波:原始數(shù)據(jù)與濾波結(jié)果是一種邏輯關(guān)系,即通過比較一定鄰域內(nèi)的灰度值大小來實(shí)現(xiàn)的。
線性濾波器易于構(gòu)造,并且易于從頻率響應(yīng)角度來進(jìn)行分析。但是,線性濾波在處理散粒噪聲(即圖像偶爾會(huì)出現(xiàn)很大的值)的時(shí)候,無法將噪聲像素去除,只能轉(zhuǎn)換為更為柔和但仍然可見的散粒。
這時(shí)非線性濾波就該登場(chǎng)了。
三、非線性濾波
1、中值濾波(Median filter)
中值濾波是一種典型的非線性濾波技術(shù),原理是用鄰域像素灰度值的中值來代替該像素點(diǎn)的灰度值。


濾波操作:在9 x 9上面有3 x 3的窗口,從左到右,從上到下移動(dòng)。將3 x 3窗口內(nèi)的灰度值按順序排列,然后取中值代替中心的灰度值。
中值濾波在一定的條件下可以克服常見線性濾波器,如均值濾波帶來的圖像細(xì)節(jié)模糊。而且對(duì)去除椒鹽噪聲非常有效,也常用于保護(hù)邊緣信息, 保存邊緣的特性使它在不希望出現(xiàn)邊緣模糊的場(chǎng)合也很有用,是非常經(jīng)典的平滑噪聲處理方法。
中值濾波與均值濾波比較
中值濾波優(yōu)勢(shì)
在均值濾波中,將噪聲像素與非噪聲像素一并放入平均計(jì)算中,影響了輸出。在中值濾波中,噪聲像素很難被選成中值,所以幾乎不會(huì)影響到輸出。因此,中值濾波消除噪聲和邊緣保護(hù)方便都更勝一籌。
中值濾波劣勢(shì)
因?yàn)橹兄禐V波要進(jìn)行排序操作,所以處理的時(shí)間長(zhǎng),是均值濾波的5倍以上。
給鎧祛痘

OpenCV提供了中值濾波的API
/**
@param ksize aperture linear size; it must be odd and greater than 1, for example: 3, 5, 7 ...
*/
void medianBlur( InputArray src, OutputArray dst, int ksize );
注意:ksize必須是奇數(shù)
+ (UIImage *)medianBlur:(UIImage *)image size:(int)size {
Mat src;
UIImageToMat(image, src);
int finalSize = size;
if (size%2 == 0) {
finalSize = size + 1;
}
Mat dst;
medianBlur(src, dst, finalSize);
UIImage* result = MatToUIImage(dst);
return result;
}
class NolinearBlurViewController: UIViewController {
@IBOutlet weak var resultImageView: UIImageView!
let image = #imageLiteral(resourceName: "ddkai")
@IBAction func onSliderValueChanged(_ sender: UISlider) {
resultImageView.image = OpenCV.medianBlur(self.image, size: Int32(Int(sender.value)))
}
}
一些思考
為何ksize必須是奇數(shù)?
因?yàn)槿绻鹝size是偶數(shù),那么將像素灰度值從小到大排列后,必然就沒有唯一的中值。即使得出中值,那么又將那個(gè)作為中心像素呢?因此,中值濾波要求ksize必須是奇數(shù)。

中值濾波有什么不適合的場(chǎng)景?
對(duì)一些細(xì)節(jié)多,特別是線、尖頂等細(xì)節(jié)多的圖像不宜采用中值濾波。因?yàn)橹兄禐V波會(huì)將這些細(xì)節(jié)也模糊掉。
2、雙邊濾波(Bilateral filter)
空間域&像素值域
對(duì)于圖像濾波來說,圖像在空間中變化緩慢,因此相鄰的像素點(diǎn)會(huì)更相近。但是這個(gè)假設(shè)在圖像的邊緣處變得不成立。如果在邊緣處也用這種思路來進(jìn)行濾波的話,即認(rèn)為相鄰相近,則得到的結(jié)果必然會(huì)模糊掉邊緣。因?yàn)檫吘墐蓚?cè)的點(diǎn)的像素值差別很大,所以權(quán)重還需考慮像素值。
因此,濾波不但要考慮空間域(以下簡(jiǎn)稱空域),還需要考慮像素值域(以下簡(jiǎn)稱值域)。
濾波分析
- 均值濾波無法克服邊緣像素信息丟失。原因是均值濾波是基于平均權(quán)重,沒有考慮空域和值域。
- 高斯模糊部分克服了該缺陷(考慮了空域),但是無法完全避免,因?yàn)?strong>沒有考慮像素值的不同,即沒有考慮值域。
雙邊濾波
雙邊濾波是一種非線性的濾波方法,具有保邊去噪的效果。

雙邊濾波的基本思路是同時(shí)考慮像素點(diǎn)的空域和值域。
雙邊濾波在考慮值域時(shí),利用像素點(diǎn)的值的大小進(jìn)行補(bǔ)充,因?yàn)檫吘墐蓚?cè)的點(diǎn)的像素值差別很大,因此會(huì)使得其加權(quán)的時(shí)候權(quán)重具有很大的差別,從而使得只考慮自己所屬的一邊的鄰域。可以理解成先根據(jù)像素值對(duì)要用來進(jìn)行濾波的鄰域做一個(gè)分割或分類,再給該點(diǎn)所屬的類別相對(duì)較高的權(quán)重,然后進(jìn)行鄰域加權(quán)求和,得到最終結(jié)果。
在雙邊濾波器中,輸出像素的值依賴于鄰域像素值的加權(quán)值組合:

- w(i,j,k,l): 加權(quán)系數(shù), 取決于空域核和值域核的乘積。
- (i,j),(k,l): 指兩個(gè)像素點(diǎn)的坐標(biāo)。
空域核:

值域核:

雙邊濾波權(quán)重函數(shù):

空域核(d)函數(shù)是根據(jù)像素距離選擇權(quán)重,距離越近權(quán)重越大。
值域核(r)函數(shù)則是根據(jù)像素的差異來分配權(quán)值。如果兩個(gè)像素值越接近,即使相距較遠(yuǎn),也比差異大而距離近的像素點(diǎn)權(quán)重大。這點(diǎn)使得邊緣(即相距近但差異大的像素點(diǎn))的特性得以保留。
阿珂美顏


OpenCV提供了雙邊濾波的API
/**
_Sigma values_: 為了簡(jiǎn)單起見,可以將2 Sigma值設(shè)置為相同。
如果它們很小(<10)濾波器不會(huì)有太大的效果。
如果它們很大(>150),它們將具有非常強(qiáng)烈的效果,使圖像看起來“卡通化”。
_Filter size_: 大的濾波器(D> 5)非常慢,因此建議在進(jìn)行實(shí)時(shí)處理應(yīng)用程序時(shí)使用d=5。對(duì)于需要重噪聲過濾的離線應(yīng)用程序可以試下d=9。
@param src : 即源圖像,需要為8位或者浮點(diǎn)型單通道、三通道的圖像。
@param d:過濾過程中每個(gè)像素鄰域的直徑。如果這個(gè)值我們?cè)O(shè)其為非正數(shù),那么OpenCV會(huì)從第五個(gè)參數(shù)sigmaSpace來計(jì)算出它來。
@param sigmaColor :顏色空間濾波器的sigma值。這個(gè)參數(shù)的值越大,就表明該像素鄰域內(nèi)有更寬廣的顏色會(huì)被混合到一起,產(chǎn)生較大的半相等顏色區(qū)域。
@param sigmaSpace:坐標(biāo)空間中濾波器的sigma值,坐標(biāo)空間的標(biāo)注方差。
他的數(shù)值越大,意味著越遠(yuǎn)的像素會(huì)相互影響,從而使更大的區(qū)域足夠相似的顏色獲取相同的顏色。
當(dāng)d>0,d指定了鄰域大小且與sigmaSpace無關(guān)。否則,d正比于sigmaSpace。
*/
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType = BORDER_DEFAULT );
+ (UIImage *)bilateralFilter:(UIImage *)image
d:(int)d
sigmaColor:(double)sigmaColor
sigmaSpace:(double)sigmaSpace {
Mat src;
UIImageToMat(image, src);
if (src.channels() == 4) {
cvtColor(src, src, CV_BGRA2BGR);
}
Mat dst;
bilateralFilter(src, dst, d, sigmaColor, sigmaSpace);
UIImage* result = MatToUIImage(dst);
return result;
}
class BilateralFilterViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var blurImageView: UIImageView!
@IBOutlet weak var gBlurImageView: UIImageView!
@IBOutlet weak var resultImageView: UIImageView!
private var d: Int32 = 1
private var color: Double = 1.0
private var space: Double = 1.0
override func viewDidLoad() {
super.viewDidLoad()
blurImageView.image = OpenCV.blur(imageView.image, sizeX: 3, sizeY: 3)
gBlurImageView.image = OpenCV.gaussianblur(imageView.image, sizeX: 3, sizeY: 3)
}
@IBAction func onSliderValueChanged(_ sender: UISlider) {
d = Int32(sender.value)
transform()
}
@IBAction func onSlider2ValueChanged(_ sender: UISlider) {
color = Double(sender.value)
transform()
}
@IBAction func onSlider3ValueChanged(_ sender: UISlider) {
space = Double(sender.value)
transform()
}
private func transform() {
resultImageView.image = OpenCV.bilateralFilter(imageView.image, d: d, sigmaColor: color, sigmaSpace: space)
}
}
小小經(jīng)驗(yàn)
如何選取合適的參數(shù)?
- 使用OpenCV API時(shí)可以先看下API的注釋文檔,比如在雙邊濾波的注釋文檔中對(duì)Sigma的取值做了說明。這些值一般都是經(jīng)驗(yàn)值。
_Sigma values_: 為了簡(jiǎn)單起見,可以將2 Sigma值設(shè)置為相同。
如果它們很小(<10)濾波器不會(huì)有太大的效果。
如果它們很大(>150),它們將具有非常強(qiáng)烈的效果,使圖像看起來“卡通化”。
使用滑竿幫助快速調(diào)節(jié)參數(shù),觀察效果。
從原理公式出發(fā),假定一些參數(shù),觀察其趨勢(shì),掌握規(guī)律。
四、小結(jié)
本篇主要介紹了非線性濾波的概念,并通過例子講解了中值濾波和雙邊濾波。 非線性濾波的應(yīng)用廣泛,不但要掌握API的調(diào)用,更要明白各種濾波的原理,這樣才能創(chuàng)造個(gè)性化的濾波,也許有一天你就創(chuàng)造出自己的美顏濾鏡了。 今天就到這了,有疑問的朋友可以給我留言,咱們下篇見!