图像的二值化
图像二值化就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果。
将256个亮度等级的灰度图像通过适当的阈值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,首先,图像的二值化有利于图像的进一步处理,使图像变得简单,而且数据量减小,能凸显出感兴趣的目标的轮廓。其次,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像。
在实际应用中,很多图像的分析最终都转换为二值图像的分析,比如:医学图像分析、前景检测、字符识别,形状识别。二值化+数学形态学能解决很多计算机识别工程中目标提取的问题。
开操作演示---文本分离与切割
开操作是先腐蚀后膨胀的过程。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
跟开操作相对应的是闭操作。另外,腐蚀和膨胀在下文中有介绍。
在 cv4j 中,我们封装好了这些形态学的常用操作,比如开闭操作、腐蚀和膨胀等等。
其中,开操作的代码如下:
public class MorphOpen {
/**
* in order to remove litter noise block, erode + dilate operator
*
* @param binary
* @param structureElement
*/
public void process(ByteProcessor binary, Size structureElement) {
Erode erode = new Erode();
Dilate dilate = new Dilate();
erode.process(binary, structureElement);
dilate.process(binary, structureElement);
}
}
先来看一个完整demo的效果图
第三步如果看不太清楚,我们看一下放大的效果图
如上图所示,demo完成了文本的切割。我们来看看具体的代码是怎么实现的。
准备工作展示原图
Resources res = getResources();
final Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.test_binary1);
image0.setImageBitmap(bitmap);
第一步二值化
CV4JImage cv4JImage = new CV4JImage(bitmap);
Threshold threshold = new Threshold();
threshold.process((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()),Threshold.THRESH_TRIANGLE,Threshold.METHOD_THRESH_BINARY_INV,255);
image1.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());
第二步开操作
MorphOpen morphOpen = new MorphOpen();
cv4JImage.resetBitmap();
morphOpen.process((ByteProcessor)cv4JImage.getProcessor(),new Size(5));
image2.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());
第三步连通组件标记
ConnectedAreaLabel connectedAreaLabel = new ConnectedAreaLabel();
byte[] mask = new byte[cv4JImage.getProcessor().getWidth() * cv4JImage.getProcessor().getHeight()];
List rectangles = new ArrayList<>();
connectedAreaLabel.process((ByteProcessor)cv4JImage.getProcessor(),mask,rectangles,true);
cv4JImage.resetBitmap();
Bitmap newBitmap = cv4JImage.getProcessor().getImage().toBitmap();
if (Preconditions.isNotBlank(rectangles)) {
Tools.drawRects(newBitmap,rectangles);
}
image3.setImageBitmap(newBitmap);
其实,做完第三步再结合ocr就可以识别出具体文字啦。如果再结合一下网络爬虫的话,意义更大。
虽然, cv4j 目前还只是移动端的库,但是它毕竟是java开发的,改成适合desktop的很容易。
腐蚀操作演示---硬币计数
腐蚀操作是一种消除边界点,使边界向内部收缩的过程。可以用来消除小且无意义的物体。腐蚀操作扫描图像的每一个像素,用结构元素与其覆盖的二值图像做“与”操作:如果都为1,结果图像的该像素为1,否则为0。
跟腐蚀操作相对的是膨胀操作。腐蚀用于分割独立的图像元素,而膨胀用于连接相邻的元素。
腐蚀的算法:
其中,g(x,y)为腐蚀后的灰度图像,f(x,y)为原灰度图像,B为结构元素。腐蚀运算是由结构元素确定的邻域块中选取图像值与结构元素值的差的最小值。
可以简化为:
来看一个例子,原图中有很多硬币,通过一步步的分析计算出硬币的个数。
准备工作展示原图
Resources res = getResources();
final Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.test_coins);
image0.setImageBitmap(bitmap);
第一步二值化
CV4JImage cv4JImage = new CV4JImage(bitmap);
Threshold threshold = new Threshold();
threshold.process((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()),Threshold.THRESH_OTSU,Threshold.METHOD_THRESH_BINARY_INV,255);
image1.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());
第二步腐蚀操作
Erode erode = new Erode();
cv4JImage.resetBitmap();
erode.process((ByteProcessor)cv4JImage.getProcessor(),new Size(3),10);
image2.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());
第三步连通组件标记
ConnectedAreaLabel connectedAreaLabel = new ConnectedAreaLabel();
byte[] mask = new byte[cv4JImage.getProcessor().getWidth() * cv4JImage.getProcessor().getHeight()];
int num = connectedAreaLabel.process((ByteProcessor)cv4JImage.getProcessor(),mask,null,false); // 获取连通组件的个数
SparseIntArray colors = new SparseIntArray();
Random random = new Random();
int height = cv4JImage.getProcessor().getHeight();
int width = cv4JImage.getProcessor().getWidth();
int size = height * width;
for (int i = 0;i0) {
newBitmap.setPixel(col,row,colors.get(c));
}
}
}
image3.setImageBitmap(newBitmap);
if (num>0)
numTextView.setText(String.format("总计识别出%d个硬币",num));
最终获取了连通组件的个数也就是硬币的个数,并且在已经识别的硬币上随机着色。
总结
cv4j 是gloomyfish和我一起开发的图像处理库,纯java实现,目前还处于早期的版本。这周,我们开始做二值图像的分析(腐蚀、膨胀、开闭操作、轮廓提取等等),这个模块并没有完成全部功能,预计下周能完工。
先前的文章:
Java实现高斯模糊和图像的空间卷积
Java实现图片滤镜的高级玩法
Java实现图片的滤镜效果