/* * 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.processors; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.shootoff.camera.CameraManager; import com.shootoff.camera.Shot; public class DeduplicationProcessor implements ShotProcessor { private final static Logger logger = LoggerFactory.getLogger(DeduplicationProcessor.class); private Optional<Shot> lastShot = Optional.empty(); // About 30 pixels at 640x480 private final static double DISTANCE_THRESHOLD_DIVISION_FACTOR = 8000.0; private double distanceThreshold; // frames public static final int DEDUPE_THRESHOLD_MINIMUM = 2; // ms private static final int timestampThreshold = 60; private final CameraManager cameraManager; public DeduplicationProcessor(final CameraManager cameraManager) { this.cameraManager = cameraManager; setDistanceThreshold(); } private void setDistanceThreshold() { distanceThreshold = (cameraManager.getFeedWidth() * cameraManager.getFeedHeight()) / DISTANCE_THRESHOLD_DIVISION_FACTOR; } public Optional<Shot> getLastShot() { return lastShot; } public boolean processShot(Shot shot, boolean updateLastShot) { if (lastShot.isPresent()) { long timeDiff = shot.getTimestamp() - lastShot.get().getTimestamp(); if (timeDiff > timestampThreshold && (shot.getFrame() - lastShot.get().getFrame()) > DEDUPE_THRESHOLD_MINIMUM) { if (updateLastShot) lastShot = Optional.of(shot); return true; } timeDiff = Math.min(timeDiff, timestampThreshold); // The Size area for a dupe decreases from 1 * distanceThreshold to // .5 distanceThreshold // over the time period final double dynamicDistancePercentage = (int) ((1 - ((.5 * timeDiff) / timestampThreshold)) * distanceThreshold); if (logger.isTraceEnabled()) { logger.trace("processShot {} {}", shot.getX(), shot.getY()); logger.trace("processShot ts {} - {}", shot.getTimestamp(), lastShot.get().getTimestamp()); logger.trace("processShot {} {} - {}", shot.getFrame(), lastShot.get().getFrame(), DEDUPE_THRESHOLD_MINIMUM); logger.trace("processShot distance {} - thresh {}", euclideanDistance(lastShot.get(), shot), dynamicDistancePercentage); } if (euclideanDistance(lastShot.get(), shot) <= dynamicDistancePercentage) { if (logger.isTraceEnabled()) logger.trace("processShot DUPE {} {}", shot.getX(), shot.getY()); if (updateLastShot) lastShot = Optional.of(shot); return false; } } if (updateLastShot) lastShot = Optional.of(shot); return true; } private double euclideanDistance(final Shot shot1, final Shot shot2) { return Math.sqrt(Math.pow(shot1.getX() - shot2.getX(), 2) + Math.pow(shot1.getY() - shot2.getY(), 2)); } @Override public boolean processShot(final Shot shot) { return processShot(shot, true); } public boolean processShotLookahead(final Shot shot) { return processShot(shot, false); } @Override public void reset() { lastShot = Optional.empty(); } }