2018Android實驗室CV培訓總結

傅立葉變換(空間域 -> 頻率域)
什麼是傅立葉變換?
維基百科:
傅立葉變換(法語:Transformation de Fourier、英語:Fourier transform)是一種線性積分變換,用於訊號在時域(或空域)和頻域之間的變換,在物理學和工程學中有許多應用。因其基本思想首先由法國學者約瑟夫·傅立葉系統地提出,所以以其名字來命名以示紀念。實際上傅立葉變換就像化學分析,確定物質的基本成分;訊號來自自然界,也可對其進行分析,確定其基本成分。
通俗的講就是可以將任何具有周期性變換的函式分解為諸多個不同頻率的三角函式的疊加

從頻率的方向觀察整個分解的三角函式

傅立葉變換在影象處理中有什麼作用?
1.影象增強與影象去噪
- 絕大部分噪音都是影象的高頻分量,通過低通濾波器來濾除高頻——噪聲; 邊緣也是影象的高頻分量,可以通過新增高頻分量來增強原始影象的邊緣;
2.影象分割之邊緣檢測
- 提取影象高頻分量
3.影象特徵提取:
- 形狀特徵:傅立葉描述
- 紋理特徵:直接通過傅立葉係數來計算紋理特徵
- 其他特徵:將提取的特徵值進行傅立葉變換來使特徵具有平移、伸縮、旋轉不變性
4.影象壓縮
- 可以直接通過傅立葉係數來壓縮資料;常用的離散餘弦變換是傅立葉變換的實變換;
垂直邊緣檢測
邊緣是影象中灰度發生急劇變化的區域邊界。影象當中存在有畫素的躍變,而邊緣檢測的目的就是找到這些畫素點。數字影象中求導是利用差分近似微分來進行的.

首先要說的就是邊緣檢測運算元
一階導數運算元
- Roberts運算元
Roberts運算元是一種斜向偏差分的梯度計算方法,梯度的大小代表邊緣的強度,梯度的方向與邊緣的走向垂直,Roberts運算元定位精度高,在水平和垂直方向的效果好,兩個卷積核Gx、Gy分別為:

- Sobel運算元
Sobel運算元是一組方向運算元,從不同的方向檢測邊緣。Sobel運算元通常對灰度漸變和噪聲較多的影象處理的比較好。兩個卷積核Gx、Gy分別為:

- Prewitt運算元
Prewitt運算元是一種邊緣樣板運算元,利用畫素點上下左右鄰點灰度差,在邊緣處達到極值檢測邊緣,對噪聲具有平滑的作用。兩個卷積核Gx、Gy分別為:

二階導數運算元
- Laplacian運算元
拉普拉斯運算元是一種常用的二階導數運算元。實際中可根據二階導數運算元過零點的性質來確定邊緣的位置。常用的兩種模板分別如圖所示:

- Canny運算元 Canny運算元把邊緣檢測問題轉換為檢測單位函式極大值的問題來考慮。而OpenCV提供了更加簡便的Canny函式。
#include <iostream> #include <opencv2/opencv.hpp> #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace std; using namespace cv; int main(int argc, int argv) { //載入影象 Mat srcImage = imread("D:\\Desktop\\pic.jpg"); if (srcImage.empty()) { cout << "can not find the img" << endl; } //顯示原始圖 imshow("【原圖】Canny邊緣檢測", srcImage); Mat edge, greyImage; //引數定義 //【1】將原影象轉換為灰度影象 cvtColor(srcImage, greyImage, CV_BGR2GRAY); //【2】先使用3×3核心來降噪 blur(greyImage, edge, Size(3, 3)); //【3】執行Canny運算元 Canny(edge, edge, 3, 9, 3); //【4】顯示效果圖 imshow("均值濾波【效果圖】", edge); waitKey(0); //等待任意按鍵按下 return 0; } 複製程式碼
影象濾波
影象濾波,即在儘量保留影象細節特徵的條件下對目標影象的噪聲進行抑制,即去掉無關的噪音,保留有用的資訊
- 平滑 -- 去噪,例如剔除到影象上的一隻鳥,根據周圍兩邊的畫素值計算一個近似值,換為均值,即對影象積分。
- 銳化 -- 將噪點變得更強,例如加強一隻鳥的顯示,將影象邊緣的差值變得更大,即對影象進行微分。
濾波常用API
- 邊緣檢測
- Sobel,拉普拉斯運算元
- 影象去噪/平滑
- GaussianBlur
中心差分
#include <opencv2/opencv.hpp> #include "opencv2/highgui/highgui.hpp" #include <iostream> #define WINDOW_NAME "【程式視窗】" using namespace cv; using namespace std; int main(int argc, char **argv) { Mat srcImage = imread("D:\\Desktop\\lena.jpg", 1); //讀取圖片 cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);//轉為灰度影象 namedWindow("srcImage", WINDOW_AUTOSIZE); //新建視窗 imshow("srcImage", srcImage);//顯示影象 //waitKey(0);//影象顯示為一幀 //由於中心差分的原因,輸出影象比原始圖象少了兩列 Mat dImage = Mat(srcImage.rows, srcImage.cols - 2, CV_8UC1); //迴圈遍歷整個影象 for (int i = 0; i < srcImage.rows; i++) { for (int j = 1; j < srcImage.cols - 1; j++) { //對整型資料型別進行運算,進行中心差分 dImage.at<uchar>(i, j - 1) = srcImage.at<uchar>(i, j + 1) - srcImage.at<uchar>(i, j - 1); } } namedWindow("dImage", WINDOW_AUTOSIZE); //處理後圖像視窗 imshow("dImage", dImage);//顯示處理後的影象 waitKey(0); //影象顯示為一幀 return 0; } 複製程式碼
中型差分結果演示:

高斯模糊
首先要清楚高斯模糊的原理是什麼?
高斯模糊,聽起來很高大上,但是其實是一門很基礎的演算法應用,普遍運用在Android及其他方面,高斯模糊之所以叫做高斯模糊,是因為它運用了概率統計上的正態分佈(高斯分佈)的密度函式,即:

其中u是x的均值,σ是x的標準差,由於每次進行計算的時候都是以當前的點為原點,所以我們將u設為0,即化簡為正態分佈的一維方程:

正態分佈的一維方程是一種山型圖形,越靠近中間,取值越大,越遠離中間,取值越小。

而高斯模糊的原理就是將影象上各個點的畫素值等效於其邊緣的畫素均值,並且這個均值不只是簡單的平均值,而是加權平均值,即將中心點”看作正態分佈的原點,其他值按照正態分佈的值取權重,相加可得。將加權平均值應用到整個影象,就會產生模糊的效果。顯然,二維的影象使用一維的正態分佈是不合適的,因此我們要使用的是正態分佈的二維方程,即:

在二維上的影象:

假如現在整個影象共有9個畫素點,中心點為原點,並且令σ為1(標準正態分佈):

帶入高斯公式當中,求出每位上的值:

現在這九個畫素值加起來等於4.8976,由於加權平均權值的和必須等於1,因此這九個畫素值嗎每個都必須都除以4.8976,得:

這樣影象中的每個畫素值都被加權平均化,與周圍的畫素點的畫素差值變小,整個影象看起來更加柔和,更加模糊。
接著將該正態分佈的權值放入卷積核當中,然後對整個影象進行卷積。卷積結束之後,高斯模糊就算是完成了。
什麼是"卷積"?
其實卷積(Convolution)是影象處理中最基本的操作,就是一個二維矩陣A(MN)和一個二維矩陣B(mn)做若干操作,生成一個新的二維矩陣C(M*N),其中m和n遠小於M和N,B稱為卷積核(kernel),又稱濾波器矩陣或模板。


而濾波器(卷積核)又分為很多種,一種是均值濾波器,中心原點加權值為0,周圍元素值相等,並且和等於1,另一種就是我們現在用的高斯濾波器,高斯濾波器是均值濾波器的高階版本,唯一的區別在於,均值濾波器的卷積核的每個元素都相同,而高斯濾波器的卷積核的元素服從高斯分佈。稍微介紹一下其他常用的濾波器:
影象銳化濾波器
即在邊緣檢測得基礎上,將邊緣檢測得到的畫素值再加到原影象上,使得整個影象的邊緣顯得更加銳化。也就是在邊緣檢測得卷積核基礎上,中心再加1,接著再對影象進行卷積。


浮雕濾波器
浮雕濾波器浮雕濾波器可以給影象一種3D陰影的效果。只要將中心一邊的畫素減去另一邊的畫素就可以了。


A:原影象。B:銳化。C:邊緣檢測。D:浮雕

那影象的模糊程度取決於什麼?
- 1.取決於σ,也就是x的標準差。σ越大,整個影象的畫素值就會變得更加平緩,也就是更模糊;而σ越小,整個影象的畫素變化很小甚至基本不變,導致結果看不到高斯模糊。

- 2.取決於卷積核的大小,卷積核越大,中心點畫素被周圍畫素(核越大使得這些周圍的畫素越遠)權值化的就更加明顯,影象也就更加模糊。


程式碼
匯入標頭檔案
#include <opencv2/opencv.hpp> #include "opencv2/highgui/highgui.hpp" #include <iostream> #define WINDOW_NAME "【程式視窗】" #define PI 3.1415926 using namespace cv; using namespace std; 複製程式碼
呼叫攝像頭
VideoCapture video(0); while (true) { Mat frame;//視訊的每一幀 video >> frame;//將視訊寫入每一幀 cvtColor(frame,frame,COLOR_RGB2GRAY); cvNamedWindow("frame", WINDOW_AUTOSIZE); imshow("frame",frame); waitKey(30); } 複製程式碼
高斯模糊 -- 卷積核進行實現
Mat srcImage = imread("D:\\Desktop\\lena.jpg", 1);//讀取圖片 cvtColor(srcImage, srcImage,COLOR_BGR2GRAY);//轉為灰度影象 namedWindow("srcImage", WINDOW_AUTOSIZE);//新建視窗 imshow("srcImage", srcImage);//顯示影象 //waitKey(0);//影象顯示為一幀 //由於中型差分的原因,輸出影象比原始圖象少了兩列 Mat dImage = Mat(srcImage.rows,srcImage.cols - 2,CV_8UC1); //迴圈遍歷整個影象 for (int i = 0; i < srcImage.rows; i++) { for (int j = 1; j < srcImage.cols - 1; j++) { //對整型資料型別進行運算,進行中型差分 dImage.at<uchar>(i, j - 1) = srcImage.at<uchar>(i, j + 1) - srcImage.at<uchar>(i, j - 1); } } namedWindow("dImage", WINDOW_AUTOSIZE);//處理後圖像視窗 imshow("dImage", dImage);//顯示處理後的影象 waitKey(0);//影象顯示為一幀 Mat srcImage = imread("D:\\Desktop\\lena.jpg", 1); //讀取圖片 cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);//轉為灰度影象 namedWindow("srcImage", WINDOW_AUTOSIZE);//新建視窗 imshow("srcImage", srcImage);//顯示影象 /*高斯模糊*/ //5×5卷積模板 Mat model = Mat(5, 5, CV_64FC1); double sigma = 80;//超引數,根據經驗所得 for (int i = - 2; i <= 2 ; i++)//進行遍歷 { for (int j = -2; j <= 2; j++) { //正態分佈 model.at<double>(i + 2, j + 2) = exp(-(i * i + j * j) / (2 * sigma * sigma)) / (2 * PI * sigma * sigma); } } //歸一化 double gaussSum = 0; gaussSum = sum(model).val[0];//卷積核 求和 for (int i = 0; i < model.rows; i++) { for (int j = 0; j < model.cols; j++) { model.at<double>(i, j) = model.at<double>(i, j) / gaussSum; } } Mat dst = Mat(srcImage.rows - 4,srcImage.cols - 4,CV_8UC1); //對整個圖片進行遍歷卷積 for (int i = 2; i < srcImage.rows - 2; i++) { for (int j = 2; j < srcImage.cols - 2; j++) { double sum = 0;//求和目標值 for (int m = 0; m < model.rows; m++) { for (int n = 0; n < model.cols; n++) { sum += (double)srcImage.at<uchar>(i + m - 2, j + n - 2) * model.at<double>(m,n);//對整個卷積核進行卷積 } } dst.at<uchar>(i - 2, j - 2) = (uchar)sum;//結果賦值到dst影象當中 } } namedWindow("gaussBlur", WINDOW_AUTOSIZE); imshow("gaussBlur", dst); waitKey(0); //影象顯示為一幀 複製程式碼
呼叫高斯模糊庫函式 -- 一行程式碼實現
Mat dst = srcImage.clone(); //一行程式碼高斯模糊 GaussianBlur(srcImage, dst, Size(17, 17), 180); namedWindow("gaussBlur", WINDOW_AUTOSIZE); imshow("gaussBlur", dst); waitKey(0); //影象顯示為一幀 複製程式碼
高斯模糊(5×5卷積核+sigma = 80)結果演示:
