mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-20 00:51:41 +00:00
Save input image before resize or draw (#214)
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util.java;
|
||||
|
||||
public interface TriConsumer<T, U, V> {
|
||||
void accept(T t, U u, V v);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
|
||||
@@ -32,7 +33,7 @@ import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
|
||||
public class FileSaveFrameConsumer {
|
||||
public class FileSaveFrameConsumer implements Consumer<Frame> {
|
||||
|
||||
// Formatters to generate unique, timestamped file names
|
||||
private static String FILE_PATH = ConfigManager.getInstance().getImageSavePath().toString();
|
||||
@@ -59,7 +60,7 @@ public class FileSaveFrameConsumer {
|
||||
this.rootTable = NetworkTablesManager.getInstance().kRootTable;
|
||||
updateCameraNickname(camNickname);
|
||||
entry = subTable.getEntry(ntEntryName);
|
||||
entry.setBoolean(false);
|
||||
entry.forceSetBoolean(false);
|
||||
this.logger = new Logger(FileSaveFrameConsumer.class, this.camNickname, LogGroup.General);
|
||||
}
|
||||
|
||||
@@ -68,8 +69,7 @@ public class FileSaveFrameConsumer {
|
||||
|
||||
if (lock.tryLock()) {
|
||||
boolean curCommand = entry.getBoolean(false);
|
||||
|
||||
if (curCommand == true && prevCommand == false) {
|
||||
if (curCommand && !prevCommand) {
|
||||
Date now = new Date();
|
||||
String savefile =
|
||||
FILE_PATH
|
||||
@@ -81,12 +81,16 @@ public class FileSaveFrameConsumer {
|
||||
+ tf.format(now)
|
||||
+ FILE_EXTENSION;
|
||||
|
||||
Imgcodecs.imwrite(savefile.toString(), frame.image.getMat());
|
||||
Imgcodecs.imwrite(savefile, frame.image.getMat());
|
||||
|
||||
// Help the user a bit - set the NT entry back to false after 500ms
|
||||
TimedTaskManager.getInstance().addOneShotTask(() -> resetCommand(), CMD_RESET_TIME_MS);
|
||||
TimedTaskManager.getInstance().addOneShotTask(this::resetCommand, CMD_RESET_TIME_MS);
|
||||
|
||||
logger.info("Saved new image at " + savefile);
|
||||
} else if (!curCommand) {
|
||||
// If the entry is currently false, set it again. This will make sure it shows up on the
|
||||
// dashboard.
|
||||
entry.forceSetBoolean(false);
|
||||
}
|
||||
|
||||
prevCommand = curCommand;
|
||||
|
||||
@@ -69,9 +69,14 @@ public class CVMat implements Releasable {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
int matNo = allMats.get(mat);
|
||||
allMats.remove(mat);
|
||||
// If this mat is empty, all we can do is return
|
||||
if (mat.empty()) return;
|
||||
|
||||
// If the mat isn't in the hashmap, we can't remove it
|
||||
Integer matNo = allMats.get(mat);
|
||||
if (matNo != null) allMats.remove(mat);
|
||||
mat.release();
|
||||
|
||||
if (shouldPrint) {
|
||||
logger.trace(() -> "CVMat" + matNo + " de-alloc - new count: " + allMats.size());
|
||||
logger.trace(getStackTraceBuilder()::toString);
|
||||
@@ -82,6 +87,11 @@ public class CVMat implements Releasable {
|
||||
return mat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CVMat{" + mat.toString() + '}';
|
||||
}
|
||||
|
||||
public static int getMatCount() {
|
||||
return allMats.size();
|
||||
}
|
||||
|
||||
@@ -158,9 +158,15 @@ public class ReflectivePipeline extends CVPipeline<CVPipelineResult, ReflectiveP
|
||||
sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
|
||||
pipeProfileNanos[1] = pipeProfileNanos[1] = hsvPipeResult.nanosElapsed;
|
||||
} else {
|
||||
// Try to copy the color frame.
|
||||
long inputMatPtr = PicamJNI.grabFrame(true);
|
||||
if (inputMatPtr != 0) rawInputMat = new Mat(inputMatPtr);
|
||||
else rawInputMat = frame.image.getMat();
|
||||
if (inputMatPtr != 0) {
|
||||
// If we grabbed it (in color copy mode), make a new Mat of it
|
||||
rawInputMat = new Mat(inputMatPtr);
|
||||
} else {
|
||||
// Otherwise, the input mat is frame we got from the camera
|
||||
rawInputMat = frame.image.getMat();
|
||||
}
|
||||
|
||||
// We can skip a few steps if the image is single channel because we've already done them on
|
||||
// the GPU
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.photonvision.common.hardware.HardwareManager;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.SerializationUtils;
|
||||
import org.photonvision.common.util.java.TriConsumer;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.camera.CameraQuirk;
|
||||
import org.photonvision.vision.camera.QuirkyCamera;
|
||||
@@ -66,6 +67,9 @@ public class VisionModule {
|
||||
private final StreamRunnable streamRunnable;
|
||||
private final LinkedList<CVPipelineResultConsumer> resultConsumers = new LinkedList<>();
|
||||
private final LinkedList<CVPipelineResultConsumer> fpsLimitedResultConsumers = new LinkedList<>();
|
||||
// Raw result consumers run before any drawing has been done by the OutputStreamPipeline
|
||||
private final LinkedList<TriConsumer<Frame, Frame, List<TrackedTarget>>> rawResultConsumers =
|
||||
new LinkedList<>();
|
||||
private final NTDataPublisher ntConsumer;
|
||||
private final UIDataPublisher uiDataConsumer;
|
||||
protected final int moduleIndex;
|
||||
@@ -175,7 +179,7 @@ public class VisionModule {
|
||||
private void recreateFpsLimitedResultConsumers() {
|
||||
// Important! These must come before the stream result consumers because the stream result
|
||||
// consumers release the frame
|
||||
fpsLimitedResultConsumers.add(result -> inputFrameSaver.accept(result.inputFrame));
|
||||
rawResultConsumers.add((in, out, tgts) -> inputFrameSaver.accept(in));
|
||||
fpsLimitedResultConsumers.add(result -> outputFrameSaver.accept(result.outputFrame));
|
||||
|
||||
fpsLimitedResultConsumers.add(
|
||||
@@ -249,6 +253,7 @@ public class VisionModule {
|
||||
this.shouldRun = false;
|
||||
}
|
||||
if (shouldRun) {
|
||||
consumeRawResults(inputFrame, outputFrame, targets);
|
||||
try {
|
||||
var osr = outputStreamPipeline.process(inputFrame, outputFrame, settings, targets);
|
||||
consumeFpsLimitedResult(osr);
|
||||
@@ -522,6 +527,13 @@ public class VisionModule {
|
||||
}
|
||||
}
|
||||
|
||||
/** Consume results prior to drawing on them. */
|
||||
private void consumeRawResults(Frame inputFrame, Frame outputFrame, List<TrackedTarget> targets) {
|
||||
for (var c : rawResultConsumers) {
|
||||
c.accept(inputFrame, outputFrame, targets);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTargetModel(TargetModel targetModel) {
|
||||
var settings = pipelineManager.getCurrentUserPipeline().getSettings();
|
||||
if (settings instanceof ReflectivePipelineSettings) {
|
||||
|
||||
Reference in New Issue
Block a user