com.blogspot.thedsweb.engine.Brightness.java Source code

Java tutorial

Introduction

Here is the source code for com.blogspot.thedsweb.engine.Brightness.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Dominik Brmer.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 ******************************************************************************/
package com.blogspot.thedsweb.engine;

import java.util.concurrent.Semaphore;
import java.util.logging.Level;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.highgui.VideoCapture;
import org.opencv.imgproc.Imgproc;

import com.blogspot.thedsweb.main.Debug;
import com.blogspot.thedsweb.util.Config;
import com.blogspot.thedsweb.util.Files;
import com.blogspot.thedsweb.util.Initialization;

public class Brightness extends Files {
    private int min;
    private int max;
    private int last;
    private int current;
    private final int maxLevel;
    private double[] referenceValue;
    private boolean face;
    private final Threshold threshold;
    private final Face probability;
    private final Thread fade;
    private final Fade transition;
    private boolean runDaemon;
    private boolean firstRun;
    private boolean backlit;
    private final BacklightDevice blDev;
    private final Semaphore semaphoreFade = new Semaphore(0, false);
    private final Semaphore semaphoreSet = new Semaphore(1, false);

    static {
        // Load native library of OpenCV
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    public Brightness(Config config) {
        // Create threshold object to calculate threshold
        threshold = new Threshold(config);

        // Create face object to track faces
        probability = new Face(config);

        // Create fade object for brightness changes
        transition = new Fade();

        // Daemon to fade between screen brightness values
        fade = new Thread(new FadeDaemon(), "fadeDaemon");
        fade.setDaemon(true);

        // Initialize Files if they dosen't exist
        Initialization.Files();
        final int[] arr = new int[4];
        readStatus(arr);
        min = arr[0];
        max = arr[1];
        last = arr[2];
        if (arr[3] > 0) {
            maxLevel = arr[3];
        } else {
            maxLevel = 15;
        }
        blDev = new BacklightDevice();
        blDev.setRef(min, max);
        referenceValue = blDev.getRef();
        firstRun = true;
        backlit = false;
        current = captureAndCalculate();
    }

    // Start Daemon
    public void start() {
        runDaemon = true;
        fade.start();
    }

    // Cancel Daemon
    public void interrupt() {
        runDaemon = false;
        fade.interrupt();
        try {
            fade.join();
        } catch (final InterruptedException e) {
        }
    }

    // Return the current Brightness
    public int getCurrent() {
        return current;
    }

    // Get the current Brightness
    public void setCurrent() {
        current = captureAndCalculate();
    }

    // Return the last set Brightness
    public int getLast() {
        return last;
    }

    // Return if Faces are detected
    public boolean getFace() {
        return face;
    }

    // Control the difference between last and
    // current Brightness value return true when
    // it is too high
    private boolean control(int current) {
        return current > last << 1 || current << 1 < last;
    }

    // Return the ideal brightness value
    // Method is based on Java's build in binarySearch
    private int brightnessValue(double[] arr, int key) {
        int low = 0;
        int high = arr.length - 1;
        final int LENGTH = arr.length - 1;

        while (low <= high) {
            final int mid = (low + high) >>> 1;
            final double midVal = arr[mid];
            double aMidVal = 255;

            if (mid != LENGTH) {
                aMidVal = arr[mid + 1];
            }

            if (key > aMidVal) {
                low = mid + 1;
            } else if (key < midVal) {
                high = mid - 1;
            } else {
                Debug.LOG.log(Level.CONFIG, "Set " + mid + " as brightness value.");
                return mid;
            }
        }
        Debug.LOG.log(Level.CONFIG, "Set " + LENGTH + " as brightness value.");
        return LENGTH;
    }

    private Mat capture10thFrame(VideoCapture cap, Mat frame) {
        // Grab and decode the 10th frame of the camera
        for (int i = 0; i <= 10; i++) {
            cap.grab();
        }
        cap.retrieve(frame);

        return frame;
    }

    private int captureAndCalculate() {
        final Mat frame = new Mat();

        // Initialize video capturing and set a small image size
        final VideoCapture cap = new VideoCapture(0);
        cap.set(3, 160);
        cap.set(4, 120);

        int meanValue = current;
        face = true;

        // Return current as meanValue if camera start fail
        if (!cap.isOpened()) {
            return meanValue;
        }

        capture10thFrame(cap, frame);

        // Calculate mean value of frame
        meanValue = meanCalculation(frame);

        // If the current value change in a extreme way
        // set it again
        if (control(meanValue)) {
            capture10thFrame(cap, frame);

            // Re-Calculate mean value of frame
            meanValue = meanCalculation(frame);
        }

        // Set true if someone's Face is detected
        face = probability.detectFace(frame, meanValue);

        // Release the camera for other programs
        cap.release();

        return meanValue;
    }

    private int meanCalculation(Mat rgb) {
        // Convert RGB to YcrCb for easier luminance calculation
        final Mat yCrCb = new Mat();
        Imgproc.cvtColor(rgb, yCrCb, Imgproc.COLOR_RGB2YCrCb);

        // Calculate luminance
        final Scalar mainMean = Core.mean(yCrCb);
        int meanLumaValue = (int) mainMean.val[0];

        // Test if backlit conditions are true
        if (!firstRun && backlitDetection(yCrCb, mainMean, meanLumaValue)) {
            Debug.LOG.log(Level.CONFIG, "Backlit detected.");
            backlit = true;
            if (meanLumaValue < current) {
                meanLumaValue = current;
            }
        } else {
            backlit = false;
        }

        // Set the first run parameter to false so that the next time the mean
        // calculation method is called the backlit detection is also running
        if (firstRun) {
            firstRun = false;
        }

        // If the frame is completely black or white the camera must already in
        // use by another program
        if (meanLumaValue == 0) {
            meanLumaValue = current;
        }

        return meanLumaValue;
    }

    private boolean checkChroma(int[] values, int mean) {
        // Check how similar the chroma values of all frame tiles are. By
        // calculate the maximum number of similar tiles.
        int max = 0;
        for (final int value : values) {
            int count = 0;
            for (final int valB : values) {
                if (valB + 5 > value && valB - 5 < value) {
                    count++;
                }
            }
            if (max < count) {
                max = count;
            }
        }
        // Return true if more than the half of the tiles have a similar chroma
        // value.
        return max > 4;
    }

    private boolean backlitDetection(Mat yCrCb, Scalar mainMean, int mainMeanLumaValue) {
        // Save the mean brightness and chroma value of the whole frame. Plus
        // the width and height of the frame.
        final int mainMeanChromaValue = (int) mainMean.val[2];
        final int PARTS = 8;
        final int w = yCrCb.width();
        final int h = yCrCb.height();

        final int wStep = w >> 2;
        final int hStep = h >> 1;

        // Separate the image into 8 equal parts.
        final Mat[] tiles = new Mat[PARTS];
        tiles[0] = yCrCb.submat(0, hStep, 0, wStep);
        tiles[1] = yCrCb.submat(0, hStep, wStep, wStep * 2);
        tiles[2] = yCrCb.submat(hStep, h, 0, wStep);
        tiles[3] = yCrCb.submat(hStep, h, wStep, wStep * 2);
        tiles[4] = yCrCb.submat(0, hStep, wStep * 2, wStep * 3);
        tiles[5] = yCrCb.submat(0, hStep, wStep * 2, w);
        tiles[6] = yCrCb.submat(hStep, h, wStep * 2, wStep * 3);
        tiles[7] = yCrCb.submat(hStep, h, wStep * 2, w);

        // Calculate the mean value of all parts.
        final Scalar[] tileMean = new Scalar[PARTS];
        for (int i = 0; i < tileMean.length; i++) {
            tileMean[i] = Core.mean(tiles[i]);
        }

        // Save the mean brightness and chroma of all parts.
        final int[] tileMeanLuma = new int[PARTS];
        final int[] tileMeanChroma = new int[PARTS];

        // Save min and max value in container
        MinMaxContainer con = null;

        for (int j = 0; j < tileMean.length; j++) {
            tileMeanLuma[j] = (int) tileMean[j].val[0];
            if (j == 0) {
                con = new MinMaxContainer(tileMeanLuma[j]);
            } else {
                con.add(tileMeanLuma[j]);
            }
            tileMeanChroma[j] = (int) tileMean[j].val[2];
        }

        // Get the highest and lowest brightness value of all frame tiles.
        final int min = con.getMin();
        final int max = con.getMax() >> 1;

        // Check if their is a consistent chroma distribution and a brightness
        // spike to detect backlit conditions.
        if (checkChroma(tileMeanChroma, mainMeanChromaValue) && min < max) {
            return true;
        }

        return false;
    }

    public void setBrightness() {
        // Threshold values to prevent flickering
        final int darkeningThreshold = threshold.getDarkeningThreshold(last);
        final int brighteningThreshold = threshold.getBrighteningThreshold(last);

        // Set the display brightness value relative to reference
        if (current <= darkeningThreshold || current > brighteningThreshold) {
            // Check if the new current value is also the new
            // minimum or maximum value
            if (current < min) {
                min = current;
                blDev.setRef(min, max);
                referenceValue = blDev.getRef();
            }
            if (current > max) {
                max = current;
                blDev.setRef(min, max);
                referenceValue = blDev.getRef();
            }

            // Save last current value
            last = current;

            // Set Brightness
            try {
                semaphoreSet.acquire();
            } catch (final InterruptedException e) {
            }
            transition.setValue(brightnessValue(referenceValue, current));
            semaphoreFade.release();

            // Save all values
            saveValue(current, min, max);
        }
    }

    public void resetBrightnessAfterReboot() {
        // Set Brightness
        blDev.set(brightnessValue(referenceValue, last));
    }

    public int calibrateBrightness() {
        firstRun = false;

        // Get new current value
        setCurrent();

        // Return failure value on backlit
        if (backlit) {
            return -1;
        }

        // Check if the new current value is also the new
        // minimum or maximum value
        if (current < min) {
            min = current;
        }
        if (current > max) {
            max = current;
        }

        // Save all values
        saveValue(current, min, max);

        return current;
    }

    public void darkenBrightness() {
        last = 0;
        try {
            semaphoreSet.acquire();
        } catch (final InterruptedException e) {
        }
        transition.setValue(0);
        semaphoreFade.release();
    }

    class FadeDaemon implements Runnable {
        @Override
        public void run() {
            int step = 500 / maxLevel;

            // Ensure that there is a min. laziness of 1 millisecond
            if (step == 0) {
                step = 1;
            }

            while (runDaemon) {
                try {
                    semaphoreFade.acquire();
                } catch (final InterruptedException e) {
                    return;
                }
                int count = 0;
                // Set dynamically the brightness level
                while (blDev.get() != transition.getValue() && count < maxLevel) {
                    int i = blDev.get();
                    if (i > transition.getValue()) {
                        i--;
                    } else {
                        i++;
                    }
                    blDev.set(i);
                    count++;
                    try {
                        Thread.sleep(step);
                    } catch (final InterruptedException e) {
                        return;
                    }
                }
                semaphoreSet.release();
            }
        }
    }
}