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

1,125 阅读4分钟

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

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

干货

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