001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.vision.pipe.impl;
019
020import org.opencv.core.Core;
021import org.opencv.core.Mat;
022import org.opencv.core.Scalar;
023import org.opencv.imgproc.Imgproc;
024import org.photonvision.common.util.numbers.IntegerCouple;
025import org.photonvision.vision.pipe.CVPipe;
026
027public class HSVPipe extends CVPipe<Mat, Mat, HSVPipe.HSVParams> {
028    @Override
029    protected Mat process(Mat in) {
030        var outputMat = new Mat();
031        // We can save a copy here by sending the output of cvtcolor to outputMat directly
032        // rather than copying. Free performance!
033        Imgproc.cvtColor(in, outputMat, Imgproc.COLOR_BGR2HSV, 3);
034
035        if (params.getHueInverted()) {
036            // In Java code we do this by taking an image thresholded
037            // from [0, minHue] and ORing it with [maxHue, 180]
038
039            // we want hue from the end of the slider to max hue
040            Scalar firstLower = params.getHsvLower().clone();
041            Scalar firstUpper = params.getHsvUpper().clone();
042            firstLower.val[0] = params.getHsvUpper().val[0];
043            firstUpper.val[0] = 180;
044
045            var lowerThresholdMat = new Mat();
046            Core.inRange(outputMat, firstLower, firstUpper, lowerThresholdMat);
047
048            // We want hue from 0 to the start of the slider
049            var secondLower = params.getHsvLower().clone();
050            var secondUpper = params.getHsvUpper().clone();
051            secondLower.val[0] = 0;
052            secondUpper.val[0] = params.getHsvLower().val[0];
053
054            // Now that the output mat's been used by the first inRange, it's fine to mutate it
055            Core.inRange(outputMat, secondLower, secondUpper, outputMat);
056
057            // Now OR the two images together to make a mat that combines the lower and upper bounds
058            // outputMat holds the second half of the range
059            Core.bitwise_or(lowerThresholdMat, outputMat, outputMat);
060
061            lowerThresholdMat.release();
062        } else {
063            Core.inRange(outputMat, params.getHsvLower(), params.getHsvUpper(), outputMat);
064        }
065
066        return outputMat;
067    }
068
069    public static class HSVParams {
070        private final Scalar m_hsvLower;
071        private final Scalar m_hsvUpper;
072        private final boolean m_hueInverted;
073
074        public HSVParams(
075                IntegerCouple hue, IntegerCouple saturation, IntegerCouple value, boolean hueInverted) {
076            m_hsvLower = new Scalar(hue.getFirst(), saturation.getFirst(), value.getFirst());
077            m_hsvUpper = new Scalar(hue.getSecond(), saturation.getSecond(), value.getSecond());
078
079            this.m_hueInverted = hueInverted;
080        }
081
082        public Scalar getHsvLower() {
083            return m_hsvLower;
084        }
085
086        public Scalar getHsvUpper() {
087            return m_hsvUpper;
088        }
089
090        public boolean getHueInverted() {
091            return m_hueInverted;
092        }
093    }
094}