中值濾波就是用一個奇數點的移動視窗(要求奇數主要是為了保證整個模板有唯一中心元素),將視窗中心點的值用視窗內各點的中值代替。假設視窗內有5點,其值為80、90、200、110和120,那麼此視窗內各點的中值即為110。
設有一個一維序列\(f_1,f_2,...,f_n\),取視窗長度(點數)為m(m為奇數),對其進行中值濾波,就是從輸入序列中相機抽出m個數\(f_{i-v},...,f_{i-1},f_i,f_{i+1},...,f_{i+v}\)(其中\(f_i\)為視窗中心點值,\(v=(m-1)/2\)),再將這m個點按其數值大小排序,取其序號為中心點的那個數作為濾波輸出。用數學公式表示為:
如:以3*3的領域為例求中值濾波中畫素5的值。
void medianFilter(cv::Mat& src, cv::Mat& dst, cv::Size size) {
/*step1:判斷視窗size是否為奇數*/
if (size.width % 2 == 0 || size.height % 2 == 0) {
cout << "折積核視窗大小應為奇數!\n";
exit(-1);
}
/*step2:對原圖進行邊界擴充*/
int h = (size.height - 1) / 2;
int w = (size.width - 1) / 2;
Mat src_border;
copyMakeBorder(src, src_border, h, h, w, w, BORDER_REFLECT_101);
/*step3:折積操作*/
map<uchar, Point> mp; // 定義容器儲存每個折積視窗內各畫素點的<畫素值, 畫素位置>
for (int i = h; i < src.rows + h; i++) {
for (int j = w; j < src.cols + w; j++) {
mp.clear();
for (int ii = i - h; ii <= i + h; ii++) {
for (int jj = j - w; jj <= j + w; jj++) {
Point point(jj, ii);
uchar value;
if (src.channels() == 1) {
// 灰度影象,儲存灰度值
value = src_border.at<uchar>(ii, jj);
}else {
// 彩色影象,儲存亮度值
uchar value_b = src_border.at<cv::Vec3b>(ii, jj)[0];
uchar value_g = src_border.at<cv::Vec3b>(ii, jj)[1];
uchar value_r = src_border.at<cv::Vec3b>(ii, jj)[2];
value = 0.114 * value_b + 0.587 * value_g + 0.299 * value_r;
}
mp[value] = point;
}
}
// 將視窗中心點的值用視窗內個點的中值代替
auto iter = mp.begin();
int count = 0;
Point pixel;
int median_size = mp.size() / 2;
while (iter != mp.end()) {
if (count == median_size) {
pixel = Point(iter->second.x, iter->second.y);
break;
}
count++;
iter++;
}
if (src.channels() == 1) {
dst.at<uchar>(i - h, j - w) = src_border.at<uchar>(pixel.y, pixel.x);
}
else {
dst.at<cv::Vec3b>(i - h, j - w)[0] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[0];
dst.at<cv::Vec3b>(i - h, j - w)[1] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[1];
dst.at<cv::Vec3b>(i - h, j - w)[2] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[2];
}
}
}
}
copyMakeBorder(src,src_border,h,h,w,w,BORDER_REFLECT_101);
在模板或折積的加權運算中的影象邊界問題:當在影象上移動模板(折積核)至影象邊界時,在原影象中找不到與折積核中的加權係數相對應的N個畫素(N為折積核元素個數),即折積核懸掛在影象的邊界上,這種現象在影象的上下左右四個邊界上均會出現。例如,當模板為:
設原影象為:
經過折積操作之後影象為:
"-"表示無法進行折積操作的畫素點。
解決方法有2種:①忽略影象邊界資料(即不管邊界,折積操作的範圍從整張圖縮小為邊界內縮K圈,K的值隨折積核尺寸變化)。②將原影象往外擴充畫素,如在影象四周複製源影象邊界的值,從而使得折積核懸掛在原影象四周時也能進行正常的計算。
opencv邊框處理copyMakeBorder: https://zhuanlan.zhihu.com/p/108408180
value=0.114*value_b+0.587*value_g+0.299*value_r;
對於彩色影象,我們取影象亮度的中間值,亮度值的計算方法為:
map<uchar, Point> mp;
map為C++的stl中的關聯性容器,為了實現快速查詢,map內部本身就是按序儲存的(map底層實現是紅黑二元樹)。在我們插入<key, value>鍵值對時,就會按照key的大小順序進行儲存,其中key的型別必須能夠進行 < 運算,且唯一,預設排序是按照從小到大遍歷。
因此,將亮度值或灰度值作為鍵,map將自動進行按鍵排序,無需手動書寫排序程式碼。
折積核size為(5, 5)。