canny算法的实现(android加载图片,数组写入文件换行)
Canny邊緣檢測首先要對圖像進行高斯去噪,前面講到了高斯去噪處理,這里從對圖像灰度進行微分運算講起吧。微分運算常用的方法是利用模板算子,把模板中心對應到圖像的每一個像素位置,然后按照模板對應的公式對中心像素和它周圍的像素進行數學運算,算出圖像對應像素點的值實驗中模板矩陣選取了Laplacian算子[44]、Soble算子、Roberts算子。拉普拉茲算子是2階微分算子,它的精度還算比較高,但對噪聲過于敏感,有噪聲的情況下效果很差。羅伯特算子在光照不均勻時候效果也很差,針對噪聲影響也較為敏感。下面以較為簡單的模板作為樣例做出講解:
1、計算x和y方向的梯度值從而得到灰度的梯度幅值和梯度方向
?Gx=(hd[x][y+1]-hd[x][y]+hd[x+1][y+1]-hd[x+1][y])/2;
?Gy=(hd[x][y]-hd[x+1][y]+hd[x][y+1]-hd[x+1][y+1])/2;
G[x][y]=(int)Math.sqrt(Gy*Gy+Gx*Gx);
angle[x][y]=Math.atan2(Gy,Gx);
2、高低閾值的選取。通常canny算子的高閾值Th和低閾值Tl的0.4,Tl=0.4*Th,而高閾值根據二值化的目的選擇不同的值,先驗知識通常Th選擇方式:梯度幅值矩陣統計在梯度值,將所有梯度累加求和,取在q%(q%在0.75-0.85之間)的那個振幅值作為高閾值。
3、非極大值抑制,這是邊緣檢測的關鍵,是將區域內的梯度振幅值的極值當作邊緣點,如下圖:
對整個梯度振幅圖掃描,如圖若(x,y)的點大于dTmp1點和dTmp2的振幅則將(x,y)視為預選邊緣點,將起值置為255。由圖可以看出dTmp1點振幅值可以G(g1) + (1-cot(sigma)) *(G(g2)-G(g1))同理可以得到dTmp2點的梯度振幅值。G這樣得到一個預選邊緣點矩陣:
int [][] mayEdgeMatrix = getMaxmaiLimitMatrix(Gxy,angle);
4、掃描mayEdgeMatrix里所有預選邊緣點,將梯度振幅大于等于Th的則視為邊緣點置為255;將低于Tl的直接置為0,視為非邊緣點;介于Tl、Th之間的的置為125,視為待檢測點。這樣得到了一個初步的邊緣圖點。
5、邊緣連接,對上一部得到的圖像進行掃描,將255周圍的8領域點進行檢測,若有為125的視為邊緣點,置為255,再以這些新置為255的點8領域查找待檢測點,若有就將其置為255,直到沒有新的邊緣點產生為止。
下面給出實現的類,在下面會給出調用的方法和相應的activity
package com.example.lammy.imagetest;import android.graphics.Bitmap; import java.util.LinkedList; /*** Created by Lammy on 2016/11/12.*/ public class MyCanny {private int Th;private int Tl;private float ratioOfTh;private Bitmap bitmap;private int h, w;private int[][] Gxy;private double[][] angle;private static int mayEdgePointGrayValue = 125;public MyCanny(Bitmap bitmap, float ratioOfTh) {this.bitmap = bitmap;this.ratioOfTh = ratioOfTh;init();}private void init() {h = bitmap.getHeight();w = bitmap.getWidth();Gxy = new int[h][w];angle = new double[h][w];}//得到高斯模板矩陣public float[][] get2DKernalData(int n, float sigma) {int size = 2 * n + 1;float sigma22 = 2 * sigma * sigma;float sigma22PI = (float) Math.PI * sigma22;float[][] kernalData = new float[size][size];int row = 0;for (int i = -n; i <= n; i++) {int column = 0;for (int j = -n; j <= n; j++) {float xDistance = i * i;float yDistance = j * j;kernalData[row][column] = (float) Math.exp(-(xDistance + yDistance) / sigma22) / sigma22PI;column++;}row++;}return kernalData;}//獲得圖的灰度矩陣public int[][] getGrayMatrix(Bitmap bitmap) {int h = bitmap.getHeight();int w = bitmap.getWidth();int grayMatrix[][] = new int[h][w];for (int i = 0; i < h; i++)for (int j = 0; j < w; j++) {int argb = bitmap.getPixel(j, i);int r = (argb >> 16) & 0xFF;int g = (argb >> 8) & 0xFF;int b = (argb >> 0) & 0xFF;int grayPixel = (int) (r + g + b) / 3;grayMatrix[i][j] = grayPixel;}return grayMatrix;}//獲得高斯模糊后的灰度矩陣public int[][] GS(int[][] hd, int size, float sigma) {float[][] gs = get2DKernalData(size, sigma);int outmax = 0;int inmax = 0;for (int x = size; x < w - size; x++)for (int y = size; y < h - size; y++) {float hc1 = 0;if (hd[y][x] > inmax)inmax = hd[y][x];for (int k = -size; k < size + 1; k++)for (int j = -size; j < size + 1; j++) {hc1 = gs[size + k][j + size] * hd[y + j][x + k] + hc1;}hd[y][x] = (int) (hc1);if (outmax < hc1)outmax = (int) (hc1);}float rate = inmax / outmax;for (int x = size; x < w - size; x++)for (int y = size; y < h - size; y++) {hd[y][x] = (int) (hd[y][x] * rate);}return hd;}//獲得Gxy 和angle即梯度振幅和梯度方向public void getGxyAndAngle(int[][] Gs) {for (int x = 1; x < h - 1; x++)for (int y = 1; y < w - 1; y++) {int Gx = (Gs[x][y + 1] - Gs[x][y] + Gs[x + 1][y + 1] - Gs[x + 1][y]) / 2;//hd[x][y+1]-hd[x][y];// int Gy = (Gs[x][y] - Gs[x + 1][y] + Gs[x][y + 1] - Gs[x + 1][y + 1]) / 2;//hd[x+1][y]-hd[x][y];////另外一種算子 // int Gx = (Gs[x - 1][y + 1] + 2 * Gs[x][y + 1] // + Gs[x + 1][y + 1] - Gs[x - 1][y - 1] - 2 // * Gs[x][y - 1] - Gs[x + 1][y - 1]) / 4; // int Gy=(Gs[x-1][y-1]+2*Gs[x-1][y]+Gs[x-1][y+1]-Gs[x+1][y-1]-2*Gs[x+1][y]-Gs[x+1][y+1])/4;//G[x][y]=Math.sqrt(Math.pow(Gx, 2)+Math.pow(Gy, 2));Gxy[x][y] = (int) Math.sqrt(Gy * Gy + Gx * Gx);angle[x][y] = Math.atan2(Gy, Gx);//將梯度方向值轉向(0,2*PI)if (angle[x][y] < 0) {angle[x][y] = angle[x][y] + 2 * Math.PI;}}}//非極大值抑制,將極值點存到edge邊緣矩陣中,極值點是可能為邊緣的點public int[][] getMaxmaiLimitMatrix(int[][]Gxy,double[][]angle) {int[][] edge =new int[h][w];for (int x = 0; x < h - 1; x++)for (int y = 0; y < w - 1; y++) {double angle1 = angle[x][y] / (Math.PI);if ((angle1 > 0 && angle1 <= 0.25) | (angle1 > 1 && angle1 <= 1.25)) {double dTmp1 = Gxy[x][y + 1] + Math.abs(Math.tan(angle[x][y]) * (Gxy[x - 1][y + 1] - Gxy[x][y + 1]));double dTmp2 = Gxy[x][y - 1] + Math.abs(Math.tan(angle[x][y]) * (Gxy[x + 1][y - 1] - Gxy[x][y - 1]));double dTmp = Gxy[x][y];if (dTmp > dTmp1 && dTmp > dTmp2)edge[x][y] = 255;}if ((angle1 <= 2 && angle1 > 1.75) | (angle1 <= 1 && angle1 > 0.75)) {double dTmp1 = Gxy[x][y + 1] + Math.abs(Math.tan(angle[x][y])) * (Gxy[x + 1][y + 1] - Gxy[x][y + 1]);double dTmp2 = Gxy[x][y - 1] + Math.abs(Math.tan(angle[x][y])) * (Gxy[x - 1][y - 1] - Gxy[x][y - 1]);double dTmp = Gxy[x][y];if (dTmp > dTmp1 && dTmp > dTmp2)edge[x][y] = 255;}if ((angle1 > 1 / 4 && angle1 <= 0.5) | (angle1 > 5 / 4 && angle1 <= 1.5)) {double dTmp1 = Gxy[x - 1][y] + Math.abs(1 / Math.tan(angle[x][y])) * (Gxy[x - 1][y + 1] - Gxy[x - 1][y]);double dTmp2 = Gxy[x + 1][y] + Math.abs(1 / Math.tan(angle[x][y])) * (Gxy[x + 1][y - 1] - Gxy[x + 1][y]);double dTmp = Gxy[x][y];if (dTmp > dTmp1 && dTmp > dTmp2)edge[x][y] = 255;}if ((angle1 > 1.5 && angle1 <= 1.75) | (angle1 > 0.5 && angle1 <= 0.75)) {double dTmp1 = Gxy[x - 1][y] + Math.abs(1 / Math.tan(angle[x][y])) * (Gxy[x - 1][y - 1] - Gxy[x - 1][y]);double dTmp2 = Gxy[x + 1][y] + Math.abs(1 / Math.tan(angle[x][y])) * (Gxy[x + 1][y + 1] - Gxy[x + 1][y]);double dTmp = Gxy[x][y];if (dTmp > dTmp1 && dTmp > dTmp2)edge[x][y] = 255;}}return edge;}public void ThTlLimitPoints(int [][] maxmaiLimitMatrix,int Th , int Tl){//上面得到的為255的才可能是邊緣點,下面根據高低閾值再次去掉小于Tl點,高于Th的仍然為255,定為邊緣點,125的為預選點for(int x=1;x<h-1;x++)for(int y=1;y<w-1;y++){if(maxmaiLimitMatrix[x][y]==255){if(Gxy[x][y]<Tl)maxmaiLimitMatrix[x][y]=0;if(Gxy[x][y]>Tl&&Gxy[x][y]<Th)maxmaiLimitMatrix[x][y]=mayEdgePointGrayValue;}}}//獲得高閾值private int getTh(int [][] Gxy){//梯度振幅統計,因為通過計算振幅的最大值不超過500,因此用500的矩陣統計int []amplitudeStatistics=new int[500];for(int x=1;x<h-1;x++)for(int y=1;y<w-1;y++){amplitudeStatistics[Gxy[x][y]]++;}int pointNumber=0;int max=0;for(int i=1;i<500;i++){if(amplitudeStatistics[i]>0){max=i;}pointNumber=pointNumber+amplitudeStatistics[i];}int ThNumber=(int)(ratioOfTh*pointNumber);int ThCount=0; int Th=0;for(int i=1;i<=max;i++){if(ThCount<ThNumber)ThCount=ThCount+amplitudeStatistics[i];else{Th=i-1;break;}}return Th;}private int getTl(int Th){return (int)(Th*0.4);}//canny算法的邊緣連接public void traceEdge(double maybeEdgePointGrayValue, int edge[][]){int [][]liantongbiaoji = new int [h][w];for(int i = 0 ; i < h ; i++)for(int j = 0 ; j < w; j++) {if(edge[i][j]==255&&liantongbiaoji[i][j]==0) {if ((edge[i][j] >= maybeEdgePointGrayValue) && liantongbiaoji[i][j] == 0) {liantongbiaoji[i][j] = 1;LinkedList<Point> qu = new LinkedList<Point>();qu.add(new Point(i, j));while (!qu.isEmpty()) {Point cur = qu.removeFirst();for (int a = -1; a <= 1; a++)for (int b = -1; b <= 1; b++) {if (cur.x + a >= 0 && cur.x + a < h && cur.y + b >= 0&& cur.y + b < w) {if (edge[cur.x + a][cur.y + b] >= maybeEdgePointGrayValue&& liantongbiaoji[cur.x + a][cur.y + b] == 0) {qu.add(new Point(cur.x + a, cur.y + b));liantongbiaoji[cur.x + a][cur.y + b] = 1;edge[cur.x + a][cur.y + b] = 255;}}}}}}}}//由灰度矩陣創建灰度圖public Bitmap createGrayImage(int[][]grayMatrix){int h=grayMatrix.length;int w = grayMatrix[0].length;Bitmap bt=Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);for(int i=0;i<h;i++)for(int j=0;j<w;j++){int grayValue=grayMatrix[i][j];int color = ((0xFF << 24)+(grayValue << 16)+(grayValue << 8)+grayValue);bt.setPixel(j, i, color);}return bt;}public Bitmap getEdgeBitmap(){int grayMatrix[][] = getGrayMatrix(bitmap);int GS[][] = GS(grayMatrix , 1 , 0.6f);getGxyAndAngle(GS);Th = getTh(Gxy);int [][] mayEdgeMatrix = getMaxmaiLimitMatrix(Gxy,angle);Tl = getTl(Th);ThTlLimitPoints(mayEdgeMatrix , Th , Tl);traceEdge(mayEdgePointGrayValue , mayEdgeMatrix);for(int x=1;x<h-1;x++)for(int y=1;y<w-1;y++) {if(mayEdgeMatrix[x][y]!=255)mayEdgeMatrix[x][y]=0;}return createGrayImage(mayEdgeMatrix);}class Point {Point(int a, int b) {this.x = a;this.y = b;}int x;int y;}} View Code實現了上述算法移植到手機,發現在java平臺上實現后運行效果非常好,而運行在手機端上效果很差。同樣的算法為何結果相差如此之大呢?
經過一步步的排查,將每一步得到的數組打印到文件與java打印的數組比較,最終發現了原因,罪魁禍首就是安卓加載jpg、png甚至是bitmap到內存時圖片的寬高都會變大,且比率不一定相同,這樣導致我加載同一張圖片時,android自動對圖片進行了放大,導致手機的邊緣更加模糊且無故增加了一些細節。為了解決這個問問題,我先獲取未加載時候圖片的寬高,在加載圖片后再壓縮回加載前圖片的大小。 在acitiviy里有講解,下面直接貼出代碼:
package com.example.lammy.imagetest;import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageView; import android.widget.Toast;import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.Writer;public class MainActivity extends AppCompatActivity {ImageView imageView;Bitmap bt;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageView = (ImageView) findViewById(R.id.image);int scr =R.drawable.xl;//獲取源圖像的寬和高(因為android在加載圖片到手機里的時候會使得圖片寬高變大,且比率不一定一樣,為了讓其不變形必須記下加載前的圖片寬高)再壓縮回去BitmapFactory.Options options=new BitmapFactory.Options();options.inJustDecodeBounds=true;//(設為true 圖片不加入內存效率高) BitmapFactory.decodeResource(getResources(),scr , options);int outWidth = options.outWidth;int outHeight = options.outHeight;System.out.println("jpg圖原圖"+outHeight+","+outWidth);options.inJustDecodeBounds=false;bt = BitmapFactory.decodeResource(getResources(),scr );System.out.println("加載后圖:"+bt.getHeight()+","+bt.getWidth());//將圖片壓縮到加載前的寬高,當然圖片太大也可以寬高同比率壓縮。bt = ThumbnailUtils.extractThumbnail(bt,outWidth,outHeight);imageView.setImageBitmap(bt);// jpg圖原圖271,482// 加載后圖:711,1265 }public void click(View view) {MyCanny myCanny =new MyCanny(bt,0.85f);int Gs [][] =myCanny.GS(myCanny.getGrayMatrix(bt) , 1 , 0.6f);try {outPutArray(Gs ,"grayMatrix.txt");} catch (Exception e) {e.printStackTrace();}Bitmap edge = myCanny.getEdgeBitmap();edge = ThumbnailUtils.extractThumbnail(edge,1000,600);imageView.setImageBitmap(edge);}// 將數組寫入到data目錄public void outPutArray(int[] [] a ,String filename) throws Exception {try {File file = new File("data/data/com.example.lammy.imagetest/files/"+filename);FileWriter fileWriter = new FileWriter(file);BufferedWriter bw=new BufferedWriter(fileWriter);int size = 15;for(int i = 0 ; i < size ; i ++) {for (int j = 0; j < size; j++) {String s = a[i][j] + " ";bw.write(s);bw.flush();}bw.newLine();bw.flush();}bw.flush();bw.close();}catch (Exception e){System.out.println("mmmmmmmmmmmmmmmmmmmmm");}} } View Code打印了一下加載圖前后的大小:
jpg圖原圖271,482加載后圖:711,1265
?發現加載到內存后放大了2.6倍左右(原因:decodeResource這個方法會根據drawable所在的資源目錄適配不同的dpi,因此放大了),且為了適應手機屏幕的分辨率,寬高放大的比率不相等(相近),這導致了我們算法的效果變差的主要原因,因此我將圖像壓縮回加載前的大小,再使用canny算法邊緣檢測,效果就和java的差不多了。下面是效果:
原圖的灰度圖 邊緣圖
轉載于:https://www.cnblogs.com/bokeofzp/p/6057952.html
總結
以上是生活随笔為你收集整理的canny算法的实现(android加载图片,数组写入文件换行)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 修复win7便签功能
- 下一篇: linux_NandFlash_driv