opencv4android常用變換(二)

opencv4android常用變換(二)

這是opencv4android的第二篇,之前的寫的反響不錯,不過有點少,本來上一篇中提到的那些函數應該在上一篇中都介紹的,不過被那個雙邊濾波器搞得腦子有點亂,就寫了那麼多,這一篇接着上一篇繼續寫。

圖像金字塔

一個圖像金字塔是一系列圖像的集合 - 所有圖像來源於同一張原始圖像 - 通過梯次向下採樣獲得,直到達到某個終止條件才停止採樣。
有兩種類型的圖像金字塔常常出現在文獻和應用中:
高斯金字塔(Gaussian pyramid): 用來向下採樣
拉普拉斯金字塔(Laplacian pyramid): 用來從金字塔低層圖像重建上層未採樣圖像
在這篇文檔中我們將使用 高斯金字塔 。–引至opencv教程。
說白了圖像金字塔就是實現放大和縮小圖片的一種方式。在android中實現圖片的放大和縮小有好多方式,這裏先不考慮各種實現方式的優缺點,先學會怎麼用opencv實現縮放圖像吧。
用到的函數:pyrDown(Mat src, Mat dst, Size dstsize)pyrUp(Mat src, Mat dst, Size dstsize),看起來幾乎是一樣的,也就方法名不一樣,參數:原圖像,輸出圖像,輸出的大小。
我也不理解爲什麼這裏的size就是輸出後的大小而不是內核的意思。整體代碼:

//縮小圖像
private void changeBitmap() {
    if(rgbMat==null|dstMat==null){
    rgbMat=new Mat();
    dstMat=new Mat();
    Utils.bitmapToMat(mBitmap,rgbMat);
    }
    Imgproc.pyrDown(rgbMat,dstMat,new Size(rgbMat.cols()/2,rgbMat.rows()/2));
    rgbMat=dstMat;
    Utils.matToBitmap(dstMat,dstBitmap);
    mImage.setImageBitmap(dstBitmap);
}
//放大圖像
private void grayBitmap(){
    if(rgbMat==null|dstMat==null){
        rgbMat=new Mat();
        dstMat=new Mat();
        Utils.bitmapToMat(mBitmap,rgbMat);
    }
    Imgproc.pyrUp(rgbMat,dstMat,new Size(mBitmap.getWidth(),mBitmap.getHeight()));
    Utils.matToBitmap(dstMat,mBitmap);
    mImage.setImageBitmap(mBitmap);
}

這裏在進行圖像的縮放時需要注意的有兩個地方, 在我的代碼中在縮小之後將原圖像設爲縮小之後的圖像了,這樣是微課能夠看到多次縮放之後的效果,第二點就是對於bitmap,在opencv教程代碼的示例中是能夠直接將Mat對象顯示成圖像的,不過在Android中並不能這樣,所以需要將Mat對象轉化爲Bitmap對象,這樣在縮小的過程中就會出現一個問題,Bitmap的大小如果和Mat的大小不一致的話就會報錯,所以在我的代碼中dstBitmap在初始化的時候是mBitmap的一半大小,這一點也需要注意。效果圖:

可以看出來在多次點擊縮小之後圖像的清晰度明顯下降。(我擦嘞,怎麼把歌詞也弄進去了?算了就這樣吧,大家也可以猜猜是什麼歌。)

簡單的閾值操作

最簡單的圖像分割的方法。

應用舉例:從一副圖像中利用閾值分割出我們需要的物體部分(當然這裏的物體可以是一部分或者整體)。這樣的圖像分割方法是基於圖像中物體與背景之間的灰度差異,而且此分割屬於像素級的分割。

爲了從一副圖像中提取出我們需要的部分,應該用圖像中的每一個像素點的灰度值與選取的閾值進行比較,並作出相應的判斷。(注意:閾值的選取依賴於具體的問題。即:物體在不同的圖像中有可能會有不同的灰度值。

一旦找到了需要分割的物體的像素點,我們可以對這些像素點設定一些特定的值來表示。(例如:可以將該物體的像素點的灰度值設定爲:‘0’(黑色),其他的像素點的灰度值爲:‘255’(白色);當然像素點的灰度值可以任意,但最好設定的兩種顏色對比度較強,方便觀察結果)。

以上內容來自opencv教程。
用到的函數: Imgproc.threshold(rgbMat,dstMat,n,255,operation);其中參數:原圖像,輸出圖像,閾值大小,設定的最大灰度值(該參數運用在二進制與反二進制閾值操作中),閾值方式。
說到閾值化,opencv教程上介紹了五種:
- 二進制閾值化 CV_THRESH_BINARY 0
- 反二進制閾值化 CV_THRESH_BINARY_INV 1
- 截斷閾值化 CV_THRESH_TRUNC 2
- 閾值化爲0 CV_THRESH_TOZERO 3
- 反閾值化爲0 CV_THRESH_TOZERO_INV 4

好像怎麼調都不齊,算了,一共這五種閾值化方式,中間的是android中的常量,後面是對應的int值。代碼~~~~:

 private void changeBitmap() {
    if(rgbMat==null|dstMat==null){
    rgbMat=new Mat();
    dstMat=new Mat();
    }
    Utils.bitmapToMat(grayBitmap,rgbMat);
    Imgproc.threshold(rgbMat,dstMat,n,255,operation);
    Utils.matToBitmap(dstMat,dstBitmap);
    dstImage.setImageBitmap(dstBitmap);
}

在這段代碼之前先轉化爲灰度圖之後在進行閾值化,看起來比較清晰,灰度化的代碼就不寫了,挺簡單的,雖然閾值化也挺簡單,就是調用一個函數而已,接下來就是效果圖了。

仔細看的能夠看出來,前兩個是相反的,後兩個也是相反的,有的時候回出現一些紫色和綠色不知道爲什麼,有專門學opencv的求指教。

實現自己的線性濾波器

本以爲opencv會很好些,但寫着寫着就感覺好累啊,不過說出去的話就是潑出去的水,咱們繼續吧。
濾波器之前看我博客的話應該有所瞭解,opencv中也爲我們提供了好幾種成熟的濾波器,現在我們可能因爲業務需要,要自己寫一個,用的方法就是filter2D。

套路好像不對,應該先上代碼的。

Mat kernel=Imgproc.getStructuringElement(shape,new Size(2*n+1,2*n+1),new Point(n,n));
    Imgproc.filter2D(rgbMat,dstMat,-1,kernel);

這回就簡單寫了,首先創建一個內核對象,之前看過的,之後就是調用filter2D()這個方法。參數簡單介紹一下,前兩個不說了,第三個是指圖像深度,不是學圖像的不是很懂,-1就是與原圖像深度相同,第四個是內核,之後其他的重載方法中還有錨點位置,還有兩個,最後一個應該是重複次數,剩下的那個就不是很理解,說是會在卷積過程中,該值會加到每個像素上。默認情況下,這個值爲 0。更多的信息可以參考opencv教程。

給圖像添加邊界

本來還以爲opencv4android中沒有這個方法那,是在Sobel導數中發現的這個給圖像添加邊界的這個方法。看來還是得好好研究研究Core啊。代碼:

 Utils.bitmapToMat(mBitmap,rgbMat);
    Core.copyMakeBorder(rgbMat,dstMat,10,10,10,10,Core.BORDER_CONSTANT,new Scalar(255,0,0));
    Log.d("TAG",dstMat.rows()+"/"+dstMat.cols()+"/"+mBitmap.getWidth()+"/"+mBitmap.getHeight());
    Log.d("TAG","dst:"+dstBitmap.getWidth()+"/"+dstBitmap.getHeight());
    Utils.matToBitmap(dstMat,dstBitmap);
    dstImage.setImageBitmap(dstBitmap);

一個很簡單的方法copyMakeBorder()八個參數,前六個分別是原圖像, 目標圖像,上下左右寬度,第七個是邊界填充方式,有兩種,另一種就在Core.BORDER_CONSTANT的下面,最後一個是樣式,我選擇紅色的。本以爲很簡單的問題,結果一直在報錯,後來進過查證才發現是因爲在通過矩陣使邊界變大的時候我們的Bitmap對象沒有變大,所有通過打Log發現矩陣從原來的611變成631,而dstBitmap還是611的,所以一直報錯,在創建的時候加20就好了!以後一定要注意這種在Mat轉換的時候所對應的Bitmap大小是否合適,上一次在放大和縮小的時候就吃一次虧了,不長記性啊!

Sobel導數

Sobel 算子是一個離散微分算子 (discrete differentiation operator)。 它用來計算圖像灰度函數的近似梯度。

Sobel 算子結合了高斯平滑和微分求導。

 if(rgbMat==null|dstMat==null){
    rgbMat=new Mat();
    dstMat=new Mat();
    }
    Mat gray=new Mat();//灰度圖
    Mat abs_grayx=new Mat();//灰度圖X方向求導
    Mat abs_grayy=new Mat();//灰度圖Y方向求導
    Utils.bitmapToMat(mBitmap,rgbMat);
    Imgproc.GaussianBlur(rgbMat,rgbMat,new Size(3,3),0,0);
    Imgproc.cvtColor(rgbMat,gray,Imgproc.COLOR_BGR2GRAY);
    Imgproc.Sobel(gray,abs_grayx,-1,1,0,3);//X方向求導,-1爲深度,1爲求導次數
    Imgproc.Sobel(gray,abs_grayy,-1,0,1,3);//Y方向求導,-1爲深度,1爲求導次數
    Core.addWeighted(abs_grayx,0.5,abs_grayy,0.5,0,dstMat);//合併梯度
    Utils.matToBitmap(dstMat,dstBitmap);
    dstImage.setImageBitmap(dstBitmap);
}

看一下效果。

opencv教程中效果。

基本就是一樣的(廢話,算法都是一樣的),最開始的時候在調用Sobel函數的時候,以爲在Imgproc這個了中沒有找到addWeighted這個方法,就直接在Sobel中把XY方向的導數全設置爲1了,跑也沒問題,就是輪廓太細太少了,後來在Core核心中嘗試了一下,發現了合併的方法,就按照opencv教程中的例子去寫。效果可以,有Sobel對於檢測物體輪廓應該有很大的幫助吧,到目前爲止我都不知道我寫的這些東西有什麼用。

總結

opencv4android的內容已經寫了兩篇了,發現這麼寫太慢了,這才寫了8個而且還都是Imgproc的,光Imgproc的就還有17個,而且還有其他的模塊,我是一定要寫的,雖然我不知道這個東西有什麼用,但我知道是有用的,接下來的寫的可能會簡單粗糙一些,直接就是方法代碼圖片,儘量加快速度,還有那麼多的東西要去學,那麼多未知等待去探索,不能墨跡。接下來的Imgproc還有17個準備8個一篇9個一篇剩下的還有四五個大模塊我還沒看,等具體再定,估計整體下來沒有20篇博客搞不定,加油吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章