在之前的文章已经提到如何在android中运用OpenCV,这次就记录一下在一个外国博主看到的几篇OpenCV+Python的博客后,自己再用android写下来的几个小案例。
这次记录的是,如何把形状的轮廓中心描绘出来。

以下图片是此次使用的图片

shapes_and_colors
shapes_and_colors

从此图可以看出,这是一张有多个不同形状的图,此次要实现的是把图片中各个形状的轮廓画出来,并标注出形状的中心。而要实现这个目标,则需要先做:
-将图片灰度化
-将图片进行模糊降噪,从而使得轮廓的描绘更加准确
-将图片二值化。一般来说,用于图片二值化的是边缘检测和阈值处理,在此次小项目中使用的是阈值处理

首先我们先读取图片,再对图片进行一些处理。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//读入图片
Bitmap srcBitmap;
srcBitmap = BitmapFactory.decodeResource(getResources(), id);

//建立几个Mat类型的对象
Mat rgbMat = new Mat();
Mat grayMat = new Mat();
Mat blur1 = new Mat();

//将原始的bitmap转换为mat型.
Utils.bitmapToMat(srcBitmap, rgbMat);
//将图像转换为灰度
Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//以两个不同的模糊半径对图像做模糊处理,前两个参数分别是输入和输出图像,第三个参数指定应用滤波器时所用核的尺寸,最后一个参数指定高斯函数中的标准差数值
Imgproc.GaussianBlur(grayMat, blur1, new Size(5, 5), 0);
//图片二值化
Imgproc.threshold(blur1, blur1, 60, 255, Imgproc.THRESH_BINARY);
//将处理后的图片显示出来
Bitmap grayBitmap;
grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
Utils.matToBitmap(blur1, grayBitmap);
img.setImageBitmap(grayBitmap);

代码1-2行主要是通过Bitmap读入图片,我是把图片放在项目的mipmap中,然后来直接读取。
代码6-11是利用mat类来存储图片信息,在OpenCV中,是通过mat类来操作图片内容。基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。(参考自官网的解释,有兴趣了解更多的可以去官网看下。)在OpenCV中的Utils类就有一个现成的函数可以将bitmap转换成mat。
接下来的几行代码,则是完成上面所说的几个步骤。
首先是灰度化图片,将图片灰度化会大大减小图像处理计算量。
其次在对图片进行降噪时,是采用高斯滤波来进行降噪。
最后是二值化图片,所谓图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果。在图像处理的三行代码中,每个函数的第一个值是源mat,第二个值才是经过处理后得到的目标mat。
在经过以上处理后的图片呈现一下形状,则是以前景为白色,背景为黑色的图片。

ok,继续下一步,寻找这些白色形状的轮廓

1
2
3
4
//寻找图形的轮廓
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(blur1, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

这几行代码是找出白色形状的轮廓,这里大概讲解一下findContours的几个参数:

public static void findContours(Mat image, List contours, Mat hierarchy, int mode, int method)

-Mat image:要找轮廓的图像,是Mat类

-List contours:找到的轮廓的集合

-Mat hierarchy:hierarchy是一个向量,向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。若并无前后轮廓这些的存在,其值为-1.

-int mode:定义轮廓的检索模式:
①:RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
②:RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到
③:RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
④:RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。

-int method:定义轮廓的近似方法:
①:CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
②:CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
③:CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
找到轮廓后,是时候把各个形状的轮廓给画出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//遍历每个图形的轮廓
for (MatOfPoint c : contours) {
//计算轮廓的中心
Moments m = Imgproc.moments(c);
int cx = (int) (m.m10 / m.m00);
int cy = (int) (m.m01 / m.m00);

//画轮廓
Imgproc.drawContours(rgbMat, contours, -1, new Scalar(0, 255, 0), 2);
//画中心的圆点
Imgproc.circle(rgbMat, new Point(cx, cy), 7, new Scalar(255, 255, 255), -1);
//在圆点旁边显示文字center
Imgproc.putText(rgbMat, "center", new Point(cx - 20, cy - 20), Core.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(255, 255, 255), 2);
//再把mat转换为bitmap显示出来
Bitmap grayBitmap;
grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
Utils.matToBitmap(rgbMat, grayBitmap);
img.setImageBitmap(grayBitmap);
}

其中,moments为图形的三阶矩,而此时为了计算出轮廓的中心,可以采用以下公式:


接下来解释三个函数的参数值:

①drawContours(Mat image, List contours, int contourIdx, Scalar color, int thickness)的各个参数解释如下:

Mat image:表示目标图像

List contours:表示输入的轮廓组

int contourIdx:指明画第几个轮廓,如果该参数为负值,则画全部轮廓。上面引用时填了-1,代表画出全部轮廓。

Scalar color:为轮廓的颜色,Scalar表示了具有4个元素的数组,用来表示RGB颜色值(三个参数)。Scalar( a, b, c )则代表的RGB颜色值为:Red = c, Green = b and Blue= a

int thickness:为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部。

②public static void circle(Mat img, Point center, int radius, Scalar color, int thickness)参数解释:

Mat img:为源图像

Point center:为画圆的圆心坐标

int radius:为圆的半径

Scalar color:为设定圆的颜色,规则根据B(蓝)G(绿)R(红)

int thickness:如果是正数,表示组成圆的线条的粗细程度。否则,表示圆是否被填充。

③public static void putText(Mat img, String text, Point org, int fontFace, double fontScale, Scalar color, int thickness)参数解释:

Mat img:需要写字的原图像

String text:需要写的内容

Point org:需要写的内容的左下角的坐标

int fontFace:字体样式,其取值如下:
-FONT_HERSHEY_SIMPLEX – 正常大小无衬线字体。
-FONT_HERSHEY_PLAIN – 小号无衬线字体。
-FONT_HERSHEY_DUPLEX – 正常大小无衬线字体。( 比CV_FONT_HERSHEY_SIMPLEX更复杂)
-FONT_HERSHEY_COMPLEX – 正常大小有衬线字体。
-FONT_HERSHEY_TRIPLEX – 正常大小有衬线字体 ( 比CV_FONT_HERSHEY_COMPLEX更复杂)
-FONT_HERSHEY_COMPLEX_SMALL – CV_FONT_HERSHEY_COMPLEX 的小译本。
-FONT_HERSHEY_SCRIPT_SIMPLEX – 手写风格字体。
-FONT_HERSHEY_SCRIPT_COMPLEX – 比CV_FONT_HERSHEY_SCRIPT_SIMPLEX更复杂。

double fontScale:字体大小

Scalar color:颜色

int thickness:字体的厚度

ok,现在我们得到如下效果:

最终效果图
最终效果图

至此,我们已经实现了想要的效果。
本文所提到的一些知识有些是摘取了别人的博客,如若作者觉得侵权,请联系我,我马上删除。
本人也是刚接触OpenCV,还是有一堆知识是不懂的,如果哪里讲错欢迎大家多多指教。

ps:本代码是以白色前景,黑色背景作为设想,所以如果使用其他颜色背景的图片需做一定的处理才有效。
最后附上我看的教程:https://www.pyimagesearch.com/2016/02/01/opencv-center-of-contour/
该博主写了很多博文,而且问的问题博主也会给你解答,唯一缺点就是全英2333,有兴趣的小伙伴可以去瞧瞧。