本文主要讲解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视频、面试经历。
最新源码汇总:每周分享新的开源代码,有效果图,更直观。
网友评论