/*
* ShootOFF - Software for Laser Dry Fire Training
* Copyright (C) 2016 phrack
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.shootoff.camera.shotdetection;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import org.opencv.core.Mat;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.shootoff.camera.shot.ShotColor;
public class PixelCluster extends HashSet<Pixel> {
private static final Logger logger = LoggerFactory.getLogger(PixelCluster.class);
private static final boolean debugColorsToFile = false;
private static final long serialVersionUID = 1L;
public double centerPixelX;
public double centerPixelY;
private final static double CURRENT_COLOR_BIAS_MULTIPLIER = .8;
// We ignore fully connected pixels because they are not on the edges
private final static int MAXIMUM_CONNECTEDNESS = 8;
// We collect all the pixels AROUND the detected shot
// Usually the pixels in the shot are max brightness which are biased green
// So we look around the shot instead
@SuppressWarnings("unused")
public int getColorDifference(final Mat workingFrame, final int[][] colorDistanceFromRed) {
Mat traceMat = null;
if (logger.isTraceEnabled() && debugColorsToFile) {
traceMat = Mat.zeros(workingFrame.size(), workingFrame.type());
}
final Map<Pixel, byte[]> visited = new HashMap<>();
int avgSaturation = 0;
int avgLum = 0;
for (final Pixel pixel : this) {
if (pixel.getConnectedness() < MAXIMUM_CONNECTEDNESS) {
for (int h = -1; h <= 1; h++) {
for (int w = -1; w <= 1; w++) {
if (h == 0 && w == 0) continue;
final int rx = pixel.x + w;
final int ry = pixel.y + h;
if (rx < 0 || ry < 0 || rx >= workingFrame.cols() || ry >= workingFrame.rows()) continue;
final Pixel nearPoint = new Pixel(rx, ry);
// && !this.contains(nearPoint)
if (!visited.containsKey(nearPoint)) {
final byte[] np = { 0, 0, 0 };
workingFrame.get(ry, rx, np);
final int npSaturation = np[1] & 0xFF;
avgSaturation += npSaturation;
final int npLum = np[2] & 0xFF;
avgLum += npLum;
visited.put(nearPoint, np);
}
}
}
}
}
final int pixelCount = visited.size();
if (pixelCount == 0) return 0;
avgSaturation /= pixelCount;
avgLum /= pixelCount;
int redSum = 0;
int greenSum = 0;
int colorDistance = 0;
final int colorDistanceFromRedSum = 0;
int avgColorDistance = 0;
int tempColorDistance = 0;
for (final Entry<Pixel, byte[]> pixelEntry : visited.entrySet()) {
final byte[] np = pixelEntry.getValue();
if (logger.isTraceEnabled() && debugColorsToFile) {
System.out.println(String.format("x %d y %d pc %d - %d %d %d - %d - %d", (int) centerPixelX,
(int) centerPixelY, pixelCount, np[0] & 0xFF, np[1], np[2] & 0xFF, avgSaturation, avgLum));
}
final int npSaturation = np[1] & 0xFF;
final int npLum = np[2] & 0xFF;
if (npSaturation > avgSaturation && npLum < avgLum) {
final int npColor = np[0] & 0xFF;
final int thisDFromRed = Math.min(npColor, Math.abs(180 - npColor)) * npLum * npSaturation;
final int thisDFromGreen = Math.abs(60 - npColor) * npLum * npSaturation;
redSum += thisDFromRed;
greenSum += thisDFromGreen;
final int currentCol = thisDFromRed - thisDFromGreen;
final Pixel pixel = pixelEntry.getKey();
// logger.trace("red {} green {} diff {} CDFR {}", thisDFromRed,
// thisDFromGreen, currentCol,
// colorDistanceFromRed[pixel.x][pixel.y]);
colorDistance += currentCol
- (int) (CURRENT_COLOR_BIAS_MULTIPLIER * colorDistanceFromRed[pixel.x][pixel.y]);
if (logger.isTraceEnabled() && debugColorsToFile) {
traceMat.put(pixelEntry.getKey().y, pixelEntry.getKey().x,
workingFrame.get(pixelEntry.getKey().y, pixelEntry.getKey().x));
// logger.trace("pixel cD {} cC {} cD {}", colorDistance,
// currentCol, CURRENT_COLOR_BIAS_MULTIPLIER *
// colorDistanceFromRed[pixel.x][pixel.y]);
tempColorDistance += currentCol;
avgColorDistance += colorDistanceFromRed[pixel.x][pixel.y];
}
}
}
if (logger.isTraceEnabled() && debugColorsToFile) {
System.out.println(String.format("%d, %d, %d, %d, %d, %b", colorDistance / pixelCount,
avgColorDistance / pixelCount, tempColorDistance / pixelCount, redSum / pixelCount,
greenSum / pixelCount, colorDistance > 0));
System.out.println(String.format("x %d y %d pc %d", (int) centerPixelX, (int) centerPixelY, pixelCount));
final Mat testMat = new Mat();
Imgproc.cvtColor(traceMat, testMat, Imgproc.COLOR_HSV2BGR);
String filename = String.format("shot-colors-%d-%d.png", (int) centerPixelX, (int) centerPixelY);
final File file = new File(filename);
filename = file.toString();
Highgui.imwrite(filename, testMat);
}
return colorDistance / pixelCount;
}
public Optional<ShotColor> getColor(final Mat workingFrame, final int[][] colorDistanceFromRed) {
final int colorDist = getColorDifference(workingFrame, colorDistanceFromRed);
// Sometimes it's better to guess than to return nothing
if (colorDist < 1000)
return Optional.of(ShotColor.RED);
else
return Optional.of(ShotColor.GREEN);
}
}