Tix
Tix

Reputation: 449

OpenCV: Detect color and draw line on color

My application is going to calculate the bounce height of the battery, I intend to use the blue strip to define the "base" in which i use to calculate the number of pixels away it is from the battery.

How do i detect that blue color and draw a line at the base of the blue strip of paper such that the line drawn could be used for pixel distance calculation?

I'm aware that opencv has a blob detection application that draws contours around a color that was selected, but what I need is the application to automatically detect the color & give me it's co-ordinate such that i can apply

canvas.drawLine(0, 0, 20, 20, p);

to draw the line

Note: the detection & line drawing is done on an bitmap image extracted from a video.

enter image description here

EDIT: When i tested it out, it doesnt detect the blue color. i even tested it out on pictures that has blue and green colored paper, but the output dint detect blue...

Here's my pictures: Outputoutput Inputinput Here's my current code:

Mat hsvMat = new Mat();
            //Mat black_hue_range = new Mat();
            //Core.inRange(hsvMat, new Scalar(0, 0, 0), new Scalar(180, 255, 30, 0), black_hue_range);
            Mat blue_hue = new Mat();
            Scalar lower_blue = new Scalar(110,50,50);
            Scalar upper_blue = new Scalar(130,255,255);

            //Convert BGR to HSV
            Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);

            //Threshold the HSV image to get only blue colors
            Core.inRange(hsvMat, lower_blue, upper_blue, blue_hue); // hue == a colour or shade
            Mat tempMat22 = new Mat();
            Core.bitwise_and(hsvMat,hsvMat,tempMat22,blue_hue);
            Utils.matToBitmap(tempMat22, b);

            //Bitmap mutableBitmap = b.copy(Bitmap.Config.ARGB_8888, true);
            imgR.setImageBitmap(b);

EDIT:

The following code returned three values which i assumed was H = data[0], S data[1], V = data[2] Now that i have the HSV value how do i get the upper and lower limit? The answer given by Alexander Reynolds here seems to be for RGB and not HSV. Note: The color pixel im reading now is Green, not blue anymore.

E/data: H:90.0 S:113.0 V:144.0

if (getIntent().hasExtra("byteArray")) {

            bitmap = BitmapFactory.decodeByteArray(getIntent().getByteArrayExtra("byteArray"), 0, getIntent().getByteArrayExtra("byteArray").length);

            int width= bitmap.getWidth();
            int height=bitmap.getHeight();

            int centerX=width/2;
            int centerY=height/2;
            srcMat = new Mat();
            Utils.bitmapToMat(bitmap, srcMat);
            Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_BGR2HSV);
            srcMat.convertTo(srcMat, CvType.CV_64FC3); //http://answers.opencv.org/question/14961/using-get-and-put-to-access-pixel-values-in-java/
            double[] data = srcMat.get(centerX, centerY);
            Log.e("data", String.valueOf("H:"+data[0]+" S:"+data[1]+" V:"+data[2]));
            Log.e("dlength", String.valueOf(data.length));
            Mat matHSV = new Mat(0,0,CvType.CV_64FC3);

Also by adding the following three lines of code, i'll receive an error saying bitmap == null, so im not really sure if the pixel reading worked or not.

matHSV.put(0,0,data);
Utils.matToBitmap(matHSV, bb);
imgDisplay.setImageBitmap(bb);

EDIT2:

I'm getting an error when trying to specify the roi using Rect:

Caused by: CvException [org.opencv.core.CvException: cv::Exception: /build/master_pack-android/opencv/modules/core/src/matrix.cpp:483: error: (-215) 0 <= _rowRange.start && _rowRange.start <= _rowRange.end && _rowRange.end <= m.rows in function cv::Mat::Mat(const cv::Mat&, const cv::Range&, const cv::Range&)

bitmap = globals.getBmp();
        Mat srcMat = new Mat();
        Utils.bitmapToMat(bitmap, srcMat);

        Mat hsvMat = new Mat();
        Imgproc.cvtColor(srcMat,hsvMat,Imgproc.COLOR_BGR2HSV);

    Mat roiMat;
            Rect rectangle = new Rect(177,1571,822,1680);// 177,1571(top right corner),   820,1680 (btm right) 820, 1565(topright)
            roiMat = new Mat(hsvMat,rectangle);
            Utils.matToBitmap(roiMat, temp);

            ImageView imageView = (ImageView) findViewById(R.id.imageView);
            imageView.setImageBitmap(temp);

I've also tried using Range:

 Range rowRange = new Range(177, 822);
        Range colRange = new Range(1571, 1680);
        roiMat = new Mat(hsvMat, rowRange, colRange); // public Mat(Mat m, Range rowRange, Range colRange)

EDIT2.5:

changing:

roiMat = new Mat(hsvMat, rowRange, colRange);

to:

roiMat = new Mat(hsvMat, colRange, rowRange); 

seemed to have fixed the issue, but now it's saying my bmp

java.lang.IllegalArgumentException: bmp == null

EDIT 3: Finally managed to convert the python code answered by Alexander Reynolds, But i cant seem to view the result as I'm getting an error:

java.lang.IllegalArgumentException: bmp == null

at

Utils.matToBitmap(idk,temp);

bitmap = cn.getBmp();
    Mat srcMat = new Mat();
    Utils.bitmapToMat(bitmap, srcMat);

    Mat hsvMat = new Mat();
    Imgproc.cvtColor(srcMat,hsvMat,Imgproc.COLOR_BGR2HSV);

    Mat roiMat;
    Rect rectangle = new Rect(177,1571,822,1680);// 177,1571(top right corner),   820,1680 (btm right) 820, 1565(topright)
    Range rowRange = new Range(177, 822);
    Range colRange = new Range(1571, 1680);
    roiMat = new Mat(hsvMat, colRange, rowRange); // public Mat(Mat m, Range rowRange, Range colRange)

    MatOfDouble mu = new MatOfDouble();
    MatOfDouble sig = new MatOfDouble();

    Core.meanStdDev(roiMat,mu,sig);


    double m = mu.get(0,0)[0];
    double d = sig.get(0,0)[0];
    int a = 9;
    Log.e("m , d", "m "+String.valueOf(m)+" d"+String.valueOf(d));
    Mat blue_mask = new Mat();
    Core.inRange(hsvMat, new Scalar(m-a*d), new Scalar(m+a*d), blue_mask); // javadoc: inRange(src, lowerb, upperb, dst)
    Mat idk = new Mat();
    Core.bitwise_and(hsvMat,hsvMat,idk,blue_mask);
    Utils.matToBitmap(idk,temp);
    Bitmap mutableBitmap = temp.copy(Bitmap.Config.ARGB_8888, true);

Upvotes: 3

Views: 3127

Answers (1)

alkasm
alkasm

Reputation: 23012

You can perform color filtering with the built-in OpenCV method inRange() which will allow you to create a mask containing white wherever any pixel values fall between a defined lower bound and upper bound, and black otherwise. From there you can simply find the location of the white pixels in the mask.

Refer to this tutorial for an example.

Also, my previous answer here gives some suggestions for finding good lower and upper bounds---in particular, find an area in an image you know (like the one linked) with the color you want, and find the mean and standard deviation of those values (in any colorspace, but I would probably suggest starting with HSV or HLS). Then you can easily set the lower bound to mean-a*stddev and upper bound to mean+b*stddev where a and b are some values you can experiment with to see what works best at selecting the blue (and only the blue). You can start with a=b and use integer values for them (1, 2, 3, ...) and hone in from there.

Once you have the mask, it's likely you'll have a few holes in it or extraneous white pixels elsewhere in the image. You can use contour detection, Hough line detection, or blob detection to get the correct rectangle. In this case, I would suggest using contour detection on the mask with findContours(), find the largest contour, find the boundingRect around it, and that will give you the pixel locations directly.


Python example

You're working in C++ but Python is faster for giving examples, so you'll have to translate. I'll use a resized version of your first image:

Starting image

import numpy as np
import cv2

img = cv2.imread('image.jpg')
img = cv2.resize(img, None, fx=0.1, fy=0.1)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
roi = hsv[430:450, 20:170]

Here I'm just resizing the image (mostly so I can display it easily), converting the colorspace, and defining a region of interest (ROI) that only includes blue pixels. In BGR, the ROI looks like this:

Blue ROI

This ROI contains only blue pixels, so I can find the mean blue value, and the standard deviation of the blue values to use as the values for inRange().

mu, sig = cv2.meanStdDev(roi)
a = 9

blue_mask = cv2.inRange(hsv, mu-a*sig, mu+a*sig)

Thus we have a mask of just the blue values:

Blue mask

From here you can do your normal contour detection and find the bounding box around it:

_, contours, _ = cv2.findContours(blue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)

And now we have a bounding rectangle (we have the location of the upper left corner, and the width and height) around the litmus paper in the original image:

Detected blue

Upvotes: 4

Related Questions