OpenCV4Android學習之影象基本特徵檢測
影象中的資訊包括邊緣、直線、橢圓、色塊或輪廓、角點等形式,這些資訊在計算機視覺和影象處理語境中通常被稱為特徵。下面就來了解一些結合OpenCV在Android平臺上的常規的特徵檢測演算法,這裡使用AndroidStudio開發平臺,當然Eclipse也一樣。如果對在這兩個IDE上部署OpenCV不熟悉的話,可以參考我之前寫的這篇文章:Eclipse與AndroidStudio關於OpenCV4Android庫的部署
1.邊緣和焦點檢測
邊緣檢測和角點檢測時兩種最基本也是非常有用的特徵檢測演算法,這些演算法包括高斯差分、Canny邊緣檢測器、Sobel運算元和Harris角點。比如我們想在一幅圖中找到不同目標的邊界或者角點,用於分析目標在影象中的旋轉或移動等情況,這時候就需要知道邊緣和角點的資訊了。
高斯差分
這裡我們先了解下什麼是邊緣和高斯模糊方法,因為下面要用到邊緣這個性質和對影象進行高斯模糊來計算邊緣點,即邊緣點。- 簡單的說,邊緣就是影象中畫素亮度變化明顯的點。
- 高斯(Gaussian)模糊是最常用的模糊方法,是將指定畫素變換為其與周邊畫素加權平均後的值,權重就是高斯分佈函式計算出來的值。OpenCV提供了GaussianBlur()的內建函式,我們可以在應用中使用它執行高斯模糊。這裡是一篇關於高斯模糊演算法的介紹:高斯模糊演算法
形如:
Mat src;
Imgproc.GaussianBlur(src,src,new Size(6,6),0);讓我們回到高斯差分技術,其演算法分成三步:
- 將影象轉換為弧度影象
- 用兩個不同的模糊半徑對灰色影象執行高斯模糊
- 對上面兩幅影象相減,得到一副只包含邊緣點的結果影象
在應用中,建立一個用於給定影象計算邊緣的函式:
/**
* 高斯差分
*/
private void DifferenceOfGaussian() {
Mat grayMat = new Mat();
Mat blur1 = new Mat();
Mat blur2 = new Mat();
//將影象轉換為灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//以兩個不同的模糊半徑對影象做模糊處理,前兩個引數分別是輸入和輸出影象,第三個引數指定應用濾波器時所用核的尺寸,最後一個引數指定高斯函式中的標準差數值
Imgproc.GaussianBlur(grayMat, blur1, new Size(15, 15), 5);
Imgproc.GaussianBlur(grayMat, blur2, new Size(21, 21), 5);
//將兩幅模糊後的影象相減
Mat DoG = new Mat();
Core.absdiff(blur1, blur2, DoG);
//反轉二值閾值化
Core.multiply(DoG, new Scalar(100), DoG);
Imgproc.threshold(DoG, DoG, 50, 255, Imgproc.THRESH_BINARY_INV);
//將Mat轉換為點陣圖
Utils.matToBitmap(DoG, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
- Canny邊緣檢測器
Canny邊緣檢測器演算法使用了多向灰度梯度和滯後閾值化等複雜技巧,它是邊緣檢測的最優方法。
演算法分為四個步驟:
- 平滑圖:使用合適的模糊半徑執行高斯模糊來減少影象內的噪聲
- 計算影象梯度:計算影象梯度,並將其分為垂直、水平和斜對角
- 非最大值:由影象梯度計算得到梯度方向,檢查某一畫素在梯度正方向和負方向的區域性最大值,然後抑制該畫素
- 用滯後閾值化選擇邊緣:檢查某一條邊緣是否明顯到足以作為最終輸出,最後取出所有不夠明顯的邊緣
下面是OpenCV對該演算法在Android上的實現程式碼:
/**
* Canny邊緣檢測器
*/
private void Canny() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
//將影象轉換為灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_RGB2GRAY);
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
//將Mat轉換回點陣圖
Utils.matToBitmap(cannyEdges, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
- Sobel運算元
Sobel運算元和Canny邊緣檢測一樣,計算畫素灰度梯度。
演算法分為四個步驟:
- 將影象轉換為灰度影象
- 計算水平方向灰度梯度絕對值
- 計算垂直方向灰度梯度絕對值
- 計算最終梯度
下面是OpenCV對該演算法在Android上的實現程式碼:
/**
* Sobel運算元
*/
private void Sobel() {
Mat grayMat = new Mat();
//用來儲存結果的Mat
Mat sobel = new Mat();
//分別用於儲存梯度和絕對梯度的Mat
Mat grad_x = new Mat();
Mat abs_grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_y = new Mat();
//將影象轉換為灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//計算水平方向梯度
Imgproc.Sobel(grayMat, grad_x, CvType.CV_16S, 1, 0, 3, 1, 0);
//計算垂直方向梯度
Imgproc.Sobel(grayMat, grad_y, CvType.CV_16S, 0, 1, 3, 1, 0);
//計算兩個方向上梯度絕對值
Core.convertScaleAbs(grad_x, abs_grad_x);
Core.convertScaleAbs(grad_y, abs_grad_y);
//計算結果梯度
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 1, sobel);
//將Mat轉換為點陣圖
Utils.matToBitmap(sobel, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
- Harris角點檢測
Harris焦點檢測器是一種在角點檢測中最常用的技術,在影象上使用滑動視窗計算亮度的變化。
下面是OpenCV對該演算法在Android上的實現程式碼:
/**
* Harris角點
*/
private void HarrisCorner() {
Mat grayMat = new Mat();
Mat corners = new Mat();
//將影象轉換成灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
Mat tempDst = new Mat();
//找出角點
Imgproc.cornerHarris(grayMat, tempDst, 2, 3, 0.04);
//在新的影象上繪製角點
Mat tempDstNorm = new Mat();
Core.normalize(tempDst, tempDstNorm, 0, 255, Core.NORM_MINMAX);
Core.convertScaleAbs(tempDstNorm, corners);
//將Mat轉換為點陣圖
Random r = new Random();
for (int i = 0; i < tempDstNorm.cols(); i++) {
for (int j = 0; j < tempDstNorm.rows(); j++) {
double[] value = tempDstNorm.get(j, i);
if (value[0] > 150)
Core.circle(corners, new Point(i, j), 5, new Scalar(r.nextInt(255)), 2);
}
}
//將Mat轉換為點陣圖
Utils.matToBitmap(corners, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
2.霍夫變換
對於影象分析來說,除了邊緣和角點外,還需要檢測如直線、圓、橢圓等其他相關形狀。例如我們想在一張桌子的影象中檢測簽字筆等,對於這種情況,一般採用霍夫變換這種技術,它被廣泛採用的利用數學等式的引數形式在影象中檢測形狀的技術。通常我們考慮二維形狀的霍夫變換,比如直線和圓,它相對於球體等複雜形狀而言更為簡單。
霍夫直線
我們對於霍夫變化一般不會直接對影象執行演算法,因為影象中任何一條明顯的直線都一定是邊緣,反之則不然。OpenCV提供了兩種實現霍夫直線的方式:標準霍夫直線和概率霍夫直線。它們的主要區別在於概率霍夫直線選取經過隨機取樣的邊緣點子集,而不是所有的邊緣點。因為這種方式處理的點更少,所以這使得演算法的執行速度更快,而不會降低演算法的效果。
下面是OpenCV對該演算法在Android上的實現程式碼:/** * 霍夫直線 */ private void HoughLines() { Mat grayMat = new Mat(); Mat cannyEdges = new Mat(); Mat lines = new Mat(); //將影象轉換成灰度 Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY); //這裡使用Canny邊緣檢測技術計算影象中的邊緣,使用其他的也可以 Imgproc.Canny(grayMat, cannyEdges, 10, 100); Imgproc.HoughLinesP(cannyEdges, lines, 1, Math.PI / 180, 50, 20, 20); Mat houghLines = new Mat(); houghLines.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1); //在影象上繪製直線 for (int i = 0; i < lines.cols(); i++) { double[] points = lines.get(0, i); double x1, y1, x2, y2; x1 = points[0]; y1 = points[1]; x2 = points[2]; y2 = points[3]; Point pt1 = new Point(x1, y1); Point pt2 = new Point(x2, y2); //在一副影象上繪製直線 Core.line(houghLines, pt1, pt2, new Scalar(255, 0, 0), 1); } //將Mat轉換為點陣圖 Utils.matToBitmap(houghLines, currentBitmap); loadImageToImageView(); } /** * 設定影象 */ private void loadImageToImageView() { ImageView imgView = (ImageView) findViewById(R.id.image_view); imgView.setImageBitmap(currentBitmap); }
處理結果圖:
霍夫圓
與霍夫直線類似,步驟也一樣。
下面是OpenCV對該演算法在Android上的實現程式碼:
/**
*霍夫圓
*/
private void HoughCircles() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat circles = new Mat();
//將影象轉換成灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//這裡使用Canny邊緣檢測技術計算影象中的邊緣,使用其他的也可以
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
Imgproc.HoughCircles(cannyEdges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, cannyEdges.rows() / 15);
Mat houghCircles = new Mat();
houghCircles.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1);
//在影象上繪製圓形
for (int i = 0; i < circles.cols(); i++) {
double[] parameters = circles.get(0, i);
double x, y;
int r;
x = parameters[0];
y = parameters[1];
r = (int) parameters[2];
Point center = new Point(x, y);
//在一副影象上繪製圓形
Core.circle(houghCircles, center, r, new Scalar(255, 0, 0), 1);
}
//將Mat轉換為點陣圖
Utils.matToBitmap(houghCircles, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
3.輪廓
有時候我們只想關注於感興趣的目標,這就需要將影象分解成更小的片元,比如我們在一角、五角和一元硬幣中分析五角的情況。我們有兩種實現途徑,一種是使用霍夫圓,另一種就是利用輪廓檢測將影象分割為更小的部分,每個部分代表一個特定的硬幣。輪廓通常以影象中的邊緣來計算,邊緣與輪廓的區別在於輪廓是閉合的,邊緣可以是任意的。
下面是OpenCV對該演算法在Android上的實現程式碼:
/**
* 輪廓檢測
*/
private void Contours() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat hierarchy = new Mat();
List<MatOfPoint> contourList = new ArrayList<MatOfPoint>(); //A list to store all the contours
//儲存輪廓列表
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//這裡使用Canny邊緣檢測技術計算影象中的邊緣,使用其他的也可以
Imgproc.Canny(originalMat, cannyEdges, 10, 100);
//找出輪廓
Imgproc.findContours(cannyEdges, contourList, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//在新的影象上繪製輪廓
Mat contours = new Mat();
contours.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC3);
Random r = new Random();
for (int i = 0; i < contourList.size(); i++) {
Imgproc.drawContours(contours, contourList, i, new Scalar(r.nextInt(255), r.nextInt(255), r.nextInt(255)), -1);
}
//將Mat轉換為點陣圖
Utils.matToBitmap(contours, currentBitmap);
loadImageToImageView();
}
/**
* 設定影象
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
處理結果圖:
以上就是影象的基本特徵在Android裝置上實現的不同演算法,也是我們在Android相關應用開發中的基礎。