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}