简体   繁体   中英

OpenCV object detection contour position

I've used openCV library to detect objects of specific colours. The detection of the colour can be changed by playing around with the saturation and hue. My issue is to get the x and y position of the contours that are shown in the view.

Consider the following image. I need to get the position of the yellow lined contours.

Code:

public class ObjRecognitionController {
// FXML camera button
@FXML
private Button cameraButton;
// the FXML area for showing the current frame
@FXML
private ImageView originalFrame;
// the FXML area for showing the mask
@FXML
private ImageView maskImage;
// the FXML area for showing the output of the morphological operations
@FXML
private ImageView morphImage;
// FXML slider for setting HSV ranges
@FXML
private Slider hueStart;
@FXML
private Slider hueStop;
@FXML
private Slider saturationStart;
@FXML
private Slider saturationStop;
@FXML
private Slider valueStart;
@FXML
private Slider valueStop;
// FXML label to show the current values set with the sliders
@FXML
private Label hsvCurrentValues;

// a timer for acquiring the video stream
private ScheduledExecutorService timer;
// the OpenCV object that performs the video capture
private VideoCapture capture = new VideoCapture();
// a flag to change the button behavior
private boolean cameraActive;

// property for object binding
private ObjectProperty<String> hsvValuesProp;

/**
 * The action triggered by pushing the button on the GUI
 */
@FXML
private void startCamera()
{
    // bind a text property with the string containing the current range of
    // HSV values for object detection
    hsvValuesProp = new SimpleObjectProperty<>();
    this.hsvCurrentValues.textProperty().bind(hsvValuesProp);

    // set a fixed width for all the image to show and preserve image ratio
    this.imageViewProperties(this.originalFrame, 400);
    this.imageViewProperties(this.maskImage, 200);
    this.imageViewProperties(this.morphImage, 200);

    if (!this.cameraActive)
    {
        // start the video capture
        this.capture.open(0);

        // is the video stream available?
        if (this.capture.isOpened())
        {
            this.cameraActive = true;

            // grab a frame every 33 ms (30 frames/sec)
            Runnable frameGrabber = new Runnable() {

                @Override
                public void run()
                {
                    Image imageToShow = grabFrame();
                    originalFrame.setImage(imageToShow);
                }
            };

            this.timer = Executors.newSingleThreadScheduledExecutor();
            this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);

            // update the button content
            this.cameraButton.setText("Stop Camera");
        }
        else
        {
            // log the error
            System.err.println("Failed to open the camera connection...");
        }
    }
    else
    {
        // the camera is not active at this point
        this.cameraActive = false;
        // update again the button content
        this.cameraButton.setText("Start Camera");

        // stop the timer
        try
        {
            this.timer.shutdown();
            this.timer.awaitTermination(33, TimeUnit.MILLISECONDS);
        }
        catch (InterruptedException e)
        {
            // log the exception
            System.err.println("Exception in stopping the frame capture, trying to release the camera now... " + e);
        }

        // release the camera
        this.capture.release();
    }
}

/**
 * Get a frame from the opened video stream (if any)
 * 
 * @return the {@link Image} to show
 */
private Image grabFrame()
{
    // init everything
    Image imageToShow = null;
    Mat frame = new Mat();

    // check if the capture is open
    if (this.capture.isOpened())
    {
        try
        {
            // read the current frame
            this.capture.read(frame);

            // if the frame is not empty, process it
            if (!frame.empty())
            {
                // init
                Mat blurredImage = new Mat();
                Mat hsvImage = new Mat();
                Mat mask = new Mat();
                Mat morphOutput = new Mat();

                // remove some noise
                Imgproc.blur(frame, blurredImage, new Size(7, 7));

                // convert the frame to HSV
                Imgproc.cvtColor(blurredImage, hsvImage, Imgproc.COLOR_BGR2HSV);

                // get thresholding values from the UI
                // remember: H ranges 0-180, S and V range 0-255
                Scalar minValues = new Scalar(this.hueStart.getValue(), this.saturationStart.getValue(),
                        this.valueStart.getValue());
                Scalar maxValues = new Scalar(this.hueStop.getValue(), this.saturationStop.getValue(),
                        this.valueStop.getValue());

                // show the current selected HSV range
                String valuesToPrint = "Hue range: " + minValues.val[0] + "-" + maxValues.val[0]
                        + "\tSaturation range: " + minValues.val[1] + "-" + maxValues.val[1] + "\tValue range: "
                        + minValues.val[2] + "-" + maxValues.val[2];
                this.onFXThread(this.hsvValuesProp, valuesToPrint);

                // threshold HSV image to select tennis balls
                Core.inRange(hsvImage, minValues, maxValues, mask);
                // show the partial output
                this.onFXThread(this.maskImage.imageProperty(), this.mat2Image(mask));

                // morphological operators
                // dilate with large element, erode with small ones
                Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(24, 24));
                Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(12, 12));

                Imgproc.erode(mask, morphOutput, erodeElement);
                Imgproc.erode(mask, morphOutput, erodeElement);

                Imgproc.dilate(mask, morphOutput, dilateElement);
                Imgproc.dilate(mask, morphOutput, dilateElement);

                // show the partial output
                this.onFXThread(this.morphImage.imageProperty(), this.mat2Image(morphOutput));

                // find the tennis ball(s) contours and show them
                frame = this.findAndDrawBalls(morphOutput, frame);

                // convert the Mat object (OpenCV) to Image (JavaFX)
                imageToShow = mat2Image(frame);
            }

        }
        catch (Exception e)
        {
            // log the (full) error
            System.err.print("ERROR");
            e.printStackTrace();
        }
    }

    return imageToShow;
}

/**
 * Given a binary image containing one or more closed surfaces, use it as a
 * mask to find and highlight the objects contours
 * 
 * @param maskedImage
 *            the binary image to be used as a mask
 * @param frame
 *            the original {@link Mat} image to be used for drawing the
 *            objects contours
 * @return the {@link Mat} image with the objects contours framed
 */
private Mat findAndDrawBalls(Mat maskedImage, Mat frame) {
    // init
    List<MatOfPoint> contours = new ArrayList<>();
    Mat hierarchy = new Mat();
    // find contours
    Imgproc.findContours(maskedImage, contours, hierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);

    // if any contour exist...
    if (hierarchy.size().height > 0 && hierarchy.size().width > 0) {
        // for each contour, display it in yellow
        for (int idx = 0; idx >= 0; idx = (int) hierarchy.get(0, idx)[0]) {
            Imgproc.drawContours(frame, contours, idx, new Scalar(0, 255, 255));
        }
    }

    return frame;
}

/**
 * Set typical {@link ImageView} properties: a fixed width and the
 * information to preserve the original image ration
 * 
 * @param image
 *            the {@link ImageView} to use
 * @param dimension
 *            the width of the image to set
 */
private void imageViewProperties(ImageView image, int dimension) {
    // set a fixed width for the given ImageView
    image.setFitWidth(dimension);
    // preserve the image ratio
    image.setPreserveRatio(true);
}

/**
 * Convert a {@link Mat} object (OpenCV) in the corresponding {@link Image}
 * for JavaFX
 * 
 * @param frame
 *            the {@link Mat} representing the current frame
 * @return the {@link Image} to show
 */
private Image mat2Image(Mat frame) {
    // create a temporary buffer
    MatOfByte buffer = new MatOfByte();
    // encode the frame in the buffer, according to the PNG format
    Imgcodecs.imencode(".png", frame, buffer);
    // build and return an Image created from the image encoded in the
    // buffer
    return new Image(new ByteArrayInputStream(buffer.toArray()));
}

/**
 * Generic method for putting element running on a non-JavaFX thread on the
 * JavaFX thread, to properly update the UI
 * 
 * @param property
 *            a {@link ObjectProperty}
 * @param value
 *            the value to set for the given {@link ObjectProperty}
 */
private <T> void onFXThread(final ObjectProperty<T> property, final T value)
{
    Platform.runLater(new Runnable() {

        @Override
        public void run()
        {
            property.set(value);
        }
    });
}

}

You can get the bounded rectangle with boundingRect() function of OpenCV

Rect rect = Imgproc.boundingRect(contours.get(idx));

Now you can get the x and y positions by rect.x and rect.y

Then you can draw the rect on image mat

Imgproc.rectangle(mat, rect.tl(), rect.br(), color, THICKNESS=1 or 2 ...);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM