From 100b82fe1c5ada6ef2ce768bf7b9f6f469650e11 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Tue, 21 Mar 2017 21:37:13 +0100 Subject: [PATCH] Made a GUI tester for video capture --- config.properties | 7 +- src/kaka/cakelight/Configuration.java | 13 +++ src/kaka/cakelight/Frame.java | 58 +++++++++---- src/kaka/cakelight/GuiTest.java | 158 ++++++++++++++++++++++++++++++++++ src/kaka/cakelight/VideoMode.java | 10 ++- 5 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 src/kaka/cakelight/GuiTest.java diff --git a/config.properties b/config.properties index b3f150f..cb1d685 100644 --- a/config.properties +++ b/config.properties @@ -1,9 +1,14 @@ # default settings -video.device=/dev/video0 +video.device=/dev/video2 video.width=720 video.height=576 video.bpp=2 +video.crop.left=28 +video.crop.right=22 +video.crop.top=8 +video.crop.bottom=120 + leds.cols=32 leds.rows=18 diff --git a/src/kaka/cakelight/Configuration.java b/src/kaka/cakelight/Configuration.java index 35fde30..30308e2 100644 --- a/src/kaka/cakelight/Configuration.java +++ b/src/kaka/cakelight/Configuration.java @@ -60,12 +60,25 @@ public class Configuration { public int width; public int height; public int bpp; + public CropConfiguration crop; private VideoConfiguration(Properties prop) { device = get(prop, "video.device", "/dev/video0"); width = Integer.parseInt(get(prop, "video.width", "720")); height = Integer.parseInt(get(prop, "video.height", "576")); bpp = Integer.parseInt(get(prop, "video.bpp", "2")); + crop = new CropConfiguration(prop); + } + + public class CropConfiguration { + public int left, right, top, bottom; + + private CropConfiguration(Properties prop) { + left = Integer.parseInt(get(prop, "video.crop.left", "0")); + right = Integer.parseInt(get(prop, "video.crop.right", "0")); + top = Integer.parseInt(get(prop, "video.crop.top", "0")); + bottom = Integer.parseInt(get(prop, "video.crop.bottom", "0")); + } } } diff --git a/src/kaka/cakelight/Frame.java b/src/kaka/cakelight/Frame.java index a162024..a1d6946 100644 --- a/src/kaka/cakelight/Frame.java +++ b/src/kaka/cakelight/Frame.java @@ -1,12 +1,11 @@ package kaka.cakelight; +import javafx.scene.paint.Color; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; -import java.awt.*; - import static kaka.cakelight.Main.saveFile; import static kaka.cakelight.Main.timeIt; @@ -15,6 +14,7 @@ public class Frame { private Configuration config; private Mat colImage; private Mat rowImage; + private Mat converted; private Frame(byte[] data) { this.data = data; @@ -46,14 +46,21 @@ public class Frame { // timeIt("resizing", () -> Imgproc.resize(converted, resized, new Size(config.leds.cols, config.leds.rows), 0, 0, Imgproc.INTER_AREA)); // INTER_AREA is the best for shrinking, but also the slowest (~1.5 ms) // }); - Mat converted = new Mat(); - Imgproc.cvtColor(src, converted, Imgproc.COLOR_YUV2RGB_YUYV); - timeIt("model 1", () -> model1(converted, Imgproc.INTER_AREA)); - timeIt("model 2", () -> model2(converted, Imgproc.INTER_AREA)); + Mat cropped = src.submat( + config.video.crop.top, + config.video.height - config.video.crop.bottom, + config.video.crop.left, + config.video.width - config.video.crop.right + ); + converted = new Mat(); + Imgproc.cvtColor(cropped, converted, Imgproc.COLOR_YUV2RGB_YUYV); +// timeIt("model 1", () -> model1(converted, Imgproc.INTER_AREA)); +// timeIt("model 2", () -> model2(converted, Imgproc.INTER_AREA)); timeIt("model 3", () -> model3(converted, Imgproc.INTER_AREA)); // save(converted, "/home/kaka/test-converted.data"); // save(resized, "/home/kaka/test-resized.data"); - System.out.println("color: " + getPixel(ListPosition.BOTTOM, 0)); + src.release(); + cropped.release(); } private void model1(Mat src, int interpolation) { @@ -73,25 +80,32 @@ public class Frame { Imgproc.resize(src, rowImage, new Size(16, config.leds.rows), 0, 0, interpolation); } - public Color getPixel(ListPosition listPosition, int xy) { + public Color getLedColor(ListPosition listPosition, int xy) { switch (listPosition) { case LEFT: - return pixelToColor(rowImage, 0, xy); + return interpolatedRowColor(xy, 0, 1, 2); case RIGHT: - return pixelToColor(rowImage, config.leds.cols - 1, xy); + return interpolatedRowColor(xy, 15, 14, 13); case TOP: - return pixelToColor(colImage, xy, 0); + return interpolatedColColor(xy, 0, 1, 2); case BOTTOM: - return pixelToColor(colImage, xy, config.leds.cols - 1); + return interpolatedColColor(xy, 8, 7, 6); } return null; } + private Color interpolatedRowColor(int y, int x1, int x2, int x3) { + return pixelToColor(rowImage, x3, y).interpolate(pixelToColor(rowImage, x2, y), 0.65).interpolate(pixelToColor(rowImage, x1, y), 0.65); + } + + private Color interpolatedColColor(int x, int y1, int y2, int y3) { + return pixelToColor(colImage, x, y3).interpolate(pixelToColor(colImage, x, y2), 0.65).interpolate(pixelToColor(colImage, x, y1), 0.65); + } + private Color pixelToColor(Mat image, int x, int y) { byte[] rgb = new byte[3]; image.get(y, x, rgb); - System.out.println("r = " + rgb[0] + ", g = " + rgb[1] + ", b = " + rgb[2]); - return new Color(rgb[0], rgb[1], rgb[2]); + return Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff); } private void save(Mat mat, String filepath) { @@ -101,6 +115,20 @@ public class Frame { } public byte[] getData() { - return data; + byte[] buff = new byte[(int) (converted.total() * converted.channels())]; + converted.get(0, 0, buff); + return buff; + } + + public Mat getColImage() { + return colImage; + } + + public Mat getRowImage() { + return rowImage; + } + + public Mat getConvertedImage() { + return converted; } } diff --git a/src/kaka/cakelight/GuiTest.java b/src/kaka/cakelight/GuiTest.java new file mode 100644 index 0000000..8feac32 --- /dev/null +++ b/src/kaka/cakelight/GuiTest.java @@ -0,0 +1,158 @@ +package kaka.cakelight; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.stage.Stage; +import org.opencv.core.Core; +import org.opencv.core.Mat; + +import static kaka.cakelight.Main.log; + +public class GuiTest extends Application { + private static final int BLOCK = 45; + private static final int GUTTER = 3 * BLOCK; + private Canvas canvas; + private Configuration config; + private CakeLight cakelight; + private boolean paused; + + public static void main(String[] args) { + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + launch(args); + } + + @Override + public void start(Stage stage) throws Exception { + config = Configuration.from("config.properties"); + canvas = new Canvas(16 * BLOCK + 2 * GUTTER, 9 * BLOCK + 2 * GUTTER); + + Pane root = new Pane(); + root.getChildren().add(canvas); + + Scene scene = new Scene(root); + scene.setOnKeyPressed(keyEvent -> { + if (keyEvent.getCode() == KeyCode.ESCAPE) { + stage.close(); + cakelight.cleanup(); + } + if (keyEvent.getCode() == KeyCode.SPACE) { + paused = !paused; + } + }); + + stage.setTitle("Cakelight"); + stage.setScene(scene); + stage.setOnCloseRequest(windowEvent -> cakelight.cleanup()); + stage.show(); + + setupCakeLight(); + } + + private void setupCakeLight() { + log("Running with config:\n" + config); + cakelight = new CakeLight(config); + VideoMode mode = new VideoMode(); + cakelight.setMode(mode); + cakelight.startLoop(); + mode.onFrame(frame -> drawFrame(canvas.getGraphicsContext2D(), frame)); + } + + private void drawFrame(GraphicsContext gc, Frame frame) { + if (paused) return; + System.out.println("Drawing a frame"); + drawCols(gc, frame); + drawRows(gc, frame); +// drawVideo(gc, frame); + drawBorderAndGrid(gc); + drawLEDs(gc, frame); + } + + private void drawLEDs(GraphicsContext gc, Frame frame) { + int ledLength = GUTTER; + float colSize = 16f * BLOCK / config.leds.cols; + float rowSize = 9f * BLOCK / config.leds.rows; +// DropShadow shadow = new DropShadow(BlurType.ONE_PASS_BOX, Color.RED, colSize * 2, colSize, 0, 0); + for (int x = 0; x < config.leds.cols; x++) { + gc.setFill(frame.getLedColor(ListPosition.TOP, x)); + gc.fillRect(GUTTER + x * colSize, GUTTER - ledLength, colSize, ledLength); + gc.setFill(frame.getLedColor(ListPosition.BOTTOM, x)); + gc.fillRect(GUTTER + x * colSize, GUTTER + 9 * BLOCK, colSize, ledLength); + } + for (int y = 0; y < config.leds.rows; y++) { + gc.setFill(frame.getLedColor(ListPosition.LEFT, y)); + gc.fillRect(GUTTER - ledLength, GUTTER + y * rowSize, ledLength, rowSize); + gc.setFill(frame.getLedColor(ListPosition.RIGHT, y)); + gc.fillRect(GUTTER + 16 * BLOCK, GUTTER + y * rowSize, ledLength, rowSize); + } + } + + private void drawVideo(GraphicsContext gc, Frame frame) { + byte[] rgb = new byte[3]; + Mat img = frame.getConvertedImage(); + float colSize = 16 * BLOCK / (float)img.cols(); + float rowSize = 9 * BLOCK / (float)img.rows(); + for (int x = 0, cols = img.cols(); x < cols; x++) { + for (int y = 0, rows = img.rows(); y < rows; y++) { + img.get(y, x, rgb); + gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5)); + gc.fillRect(GUTTER + x * colSize, GUTTER + y * rowSize, colSize, rowSize); + } + } + } + + private void drawCols(GraphicsContext gc, Frame frame) { + byte[] rgb = new byte[3]; + for (int x = 0; x < config.leds.cols; x++) { + for (int y = 0; y < 9; y++) { + frame.getColImage().get(y, x, rgb); + drawColPixel(gc, x, y, rgb); + } + } + } + + private void drawRows(GraphicsContext gc, Frame frame) { + byte[] rgb = new byte[3]; + for (int y = 0; y < config.leds.rows; y++) { + for (int x = 0; x < 16; x++) { + frame.getRowImage().get(y, x, rgb); + drawRowPixel(gc, x, y, rgb); + } + } + } + + private void drawColPixel(GraphicsContext gc, int x, int y, byte[] rgb) { + float ledSize = 16f * BLOCK / config.leds.cols; + gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5)); + gc.fillRect(GUTTER + x * ledSize, GUTTER + y * BLOCK, ledSize, BLOCK); + } + + private void drawRowPixel(GraphicsContext gc, int x, int y, byte[] rgb) { + float ledSize = 9f * BLOCK / config.leds.rows; + gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5)); + gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * ledSize, BLOCK, ledSize); + } + + private void drawBorderAndGrid(GraphicsContext gc) { + gc.setStroke(Color.BLACK); + gc.setLineWidth(BLOCK * 0.1); + gc.strokeRect(GUTTER, GUTTER, 16 * BLOCK, 9 * BLOCK); + gc.setLineWidth(1); + for (int x = 1; x < 16; x++) { + gc.strokeLine(GUTTER + x * BLOCK, GUTTER, GUTTER + x * BLOCK, GUTTER + 9 * BLOCK); + } + for (int y = 1; y < 9; y++) { + gc.strokeLine(GUTTER, GUTTER + y * BLOCK, GUTTER + 16 * BLOCK, GUTTER + y * BLOCK); + } + } + + private void drawPixel(GraphicsContext gc, int x, int y, Paint paint) { + gc.setFill(paint); + gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * BLOCK, BLOCK, BLOCK); + } +} diff --git a/src/kaka/cakelight/VideoMode.java b/src/kaka/cakelight/VideoMode.java index be2ee41..53cf09b 100644 --- a/src/kaka/cakelight/VideoMode.java +++ b/src/kaka/cakelight/VideoMode.java @@ -2,6 +2,7 @@ package kaka.cakelight; import java.io.IOException; import java.util.Optional; +import java.util.function.Consumer; import static kaka.cakelight.Main.log; import static kaka.cakelight.Main.timeIt; @@ -9,6 +10,7 @@ import static kaka.cakelight.Main.timeIt; public class VideoMode implements Mode { private Configuration config; private Thread thread; + private Consumer frameConsumer; @Override public void enter(Configuration config) { @@ -22,12 +24,14 @@ public class VideoMode implements Mode { } private void startGrabberThread() { + assert frameConsumer != null; thread = new Thread() { public void run() { try (FrameGrabber grabber = FrameGrabber.from(config)) { while (!isInterrupted()) { // Optional frame = grabber.grabFrame(); - timeIt("frame", grabber::grabFrame); + grabber.grabFrame().ifPresent(frameConsumer); +// timeIt("frame", grabber::grabFrame); // TODO: process frame // TODO: save where the LedController can access it } @@ -38,4 +42,8 @@ public class VideoMode implements Mode { }; thread.start(); } + + public void onFrame(Consumer consumer) { + frameConsumer = consumer; + } } -- 2.11.0