This is the 8th day of my participation in Gwen Challenge

Previously, we mentioned contour discovery, contour perimeter and contour area, and then judged contour shape by the fixed relationship between contour area and circumference. But for irregular shapes, in fact, it is difficult for us to judge through quantitative relations. Referring to the previous straight line fitting method, we can also analyze the contour by shape fitting method. The most common is to fit the outline into rectangles and other polygons.

API

Maximum enclosing rectangle

public static Rect boundingRect(Mat array)
Copy the code
  • Parameter 1: array, input grayscale map or two-dimensional point collection.

The method is used to obtain the maximum enclosing rectangle containing the contour of the object or the two-dimensional point set in the input image. Return a Rect object, which can be drawn directly using the Rectangle () method.

Minimum enclosing rectangle

public static RotatedRect minAreaRect(MatOfPoint2f points) 
Copy the code

Parameter 1: points, the two-dimensional collection of input points.

The method is used to obtain the minimum enclosing rectangle of the input two-dimensional point set. Return the RotateRect object. Both the RotateRect and Rect types represent rectangles, but there are some differences in how they are represented. The difference is obvious by looking at the member variables. Rect is positioned by the coordinates in the upper left corner, default horizontal, horizontal and vertical, and then determined by width and height. RotateRect determines the position through center and calculates the coordinates of each vertex by combining Angle with width and height to determine the rectangle.

public class Rect {

 public int x, y, width, height;

 public Rect(int x, int y, int width, int height) {
     this.x = x;
     this.y = y;
     this.width = width;
     this.height = height;
 }
……
}
Copy the code
public class RotatedRect {

 public Point center;
 public Size size;
 public double angle;

 public RotatedRect(a) {
     this.center = new Point();
     this.size = new Size();
     this.angle = 0;
 }
……
}
Copy the code

Contour polygon

public static void approxPolyDP(MatOfPoint2f curve, MatOfPoint2f approxCurve, double epsilon, boolean closed) 
Copy the code
  • Parameter 1: Curve, input contour pixel.

  • Parameter 2: approxCurve, the result of polygon approximation, including polygon vertex coordinate set.

  • Parameter 3: Epsilon, polygon approximation precision, the maximum distance between the original curve and the approximation curve.

  • Parameter 4: Closed: indicates whether the curve is closed. True indicates closed; false indicates not closed.

Douglas-peucker algorithm is used in this method. Douglas-peukcer algorithm was proposed by D.Douglas and T. Pueker in 1973, also known as Lamer-Douglas-Puke algorithm, iterative adaptive point algorithm, splitting and merging algorithm, D-P algorithm) is an algorithm that approximates a curve as a series of points and reduces the number of points. It is a classical algorithm for thinning linear elements. Using it to deal with a large number of redundant geometric data points can not only achieve the purpose of simplifying the amount of data, but also preserve the skeleton of geometric shapes to a large extent. Most of the existing line simplification algorithms are improved on the basis of this algorithm. It is characterized by invariance of translation and rotation. Given curve and threshold, the sampling result is certain.

The basic idea of the algorithm is as follows:

To connect the first and last points of each curve with a virtual line, calculate the distance between all points and the line, and find the maximum distance value dmax, and compare dmax with the limit D: If dmax < D, all the intermediate points on the curve are omitted; If Dmax ≥D, the corresponding coordinate point of Dmax is reserved, and the curve is divided into two parts with this point as the boundary, and the method is repeated for these two parts

operation

* author: yidong * 2020/10/7 */
class ContourPolyActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityContourPolyBinding
    private var mSource: Mat = Mat()
    private var mGray: Mat = Mat()
    private var mBinary: Mat = Mat()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_contour_poly)
        mBinding.presenter = this
        val bgr = Utils.loadResource(this, R.drawable.contourpoly)
        Imgproc.cvtColor(bgr, mSource, Imgproc.COLOR_BGR2RGB)
        Imgproc.cvtColor(bgr, mGray, Imgproc.COLOR_BGR2GRAY)
        Imgproc.GaussianBlur(mGray, mGray, Size(5.0.5.0), 2.0.2.0)
        Imgproc.threshold(
            mGray,
            mBinary,
            20.0.255.0,
            Imgproc.THRESH_BINARY or Imgproc.THRESH_OTSU
        )
        mBinding.ivLena.showMat(mBinary)
    }

    fun findRect(flag: Int) {
        val tmp = mSource.clone()
        val contours = mutableListOf<MatOfPoint>()
        val hierarchy = Mat()
        Imgproc.findContours(
            mBinary,
            contours,
            hierarchy,
            Imgproc.RETR_TREE,
            Imgproc.CHAIN_APPROX_SIMPLE
        )

        for (i in 0 until contours.size) {
            when (flag) {
                0 -> {
                    title = "Maximum enclosing rectangle"
                    val rect = Imgproc.boundingRect(contours[i])
                    Imgproc.rectangle(tmp, rect, Scalar(255.0.255.0.0.0), 4, Imgproc.LINE_8)
                }
                1 -> {
                    title = "Minimum enclosing rectangle"
                    val source = MatOfPoint2f()
                    source.fromList(contours[i].toList())
                    val rect = Imgproc.minAreaRect(source)
                    val points = arrayOfNulls<Point>(4)
                    val center = rect.center
                    rect.points(points)
                    Log.d(App.TAG, "RotateRect: ${points.toList()}The Center:$center")
                    for (j in 0.3.) {
                        Imgproc.line(
                            tmp,
                            points[j % 4],
                            points[(j + 1) % 4],
                            Scalar(255.0.255.0.0.0),
                            4,
                            Imgproc.LINE_8
                        )
                    }
                }
                else -> {
                    title = "Outline polygon"
                    val result = MatOfPoint2f()
                    val source = MatOfPoint2f()
                    source.fromList(contours[i].toList())
                    Imgproc.approxPolyDP(source, result, 4.0.true)
                    Log.d(App.TAG, "Poly: ${result.dump()}")
                    val points = result.toArray()
                    for (j in points.indices) {
                        Imgproc.line(
                            tmp,
                            points[j % points.size],
                            points[(j + 1) % points.size],
                            Scalar(255.0.255.0.0.0),
                            4,
                            Imgproc.LINE_8
                        )
                    }
                }
            }
        }
        mBinding.ivResult.showMat(tmp)
        tmp.release()
        hierarchy.release()
    }


    override fun onDestroy(a) {
        mSource.release()
        mGray.release()
        mBinary.release()
        super.onDestroy()
    }
}
Copy the code

The effect

The source code

Github.com/onlyloveyd/…