美文网首页Android干货AndroidAndroid开发
使用JNI实现Sobel算子图像边缘检测

使用JNI实现Sobel算子图像边缘检测

作者: Jomeslu | 来源:发表于2017-04-08 00:50 被阅读1434次

    本文主要讲解sobel的算法原理以及如何使用C++算法实现。通过JNI调用像素点,重新绘制生成沙子效果的图片(sand)。代码已经开源,下载地址:https://github.com/Jomes/sand

    图形边缘检测

    图形边缘检测是图像处理的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。边缘检测算子分为两阶:
    一阶:Sobel算子,Roberts Cross算子, Prewitt算子, Canny算子,罗盘算子
    二阶:Marr-Hildreth,在梯度方向的二阶导数过零点。
    sand的实现是使用Sobel算子实现的,本文重点讲解Sobel算子。

    Sobel算子原理

    Soble边缘检测算法是计算机视觉领域重要的处理方法,也是图像处理常用的算子之一。它是一离散性算子,用来运算图像亮度函数的梯度之近似值。在图像的任何一点使用此算子,将会产生对应的梯度矢量或是其法矢量。

    公式

    它是由2组3*3矩阵组成,分别为纵向和横向,通过与图像像素倦卷积,分别得到纵向、横向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:

    公式

    具体计算如下:

    Gx= (-1)*f(x-1, y-1) + 0*f(x,y-1) + 1*f(x+1,y-1)
          +(-2)*f(x-1,y) + 0*f(x,y)+2*f(x+1,y)
          +(-1)*f(x-1,y+1) + 0*f(x,y+1) + 1*f(x+1,y+1)
       = [f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)]-[f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1)]
    
    Gy =1* f(x-1, y-1) + 2*f(x,y-1)+ 1*f(x+1,y-1)
          +0*f(x-1,y) 0*f(x,y) + 0*f(x+1,y)
          +(-1)*f(x-1,y+1) + (-2)*f(x,y+1) + (-1)*f(x+1, y+1)
         = [f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)]-[f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)]
    

    其中f(a,b), 表示图像(a,b)点的灰度值;

    图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

    0_1276230660TlTp (1).gif

    如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。
    引用部分文章

    算法的实现

    定义2组3*3矩阵组成

    int const SOBEL_X[3][3] = {{-1, 0, 1},
                               {-2, 0, 2},
                               {-1, 0, 1}};
    int const SOBEL_Y[3][3] = {{-1, -2, -1},
                               {0,  0,  0},
                               {1,  2,  1}};
    

    计算Gx亮度差分近似值

    int get_x(const int *pixel, int w, int h,int x, int y) {
        int pixel_x = (
                (SOBEL_X[0][0] * get_color(pixel, w, h, x - 1, y - 1)) +
                (SOBEL_X[0][1] * get_color(pixel, w, h, x, y - 1)) +
                (SOBEL_X[0][2] * get_color(pixel, w, h, x + 1, y - 1)) +
                (SOBEL_X[1][0] * get_color(pixel, w, h, x - 1, y)) +
                (SOBEL_X[1][1] * get_color(pixel, w, h, x, y)) +
                (SOBEL_X[1][2] * get_color(pixel, w, h, x + 1, y)) +
                (SOBEL_X[2][0] * get_color(pixel, w, h, x - 1, y + 1)) +
                (SOBEL_X[2][1] * get_color(pixel, w, h, x, y + 1)) +
                (SOBEL_X[2][2] * get_color(pixel, w, h, x + 1, y + 1))
        );
        return pixel_x;
    }
    

    计算Gy亮度差分近似值

    int get_y(const int *pixel, int w, int h, int x, int y) {
        int pixel_Y = (
                (SOBEL_Y[0][0] * get_color(pixel, w, h, x - 1, y - 1)) +
                (SOBEL_Y[0][1] * get_color(pixel, w, h, x, y - 1)) +
                (SOBEL_Y[0][2] * get_color(pixel, w, h, x + 1, y - 1)) +
    
                (SOBEL_Y[1][0] * get_color(pixel, w, h, x - 1, y)) +
                (SOBEL_Y[1][1] * get_color(pixel, w, h, x, y)) +
                (SOBEL_Y[1][2] * get_color(pixel, w, h, x + 1, y)) +
    
                (SOBEL_Y[2][0] * get_color(pixel, w, h, x - 1, y + 1)) +
                (SOBEL_Y[2][1] * get_color(pixel, w, h, x, y + 1)) +
                (SOBEL_Y[2][2] * get_color(pixel, w, h, x + 1, y + 1))
        );
        return pixel_Y;
    }
    

    遍历图片所有的像素点,梯度G大于某一阀值 则认为该点(x,y)为边缘点。将边缘的坐标保存以来。

    void sobel(const int *pixel, int *total_pot,Dot *dot,int w, int h, int threshold, int point_count) {
        *total_pot =0;
        int i =0;
        for (int y = 1; y < h; ++y) {
            for (int x = 1; x < w; ++x) {
                int pixelX = get_x(pixel, w, h,x,y);
                int pixelY = get_y(pixel, w, h,x,y);
               // 开方
                int boundary_gray = (int) sqrt(pixelX * pixelX + pixelY * pixelY);
               //梯度G大于某一阀值 则认为该点(x,y)为边缘点。
                if(boundary_gray>threshold) {
                    i++;
                    if (i<point_count) {
                        *total_pot = i;
                        Dot *d = &(dot[i]);
                        d->x = x;
                        d->y = y;
                    }
                }
            }
        }
    }
    

    底层C进行Sobel运算后,得到了边缘检测的坐标数组,拿到数组后进行绘制新的bitmap,这样新的沙子效果图出来了。

        public Bitmap tramform(Bitmap bitmap,int threshold,int ponitNum ){
            int width =  bitmap.getWidth();
            int height = bitmap.getHeight();
            Bitmap newImage = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(newImage);
            Paint paint = new Paint();
            paint.setAntiAlias(false);
            paint.setStyle(Paint.Style.STROKE);
            int pixels[] = new int [width*height];
            bitmap.getPixels(pixels,0,width,0,0,width,height);
            int[] generate = generate(pixels, width, height, threshold, ponitNum);
            for (int i = 0, n = generate.length; i + 1 < n; i += 2) {
                    int x = generate[i]>0? generate[i]:0;
                    int y = generate[i+1] >0?generate[i+1]:0 ;
                    int color = bitmap.getPixel(x,y);
                    paint.setColor(color);
                    canvas.drawCircle(x, y, 1, paint);
                }
    
            return newImage;
        }
    

    效果图

    通过效果图可以看出,绘制了一张挺漂亮的沙子效果图片。其实我们也是画家,只是我们不是用笔画而已,
    假如将图片换成你女朋友的图片,你女朋友会不会很感动呢!


    sand.gif

    下载地址:https://github.com/Jomes/sand

    干货

    Android博客周刊 :每周分享国内外热门技术博客以及优秀的类库、Google视频、面试经历。
    最新源码汇总:每周分享新的开源代码,有效果图,更直观。

    相关文章

      网友评论

      • 35663802804d:还没看你在讲什么。但似乎很吊的样子,先赞个
      • jackzhous:我试了一张照片,为什么只重新绘制了一半就不绘制了
        Jomeslu:多加ponitnum 就行

      本文标题:使用JNI实现Sobel算子图像边缘检测

      本文链接:https://www.haomeiwen.com/subject/zrlvottx.html