在上一篇文章中,写出了如何画出形状的轮廓。而这一次,则在上一次的基础上,通过对轮廓的特征来判断图形是什么形状。 这一次使用的图还是上一次的那张图。
首先,先来构造一个类ShapeDetector,然后构建一个函数detect(MatOfPoint2f c)用于识别形状识别。
1 2 3 4 5 6 String shape = "unknown" ; double peri = Imgproc.arcLength(c,true );MatOfPoint2f approx = new MatOfPoint2f(); Imgproc.approxPolyDP(c,approx,0.04 * peri,true );
这里引入了contour approximation,轮廓近似,由于怕翻译得不好,下文继续采用contour approximation。这种近似是源于Ramer–Douglas–Peucker algorithm算法,这个算法的大概意思就是说,利用线段来近似描绘曲线,以减少源曲线的点数。(想要了解更多的小伙伴可以去百度了解,这里就不详细解释。)在OpenCV库中,使用Imgproc.approxPolyDP()就可以得到contour approximation。而为了取得contour approximation,我们要先得到轮廓的周长,然后再以此来得到近似。
public static void approxPolyDP(MatOfPoint2f curve, MatOfPoint2f approxCurve, double epsilon, boolean closed) 参数详解如下:
MatOfPoint2f curve :一般是由图像的轮廓点组成的点集,这里我们是使用之前所得到的轮廓
MatOfPoint2f approxCurve :表示输出的轮廓近似contour approximation
double epsilon :主要表示输出的精度,就是两个轮廓点之间最大距离数,常用值通常在原始轮廓周长的1-5%范围内。
boolean closed :表示输出的多边形是否封闭
ps:想要了解更多contour approximation,我之前提到的外国作者Adrian也给出了一个链接:https://www.pyimagesearch.com/pyimagesearch-gurus/
当我们得到了轮廓的近似曲线后,我们可以通过轮廓曲线有多少个顶点来判断是什么形状:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if (approx.toList().size()==3 ){ shape = "triangle" ; } else if (approx.toList().size()==4 ){ Rect rect = new Rect(); rect = Imgproc.boundingRect(new MatOfPoint(approx.toArray())); float ar = rect.width/(float )rect.height; if (ar>=0.95 && ar<=1.05 ){ shape = "square" ; }else { shape = "rectangle" ; } } else if (approx.toList().size()==5 ){ shape = "pentagon" ; } else { shape = "circle" ; }
这里必须要注意的是,轮廓曲线是由顶点列表组成,所以我们才可以以此来判断形状。如上代码,比如说轮廓有三个点,那么就说明该形状有三个顶点,那么我们可以确认是三角形。而对于四个点,我们还可以进一步判断是正方形还是长方形。我们知道,正方形四条边一样长,那么我们就可以用图形的宽高比来做进一步确认。这里使用boundingRect()是指用一个最小的矩形,把找到的形状包起来,所以我们就可以得到一个矩形,然后再计算宽高比。从一开始所给的图片中,当排除了所有的有角的图形后,只剩下圆形,因此这里可以粗略把其他情况视为圆形。ps:需要注意的是,这里需要不断转换MatOfPoint2f和MatOfPoint 至此,形状检测的函数完成。
接下来,就是像之前一样,寻找各个图形的轮廓。由于之前已经解释过了,这里就不再解释下面各个函数的用法。
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 rgbMat = new Mat(); Mat grayMat = new Mat(); Mat blur1 = new 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); List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(blur1, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
当找到轮廓后,就要开始画轮廓并标记所属形状。标记是把之前的文字center变成了形状的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ShapeDetector sd = new ShapeDetector(); for (MatOfPoint c : contours) { Moments m = Imgproc.moments(c); int cx = (int ) (m.m10 / m.m00 ); int cy = (int ) (m.m01 / m.m00 ); String shape = sd.detect(new MatOfPoint2f(c.toArray())); Imgproc.drawContours(rgbMat, contours, -1 , new Scalar(0 , 255 , 0 ), 2 ); Imgproc.putText(rgbMat, shape, new Point(cx , cy ), Core.FONT_HERSHEY_SIMPLEX, 0.5 , new Scalar(255 , 255 , 255 ), 2 ); Bitmap grayBitmap= Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565); Utils.matToBitmap(rgbMat, grayBitmap); img.setImageBitmap(grayBitmap); }
效果图如下:
效果图
最后再次附上Adrian的教程:
https://www.pyimagesearch.com/2016/02/08/opencv-shape-detection/