preface

Wrote two basic, a little water, but will continue to write, after all, the foundation is solid is not it, before continuing to water to a dry goods, based on OpenCV shape recognition.

The body of the

The above firstThis picture of the background color is white, above has a different color graphics, with the red rectangle, for example, but now we first pretended not to know it’s a rectangle, only know that he is red a graphics, in order to identify shapes first to find the graphics, the grams of the question now is how to found in the white as the background picture of the red graphics?

Well, since we already said it’s a red figure, why don’t we just look for red?

Theory exists, practice begins

Note: since this is a case study, there will be no detailed introduction to the method, but don’t worry, it will be covered in a later foundation article!

  1. Start by importing images as usual
        try {
            srcMat = Utils.loadResource(this, R.drawable.shape);
        } catch (IOException e) {
            e.printStackTrace();
        }
Copy the code

Color space

Next, we are going to do some processing on the picture. First, we will briefly introduce the concept of color space, and then we will write a separate article to explain it in detail. Here is a brief summary:

RGB

RGB: is the color space we most often hear and use. Based on R(Red), G(Green) and B(Blue), it is superimposed to different degrees to produce rich and extensive colors, so it is commonly known as the three primary color mode. The biggest advantage of RGB color space is that it is intuitive and easy to understand. The disadvantage is that R,G and B components are highly correlated, that is, if one component of a color changes to a certain extent, the color is likely to change. Human eyes have different sensitivity to the common red, green and blue colors, so the uniformity of RGB color space is very poor, and the perceptual difference between two colors can not be expressed as the distance between two points in the color space. (PS: copied from Baidu)

It can be seen that RGB is not suitable for describing a broad color range, so we will introduce our HSV color space.

HSV

HSV(Hue, Saturation, Value) is A color space created by A. R. Smith in 1978 based on the intuitive characteristics of colors. It is also known as the Hexcone Model.

H(tone): Measured by Angle, the value ranges from 0° to 360°. Starting from red, the value is 0°, green is 120°, and blue is 240°. Their complementary colors are: yellow for 60°, cyan for 180°, purple for 300°;

S(saturation): Saturation S indicates the degree to which the color approximates the spectral color. A color that can be seen as the result of a certain spectral color mixed with white. The larger the proportion of spectral color, the higher the degree of color close to spectral color, the higher the saturation of color. High saturation, deep and bright color. The white component of spectral color is 0 and the saturation is the highest. The value ranges from 0% to 100%. A larger value indicates a more saturated color.

V(brightness): brightness indicates the brightness of the color. For the color of the light source, the brightness value is related to the brightness of the illuminator. For object color, this value is related to the transmittance or reflectance of the object. The value usually ranges from 0% (black) to 100% (white).

It can be seen that we can easily use H in HSV to represent a color. Here is a diagram of the range of HSV color components found on the Internet:

We will also use HSV color Spaces in our code

By the way, the Bitmap color space in Android is not RGB, but BGR

  1. We’re throughcvtColor(), from the sourcesrcMatThe BGR color space is converted into HSV color space for storagehsvMatIn the
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
Copy the code
  1. inRange(), turns the area within the specified color threshold to white and the rest to black, as shown in the following codehsvMatThe medium red parts turn white, and the rest turn black and coexistbinaryMatIn the.
                Core.inRange(hsvMat, new Scalar(156.43.46), new Scalar(180.255.255), binaryMat);
Copy the code

Ok, so let’s just write this to a button and print it out and see what happens

Sure enough, we found the red zone we were looking for

  1. findContours(): As the name implies this method can help us find the contours we need to binarize the graphbinaryMatPassed in, the method stores the outline into aMatofPointThe list ofcontours,RETR_EXTERNALRepresents monitoring all profiles,CV_CHAIN_APPROX_SIMPLEIndicates that only the inflection point information is saved for the contour, and there is also a save contour levelhierarchy:
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
Copy the code
  1. So once we have the outline, let’s just draw the outline for our convenience, right hereresultMatIn fact, issrcMatFor the purpose of distinguishingMat resultMat = srcMat.clone();, the following code means that thecontoursAll contours in the outline list (-1 means all contours) are in,resultMatPaint in black (new Scalar(0, 0, 0)) Draw lines of 10 in thickness.
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
Copy the code

The effect is as follows:

  1. Once we find the outline, we need to use itapproxPolyDP()To fit the contour:

What is a fit?

The operation in this step is to turn a smooth curve into a polygon. The degree of fitting can be controlled by adjusting the threshold value. Simply speaking, the larger the field value, the fewer the edges. Of course, less is better. Here we use a formula, which will be explained in a later basic. For now, all you need to know is that Epsilon is our threshold, which we can adjust by changing 0.04;

epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
Copy the code

Contours.get (1) Since we only have one contour here, we can just type 0. With this operation, the set of vertices of the fitted polygon is saved into approxCurve, which is actually a two-dimensional array, The number of rows in this array is equal to the number of vertices in the polygon

                    contours2f = new MatOfPoint2f(contours.get(0).toArray());
                    epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                    approxCurve = new MatOfPoint2f();
                    Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
Copy the code
  1. A triangle is three vertices, a rectangle is four vertices, a pentagon is five, a star is ten, and a circle is greater than five but not equal to ten. The result is the following code:
                    if (approxCurve.rows() == 3) {
                        tri++;
                    }
                    if (approxCurve.rows() == 4) {
                        rect++;
                    }
                    if (approxCurve.rows() == 5) {
                        pentagon++;
                    }
                    if (approxCurve.rows() == 10) {
                        star++;
                    }
                    if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                        circle++;
                    }
Copy the code

Let’s see how it works:


Ok, so he’s done with the shape, and all the other colors, and so on. At this point you might say can we identify all the shapes at once? Of course, we can, at first we recognize the red shape, to identify all the shapes just need to change the red to white, and then invert the binary graph, as follows:

The complete code

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private String TAG = "MainActivity";

    private Button redBtn, blueBtn, greenBtn, yellowBtn, cyanBtn, allBtn;

    private ImageView iv_dst;

    private Bitmap resultBitmap;

    private Mat srcMat, hsvMat;

    private List<MatOfPoint> contours;
    private MatOfPoint2f contours2f, approxCurve;
    private int contoursSize;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        OpenCVInit();

        redBtn = findViewById(R.id.btn_red);
        blueBtn = findViewById(R.id.btn_blue);
        greenBtn = findViewById(R.id.btn_green);
        yellowBtn = findViewById(R.id.btn_yellow);
        cyanBtn = findViewById(R.id.btn_cyan);
        allBtn = findViewById(R.id.btn_all);
        iv_dst = findViewById(R.id.iv_dst);

        try {
            srcMat = Utils.loadResource(this, R.drawable.shape);
        } catch (IOException e) {
            e.printStackTrace();
        }

        redBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                Mat hierarchy = new Mat();
                Mat binaryMat = new Mat();
                Mat resultMat = srcMat.clone();
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(156.43.46), new Scalar(180.255.255), binaryMat);
                resultBitmap = Bitmap.createBitmap(hsvMat.width(), hsvMat.height(), Bitmap.Config.ARGB_8888);
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                double epsilon;
                contours2f = new MatOfPoint2f(contours.get(0).toArray());
                epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                approxCurve = new MatOfPoint2f();
                Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                if (approxCurve.rows() == 3) {
                    tri++;
                }
                if (approxCurve.rows() == 4) {
                    rect++;
                }
                if (approxCurve.rows() == 5) {
                    pentagon++;
                }
                if (approxCurve.rows() == 10) {
                    star++;
                }
                if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                    circle++;
                }

                Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR);
                Utils.matToBitmap(resultMat, resultBitmap);
                iv_dst.setImageBitmap(resultBitmap);
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t");
                Core.inRange(hsvMat, new Scalar(100.43.46), new Scalar(124.255.255), hsvMat); }}); greenBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                Mat hierarchy = new Mat();
                Mat binaryMat = new Mat();
                Mat resultMat = srcMat.clone();
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(35.43.46), new Scalar(77.255.255), binaryMat);
                resultBitmap = Bitmap.createBitmap(hsvMat.width(), hsvMat.height(), Bitmap.Config.ARGB_8888);
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                double epsilon;
                contours2f = new MatOfPoint2f(contours.get(0).toArray());
                epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                approxCurve = new MatOfPoint2f();
                Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                if (approxCurve.rows() == 3) {
                    tri++;
                }
                if (approxCurve.rows() == 4) {
                    rect++;
                }
                if (approxCurve.rows() == 5) {
                    pentagon++;
                }
                if (approxCurve.rows() == 10) {
                    star++;
                }
                if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                    circle++;
                }

                Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR);
                Utils.matToBitmap(resultMat, resultBitmap);
                iv_dst.setImageBitmap(resultBitmap);
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t"); }}); blueBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                Mat hierarchy = new Mat();
                Mat binaryMat = new Mat();
                Mat resultMat = srcMat.clone();
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(100.43.46), new Scalar(124.255.255), binaryMat);
                resultBitmap = Bitmap.createBitmap(hsvMat.width(), hsvMat.height(), Bitmap.Config.ARGB_8888);
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                double epsilon;
                contours2f = new MatOfPoint2f(contours.get(0).toArray());
                epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                approxCurve = new MatOfPoint2f();
                Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                if (approxCurve.rows() == 3) {
                    tri++;
                }
                if (approxCurve.rows() == 4) {
                    rect++;
                }
                if (approxCurve.rows() == 5) {
                    pentagon++;
                }
                if (approxCurve.rows() == 10) {
                    star++;
                }
                if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                    circle++;
                }

                Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR);
                Utils.matToBitmap(resultMat, resultBitmap);
                iv_dst.setImageBitmap(resultBitmap);
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t"); }}); yellowBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                Mat hierarchy = new Mat();
                Mat binaryMat = new Mat();
                Mat resultMat = srcMat.clone();
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(26.43.46), new Scalar(34.255.255), binaryMat);
                resultBitmap = Bitmap.createBitmap(hsvMat.width(), hsvMat.height(), Bitmap.Config.ARGB_8888);
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                double epsilon;
                contours2f = new MatOfPoint2f(contours.get(0).toArray());
                epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                approxCurve = new MatOfPoint2f();
                Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                if (approxCurve.rows() == 3) {
                    tri++;
                }
                if (approxCurve.rows() == 4) {
                    rect++;
                }
                if (approxCurve.rows() == 5) {
                    pentagon++;
                }
                if (approxCurve.rows() == 10) {
                    star++;
                }
                if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                    circle++;
                }

                Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR);
                Utils.matToBitmap(resultMat, resultBitmap);
                iv_dst.setImageBitmap(resultBitmap);
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t"); }}); cyanBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                Mat hierarchy = new Mat();
                Mat binaryMat = new Mat();
                Mat resultMat = srcMat.clone();
                hsvMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(78.43.46), new Scalar(99.255.255), binaryMat);
                resultBitmap = Bitmap.createBitmap(hsvMat.width(), hsvMat.height(), Bitmap.Config.ARGB_8888);
                Imgproc.findContours(binaryMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                double epsilon;
                contours2f = new MatOfPoint2f(contours.get(0).toArray());
                epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                approxCurve = new MatOfPoint2f();
                Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                if (approxCurve.rows() == 3) {
                    tri++;
                }
                if (approxCurve.rows() == 4) {
                    rect++;
                }
                if (approxCurve.rows() == 5) {
                    pentagon++;
                }
                if (approxCurve.rows() == 10) {
                    star++;
                }
                if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                    circle++;
                }

                Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR);
                Utils.matToBitmap(resultMat, resultBitmap);
                iv_dst.setImageBitmap(resultBitmap);
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t"); }}); allBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tri = 0;
                int rect = 0;
                int circle = 0;
                int star = 0;
                int pentagon = 0;
                contours = new ArrayList<>();
                hsvMat = new Mat();
                Mat resultMat = srcMat.clone();
                Mat binaryMat = new Mat();
                Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);
                Core.inRange(hsvMat, new Scalar(0.0.221), new Scalar(180.30.255), binaryMat);
                Core.bitwise_not(binaryMat, binaryMat);
                resultBitmap = Bitmap.createBitmap(binaryMat.width(), binaryMat.height(), Bitmap.Config.ARGB_8888);

                Mat outMat = new Mat();
                Imgproc.findContours(binaryMat, contours, outMat, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
                contoursSize = contours.size();
                double epsilon;
                Imgproc.drawContours(resultMat, contours, -1.new Scalar(0.0.0), 10);
                for (int i = 0; i < contoursSize; i++) {
                    contours2f = new MatOfPoint2f(contours.get(i).toArray());
                    epsilon = 0.04 * Imgproc.arcLength(contours2f, true);
                    approxCurve = new MatOfPoint2f();
                    Imgproc.approxPolyDP(contours2f, approxCurve, epsilon, true);
                    if (approxCurve.rows() == 3) {
                        tri++;
                    }
                    if (approxCurve.rows() == 4) {
                        rect++;
                    }
                    if (approxCurve.rows() == 5) {
                        pentagon++;
                    }
                    if (approxCurve.rows() == 10) {
                        star++;
                    }
                    if (approxCurve.rows() > 5&& approxCurve.rows() ! =10) {
                        circle++;
                    }
                }
                Log.d(TAG, "Triangle :" + tri + "\t" + "Rectangle." + rect + "\t" + "Pentagon :" + pentagon + "\t" + "Star." + star + "\t" + "Circle." + circle + "\t"); Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_RGB2BGR); Utils.matToBitmap(resultMat, resultBitmap); iv_dst.setImageBitmap(resultBitmap); }}); }private void OpenCVInit(a) {
        boolean success = OpenCVLoader.initDebug();
        if (success) {
            Log.d(TAG, "OpenCV Lode success");
        } else {
            Log.d(TAG, "OpenCV Lode failed "); }}}Copy the code

activity_main.xml


      
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_red"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RED"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btn_green"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.584" />

    <ImageView
        android:id="@+id/iv_dst"
        android:layout_width="320dp"
        android:layout_height="174dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.194"
        app:srcCompat="@drawable/shape" />

    <Button
        android:id="@+id/btn_blue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BLUE"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/btn_green"
        app:layout_constraintTop_toTopOf="@+id/btn_red" />

    <Button
        android:id="@+id/btn_green"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="green"
        app:layout_constraintTop_toTopOf="@+id/btn_red"
        app:layout_constraintStart_toEndOf="@id/btn_red"
        app:layout_constraintEnd_toStartOf="@id/btn_blue"
        tools:layout_editor_absoluteX="190dp" />

    <Button
        android:id="@+id/btn_yellow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="yellow"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="@+id/btn_red"
        app:layout_constraintTop_toBottomOf="@+id/btn_red"
        app:layout_constraintVertical_bias="0.1" />

    <Button
        android:id="@+id/btn_cyan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cyan"
        app:layout_constraintStart_toStartOf="@+id/btn_green"
        app:layout_constraintTop_toTopOf="@+id/btn_yellow" />

    <Button
        android:id="@+id/btn_all"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="all"
        app:layout_constraintStart_toStartOf="@+id/btn_blue"
        app:layout_constraintTop_toTopOf="@+id/btn_cyan" />

</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

Case diagram