jashliao 用 VC++ 實現 fanfuhan OpenCV 教學021 ~ opencv-021-圖像捲積和均值模糊(blur)影像濾波器 [打馬賽克/降低銳利度&降低雜訊干擾/顏色分割前置動作]
jashliao 用 VC++ 實現 fanfuhan OpenCV 教學021 ~ opencv-021-圖像捲積和均值模糊(blur)影像濾波器 [打馬賽克/降低銳利度&降低雜訊干擾/顏色分割前置動作]
資料來源: https://fanfuhan.github.io/
https://fanfuhan.github.io/2019/04/01/opencv-021/
GITHUB:https://github.com/jash-git/fanfuhan_ML_OpenCV
https://github.com/jash-git/jashliao-implements-FANFUHAN-OPENCV-with-VC
★前言:
★主題:
濾波實際上是信號處理里的一個概念,而圖像本身也可以看成是一個二維的信號。其中像素點灰度值的高低代表信號的強弱。
其中:
高頻:圖像中灰度變化劇烈的點。
低頻:圖像中平坦的,灰度變化不大的點。
根據圖像的高頻與低頻的特徵,我們可以設計相應的高通與低通濾波器,高通濾波可以檢測圖像中尖銳、變化明顯的地方;低通濾波可以讓圖像變得光滑,濾除圖像中的噪聲。
濾波器的種類有很多, 在新版本的OpenCV中,提供了如下五種常用的圖像平滑處理操作方法,且他們分別被封裝在單獨的函數中,使用起來非常方便:
· 方框濾波——boxblur函數
· 均值濾波——blur函數
· 高斯濾波——GaussianBlur函數
· 中值濾波——medianBlur函數
· 雙邊濾波——bilateralFilter函數
本次主要以blur為測試目標,blur函數的作用是,對輸入的圖像src進行均值濾波后用dst輸出。
OPENCV提供的均值濾波(blur)函數,其相關介紹如下所列:
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數對通道是獨立處理的,且可以處理任意通道數的圖片,但需要注意,待處理的圖片深度應該為CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二個參數,OutputArray類型的dst,即目標圖像,需要和源圖片有一樣的尺寸和類型。比如可以用Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標圖。
第三個參數,Size類型(對Size類型稍后有講解)的ksize,內核的大小。一般這樣寫Size( w,h )來表示內核的大小( 其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3×3的核大小,Size(5,5)就表示5×5的核大小
第四個參數,Point類型的anchor,表示錨點(即被平滑的那個點),注意他有默認值Point(-1,-1)。如果這個點坐標是負值的話,就表示取核的中心為錨點,所以默認值Point(-1,-1)表示這個錨點在核的中心。
第五個參數,int類型的borderType,用於推斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT,我們一般不去管它。
缺點:均值濾波本身存在着固有的缺陷,即它不能很好地保護圖像細節,在圖像去噪的同時也破壞了圖像的細節部分,從而使圖像變得模糊,不能很好地去除噪聲點。
★C++
// VC_FANFUHAN_OPENCV021.cpp : 定義主控台應用程式的進入點。 // /* // Debug | x32 通用屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> C:\opencv\build\include | | 連結器 | |一一般 | | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib | | |一輸入 | | 其他相依性 -> opencv_world411d.lib;%(AdditionalDependencies) // Releas | x64 組態屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> C:\opencv\build\include | | 連結器 | |一般 | | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib | | |一輸入 | | 其他相依性 -> opencv_world411.lib;%(AdditionalDependencies) */ #include "stdafx.h" #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; void showHistogram(InputArray src, cv::String StrTitle); void backProjection_demo(Mat &mat, Mat &model); void blur3x3(Mat &src,Mat *det); void pause() { printf("Press Enter key to continue..."); fgetc(stdin); } int main() { Mat src = imread("../../images/target.png"); if (src.empty()) { cout << "could not load image.." << endl; pause(); return -1; } else { imshow("input_src", src); showHistogram(src, "Histogram_input_src"); //均值模糊 Mat dst; blur(src, dst, Size(6, 6), Point(-1, -1), 4);//blur(src, dst, Size(15, 15), Point(-1, -1), 4);//blur(src, dst, Size(3, 3), Point(-1, -1), 4); imshow("dst_blur", dst); showHistogram(dst, "Histogram_dst_blur"); Mat dst01 = Mat::zeros(src.size(), src.type()); blur3x3(src, &dst01);// 3x3 均值模糊,自定义版本实现 imshow("dst01_blur3x3", dst01); showHistogram(dst01, "Histogram_dst01_blur3x3"); waitKey(0); } return 0; } void blur3x3(Mat &src, Mat *det) { // 3x3 均值模糊,自定义版本实现 for (int row = 1; row < src.rows - 1; row++) { for (int col = 1; col < src.cols - 1; col++) { Vec3b p1 = src.at<Vec3b>(row - 1, col - 1); Vec3b p2 = src.at<Vec3b>(row - 1, col); Vec3b p3 = src.at<Vec3b>(row - 1, col + 1); Vec3b p4 = src.at<Vec3b>(row, col - 1); Vec3b p5 = src.at<Vec3b>(row, col); Vec3b p6 = src.at<Vec3b>(row, col + 1); Vec3b p7 = src.at<Vec3b>(row + 1, col - 1); Vec3b p8 = src.at<Vec3b>(row + 1, col); Vec3b p9 = src.at<Vec3b>(row + 1, col + 1); int b = p1[0] + p2[0] + p3[0] + p4[0] + p5[0] + p6[0] + p7[0] + p8[0] + p9[0]; int g = p1[1] + p2[1] + p3[1] + p4[1] + p5[1] + p6[1] + p7[1] + p8[1] + p9[1]; int r = p1[2] + p2[2] + p3[2] + p4[2] + p5[2] + p6[2] + p7[2] + p8[2] + p9[2]; det->at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b / 9); det->at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g / 9); det->at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r / 9); } } } void backProjection_demo(Mat &image, Mat &model) { Mat image_hsv, model_hsv; cvtColor(image, image_hsv, COLOR_BGR2HSV);//彩色轉HSV cvtColor(model, model_hsv, COLOR_BGR2HSV); // 定义直方图参数与属性 int h_bins = 32, s_bins = 32; int histSize[] = { h_bins, s_bins };//要切分的像素強度值範圍,預設為256。每個channel皆可指定一個範圍。例如,[32,32,32] 表示RGB三個channels皆切分為32區段 float h_ranges[] = { 0, 180 }, s_ranges[] = { 0, 256 }; const float* ranges[] = { h_ranges, s_ranges }; int channels[] = { 0, 1 }; Mat roiHist;//計算ROI的直方圖 calcHist(&model_hsv, 1, channels, Mat(), roiHist, 2, histSize, ranges); normalize(roiHist, roiHist, 0, 255, NORM_MINMAX, -1, Mat()); Mat roiproj, backproj; calcBackProject(&image_hsv, 1, channels, roiHist, roiproj, ranges);//使用反向投影 產生ROI(前景)的mask bitwise_not(roiproj, backproj);//產生背景的mask imshow("ROIProj", roiproj); imshow("BackProj", backproj); } void showHistogram(InputArray src, cv::String StrTitle) { bool blnGray = false; if (src.channels() == 1) { blnGray = true; } // 三通道/單通道 直方圖 紀錄陣列 vector<Mat> bgr_plane; vector<Mat> gray_plane; // 定义参数变量 const int channels[1] = { 0 }; const int bins[1] = { 256 }; float hranges[2] = { 0, 255 }; const float *ranges[1] = { hranges }; Mat b_hist, g_hist, r_hist, hist; // 计算三通道直方图 /* void calcHist( const Mat* images, int nimages,const int* channels, InputArray mask,OutputArray hist, int dims, const int* histSize,const float** ranges, bool uniform=true, bool accumulate=false ); 1.輸入的圖像數組 2.輸入數組的個數 3.通道數 4.掩碼 5.直方圖 6.直方圖維度 7.直方圖每個維度的尺寸數組 8.每一維數組的範圍 9.直方圖是否是均勻 10.配置階段不清零 */ if (blnGray) { split(src, gray_plane); calcHist(&gray_plane[0], 1, 0, Mat(), hist, 1, bins, ranges); } else { split(src, bgr_plane); calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges); calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges); calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges); } /* * 显示直方图 */ int hist_w = 512; int hist_h = 400; int bin_w = cvRound((double)hist_w / bins[0]); Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3); // 归一化直方图数据 if (blnGray) { normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1); } else { normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1); normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1); normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1); } // 绘制直方图曲线 for (int i = 1; i < bins[0]; ++i) { if (blnGray) { line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(hist.at<float>(i))), Scalar(255, 255, 255), 2, 8, 0); } else { line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0); line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0); line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0); } } imshow(StrTitle, histImage); }
Python
import cv2 as cv import numpy as np def custom_blur(src): h, w, ch = src.shape print("h , w, ch", h, w, ch) result = np.copy(src) for row in range(1, h-1, 1): for col in range(1, w-1, 1): v1 = np.int32(src[row-1, col-1]) v2 = np.int32(src[row-1, col]) v3 = np.int32(src[row-1, col+1]) v4 = np.int32(src[row, col-1]) v5 = np.int32(src[row, col]) v6 = np.int32(src[row, col+1]) v7 = np.int32(src[row+1, col-1]) v8 = np.int32(src[row+1, col]) v9 = np.int32(src[row+1, col+1]) b = v1[0] + v2[0] + v3[0] + v4[0] + v5[0] + v6[0] + v7[0] + v8[0] + v9[0]; g = v1[1] + v2[1] + v3[1] + v4[1] + v5[1] + v6[1] + v7[1] + v8[1] + v9[1]; r = v1[2] + v2[2] + v3[2] + v4[2] + v5[2] + v6[2] + v7[2] + v8[2] + v9[2]; result[row, col] = [b//9, g//9, r//9] cv.imshow("result", result) src = cv.imread("D:/vcprojects/images/lena.png") cv.namedWindow("input", cv.WINDOW_AUTOSIZE) cv.imshow("input", src) dst = cv.blur(src, (15, 15)) cv.imshow("blur", dst) custom_blur(src) cv.waitKey(0) cv.destroyAllWindows()
★結果圖:
★延伸說明/重點回顧: