2

I have the following class:

    public class ThresholdHSV {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    public static void main(String[] args) {
        Mat image = Imgcodecs.imread("src/playground/input.png");
        Mat hsv = new Mat();
        Imgproc.cvtColor(image, hsv, COLOR_BGR2HSV);

        int minHue = 168;
        int maxHue = 180;
        int minSaturation = 130;
        int maxSaturation = 220;
        int minValue = 120;
        int maxValue = 220;

        Mat mask = new Mat();
        Core.inRange(hsv, new Scalar(minHue, minSaturation, minValue), new Scalar(maxHue, maxSaturation, maxValue), mask);

        Mat grey = new Mat();
        Imgproc.cvtColor(image, grey, COLOR_BGR2GRAY);

        Mat result = new Mat();
        grey.copyTo(result, mask);
        image.copyTo(result, mask);

        Imgcodecs.imwrite("src/playground/output.png", result);
    }
}

I would like to keep all the pixels of the image that have HSV values between the min and max provided values and set the rest to greyScale. To do so I am using OpenCV, I was able to keep all the pixels within the defined ranges, but the rest are all set to black.

Here is what it looks like :

Before:

enter image description here

And here is after executing the main method :

enter image description here

This is a link to a question I posted before containing images of the result I want to achieve, where I used brute-force-image-processing.

Why am I getting the rest of the pixels in black, and how to correct it?

Starnec
  • 551
  • 1
  • 5
  • 15

1 Answers1

1

Here is a nice solution implemented in MATLAB.
Here is my answer using Python and OpenCV (translation of the original MATLAB code).
In JAVA there are no vectorized matrix operations, so the solution relies on OpenCV.

Since the solution is based on the Python implementation, it finds all the non-red pixels (instead of finding the red pixels).
The HSV ranges of the non-red pixels are taken from the original solution:

int minHue = 21;
int maxHue = 169; //340/2-1

Saturation and Value includes the full range of [0, 255].


Important modification from your posted code:

grey image should be in BGR (3 channels) format before using grey.copyTo(result, mask), because the destination image result has 3 color channels.

We may convert grey to BGR, and than using copyTo:

Mat grey_as_bgr = new Mat();
Imgproc.cvtColor(grey, grey_as_bgr, Imgproc.COLOR_GRAY2BGR); //Convert from Gray to BGR where R=G=B (we need 3 color channels).
grey_as_bgr.copyTo(result, mask);

JAVA code sample:

package myproject;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgcodecs.Imgcodecs;

class Sample {

static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }
     
public static void main(String[] args) {
    //The following JAVA code is partly based on the following Python and OpenCV code:
    //Python code (my answer): https://stackoverflow.com/a/71542681/4926757
    //The Python code is a conversion from the original MATLAB code:
    //Original MATLAB code: https://stackoverflow.com/questions/4063965/how-can-i-convert-an-rgb-image-to-grayscale-but-keep-one-color
 
    Mat image = Imgcodecs.imread("src/playground/input.png");  //Read input image //img = cv2.imread('src/playground/input.png')
    Mat hsv = new Mat();
    Imgproc.cvtColor(image, hsv, Imgproc.COLOR_BGR2HSV); //Convert the image to HSV color space. //hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    //Instead of finding red pixels, find all the non-red pixels.
    //Note: in OpenCV hue range is [0,179]) The original MATLAB code is 360.*hsvImage(:, :, 1), when hue range is [0, 1].
    int minHue = 21;  //non_red_idx = (h > 20//2) & (h < 340//2)  # Select "non-red" pixels (divide the original MATLAB values by 2 due to the range differences).
    int maxHue = 169; //340/2-1;
    int minSaturation = 0;
    int maxSaturation = 255;
    int minValue = 0;
    int maxValue = 255;    
    
    //Create a mask of all non-red pixels
    Mat mask = new Mat();
    Core.inRange(hsv, new Scalar(minHue, minSaturation, minValue), new Scalar(maxHue, maxSaturation, maxValue), mask);
    
    Mat grey = new Mat();
    Mat grey_as_bgr = new Mat();
    Imgproc.cvtColor(image, grey, Imgproc.COLOR_BGR2GRAY); //Convert image from BGR to Grey
    Imgproc.cvtColor(grey, grey_as_bgr, Imgproc.COLOR_GRAY2BGR); //Convert from Gray to BGR where R=G=B (we need 3 color channels).
    
    Mat result = image.clone();  //Clone image, and store in result
        
    grey_as_bgr.copyTo(result, mask);  //Copy the non-red pixels from grey_as_bgr to result (the red pixels are kept unmodified).
    
    Imgcodecs.imwrite("src/playground/output.png", result); //Save the result
}

}

Output:
enter image description here

Rotem
  • 30,366
  • 4
  • 32
  • 65
  • Thank you for the answer. I understood the approach and the reason behind it since Java does not have **vectorized matrix operations**. But there are 2 issues that I spotted: 1) What if I select a ***hue*** value with some max and min values in the middle of the **[0-179]** range for example between 40 and 50 (**non-red**) color)? 2) The approach shows that I can only isolate hue that starts from 0 through n and keep all the **[n+1,179]** values (issue 1) but also all the shades of that color (in your example every shade of red was kept). – Starnec Feb 11 '23 at 15:15
  • The reason why I consider these as issues is that I am using sliders to set the min and max of HSV values and updating the image in real-time based on those values. – Starnec Feb 11 '23 at 15:15
  • 1
    The reason I choose non-red pixels is because the red rane in H is splitted. If you choose mask of the red pixels, initialize result to grey_as_bgr and use copyTo from image (copy the red to the grey) – Rotem Feb 11 '23 at 17:30