基于积分图的二值图像膨胀算法实现
积分图来源与发展
积分图是Crow在1984年首次提出,是为了在多尺度透视投影中提高渲染速度。随后这种技术被应用到基于NCC的快速匹配、对象检测和SURF变换中、基于统计学的快速滤波器等方面。积分图是一种在图像中快速计算矩形区域和的方法,这种算法主要优点是一旦积分图首先被计算出来我们可以计算图像中任意大小矩形区域的和而且是在常量时间内。这样在图像模糊、边缘提取、对象检测的时候极大降低计算量、提高计算速度。第一个应用积分图像技术的应用是在Viola-Jones的对象检测框架中出现。
积分图概念
在积分图(Integral Image - ii)上任意位置(x, y)处的ii(x, y)表示该点左上角所有像素之和,表示如下:
膨胀介绍
膨胀操作是图像形态学两个最基本的操作之一,另外一个是腐蚀。主要应用在二值图像和灰度图像分析上,膨胀操作可以适当的根据结构元素的大小来扩张图像前景对象。对二值图像来说,看上去像似边缘增长一样。数学定义如下:
其中W表示窗口大小,OpenCV中定义为结构元素,常见的结构元素包括
矩形
十字交叉
假设有3x3结构元素
二值图像如下
使用3x3结构元素膨胀之后结果如下:
上述图像说明如下:
1-表示白色(255),
0-表示黑色。
蓝色1-表示膨胀之后扩张区域,
红色1-表示二值图像原区域大小。
基于积分图的形态学膨胀算法步骤
根据输入二值图像建立积分图
使用积分图索引查找结构元素重叠区块的像素总和,如果不为0 而且总和不等于窗口大小X255,则中心像素设为255 ,即膨胀
重复第二步实现对每个像素点做相同计算。
输出结果,显示
积分图方式与传统方式运行时间比对
图像为600x400大小, 基于Java语言JDK8实现代码与运行测试结果如下:
从上面可以看出,基于积分图的方式,随着结构元素的变大,计算时间趋于一个常量时间值-C,而基于传统方式随着结构元素变大,时间消耗成几何级数增加。充分证明了基于积分图方式二值膨胀操作是一种高效时间线性化的算法实现。
基于积分图膨胀代码Java版本实现
public class FastDilateOperator extends AbstractByteProcessor {
private byte[] data;
private int radius; // must be odd
public FastDilateOperator() {
this.radius = 21;
}
public void setRadius(int radius) {
this.radius = radius;
if(radius % 2 == 0) {
throw new RuntimeException("invalid parameters");
}
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public void process(int width, int height) {
int size = width*height;
byte[] output = new byte[size];
IntIntegralImage grayii = new IntIntegralImage();
grayii.setImage(data);
grayii.process(width, height);
int yr = radius/2;
int xr = radius/2;
System.arraycopy(data, 0, output, 0, size);
int index = 0;
int sum = radius*radius*255;
int c = 0;
for(int row=0; row<height; row++) {
for(int col=0; col<width; col++) {
index = row * width + col;
c = data[index]&0xff;
if(c == 255) continue;
// 计算均值
int sr = grayii.getBlockSum(col, row, (yr * 2 + 1), (xr * 2 + 1));
// 二值化
if(sr > 0 && sr < sum) {
output[index] = (byte)255;
}
}
}
System.arraycopy(output, 0, data, 0, size);
}
}
其中IntIntegralImage类为Java版本的积分图算法实现。 传统二值图像膨胀算法Java版本实现如下:
public class NormalDilateOperator extends AbstractByteProcessor {
private byte[] data;
private int radius; // must be odd
public NormalDilateOperator() {
this.radius = 21;
}
public void setRadius(int radius) {
this.radius = radius;
if(radius % 2 == 0) {
throw new RuntimeException("invalid parameters");
}
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public void process(int width, int height) {
int size = width*height;
byte[] output = new byte[size];
IntIntegralImage grayii = new IntIntegralImage();
grayii.setImage(data);
grayii.process(width, height);
int yr = radius/2;
int xr = radius/2;
System.arraycopy(data, 0, output, 0, size);
int c = 0;
int nx=0, ny=0;
for(int row=0; row<height; row++) {
for(int col=0; col<width; col++) {
c = data[row*width+col]&0xff;
if(c == 255)continue;
c=0;
for(int y=-yr; y<=yr; y++) {
ny = y + row;
if(ny < 0 || ny >= height){
ny = 0;
}
for(int x=-xr; x<=xr; x++) {
nx = x+col;
if(nx < 0 || nx >= width) {
nx = 0;
}
c += data[ny*width+nx]&0xff;
}
}
if(c > 0) {
output[row*width+col] = (byte)255;
}
}
}
System.arraycopy(output, 0, data, 0, size);
}
}
运行截屏如下:
千万别瞧不起重复造轮子,
多数时候不是你不想造而是你根本
没有能力造出一个好轮子!
关注【OpenCV学堂】
长按或者扫码二维码即可关注
+OpenCV学习群 376281510
进群暗号:OpenCV