Made a GUI tester for video capture
authorTomas Wenström <tomas.wenstrom@gmail.com>
Tue, 21 Mar 2017 20:37:13 +0000 (21:37 +0100)
committerTomas Wenström <tomas.wenstrom@gmail.com>
Tue, 21 Mar 2017 20:37:44 +0000 (21:37 +0100)
config.properties
src/kaka/cakelight/Configuration.java
src/kaka/cakelight/Frame.java
src/kaka/cakelight/GuiTest.java [new file with mode: 0644]
src/kaka/cakelight/VideoMode.java

index b3f150f..cb1d685 100644 (file)
@@ -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
index 35fde30..30308e2 100644 (file)
@@ -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"));
+            }
         }
     }
 
index a162024..a1d6946 100644 (file)
@@ -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 (file)
index 0000000..8feac32
--- /dev/null
@@ -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);
+    }
+}
index be2ee41..53cf09b 100644 (file)
@@ -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<Frame> 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> 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<Frame> consumer) {
+        frameConsumer = consumer;
+    }
 }