3

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);
Tix
  • 449
  • 1
  • 8
  • 27

1 Answers1

4

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

Community
  • 1
  • 1
alkasm
  • 22,094
  • 5
  • 78
  • 94
  • Hey, @Alexander Reynolds, I've converted the image to HSV and used inRange to detect the blue color, but the output doesnt seem like its correct. I've editing the question to reflect the changes ive made – Tix Aug 04 '17 at 06:26
  • @Tix hard to be sure what the problem is...how did you come up with your values for the blue color exactly? – alkasm Aug 04 '17 at 06:34
  • Hey,@Alexander Reynolds I found it on [opencv's tutorial on colorpsace](http://docs.opencv.org/3.1.0/df/d9d/tutorial_py_colorspaces.html) – Tix Aug 04 '17 at 06:44
  • @Tix well you can't just copy their same blue values, they're defining a certain pixel range and your blue is far different from theirs. Instead try cropping just the blue part and examining what the values are. As I mentioned, you can calculate the mean and standard deviation of all the blue pixels for a good range of values. – alkasm Aug 04 '17 at 06:45
  • I think I've finally managed to get the HSV value for the pixel, but im not quite sure how to go about calculating the lower and upper bounds as im not quite sure what `mean-a*stddev` means... I've updated the question with my recent codes. – Tix Aug 07 '17 at 06:46
  • @Tix I was referring to the standard distribution, which is often abbreviated as stddev. You can think of it as a statistical measure of how "far" an average pixel is from the mean value. You can calculate the mean and standard deviation of a Mat with the OpenCV function meanStdDev(). In my answer the value a is just a multiplier so that you can include a larger range of values. I just edited with a Python example using your image. – alkasm Aug 07 '17 at 08:04
  • How do you find the location values `roi = hsv[430:450, 20:170]` of the region of interest? and what does `430:450, 20:170` mean? I'm sorry, im relatively new to programming – Tix Aug 07 '17 at 08:53
  • @Tix I hard programmed those values in. The point is to get a section of the image with blue values corresponding to the correct color as they appear in your images. The colons are "slice" notation in Python; it just means the region of interest goes from row 430 to 450, and columns 20 to 170. Look up how to select a region of interest, there are many tutorials. – alkasm Aug 08 '17 at 00:01
  • Hey @Alexander Reynolds, I'm really sorry to bother you like this, but after doing the calculations and applying it to a Mat, converting the mat to bitmap causes a `Utils.matToBitmap(idk,temp);` – Tix Aug 08 '17 at 06:14
  • @Tix I have no idea what that means and that's not an error message. If you have specific problems in a certain task, look up the documentation and if you cannot find your answer, post a new question. I gave a working pipeline to the original problem. – alkasm Aug 08 '17 at 06:50