Fügen Sie dem Player Ambilight mit intelligenten Xiaomi-Lampen hinzu





Hallo!

Ich denke, viele, die sich für ein Smart Home oder einfach eine technologische Anordnung ihres Hauses interessieren, haben über das "atmosphärische" und nicht standardmäßige Beleuchtungssystem nachgedacht.



Eine Möglichkeit, einen Raum beim Ansehen von Filmen auf solch "ungewöhnliche" Weise zu beleuchten, bietet Philips mit Ambilight-Technologie, die in die hochentwickelten Fernseher der Marke integriert ist.



In diesem Artikel erfahren Sie, wie Ambilight mit Xiaomi Yeelight Smart Bulbs implementiert wird!



Über Ambilight



Wer weiß nicht - Ambilight-Technologie ist eine in Fernsehgeräte integrierte Hintergrundbeleuchtung, die das Farbbild des Rahmens auf dem Fernsehbildschirm analysiert und das diffuse Licht um den Umfang des Fernsehgeräts herum reproduziert.







Vorteile von Ambilight:



  • , ;
  • ;
  • , .


Im Allgemeinen ist Ambilight eine ziemlich interessante Technologie, und die Bestätigung dieser Tatsache ist das Vorhandensein einer großen Anzahl verschiedener Optionen für die im Internet präsentierte "handwerkliche" Implementierung. Sie basieren jedoch überwiegend auf der Verwendung eines adressierbaren LED-Streifens, der auf die Rückseite der TV / Monitor / Laptop-Abdeckung geklebt wird. Für eine solche Implementierung ist es erforderlich, dass mindestens eine physische externe Steuerung für die Steuerung der LEDs verantwortlich ist. Dies erfordert spezifisches Wissen von der Person, die ein solches System installieren möchte. Daher schlage ich als Alternative die "proger" und ziemlich einfache Version einer solchen Hintergrundbeleuchtung mit intelligenten Lampen vor.



Was sind diese intelligenten Lampen?



Um diese Beleuchtungsoption zu erstellen, benötigen Sie ein Beleuchtungsgerät der Marke Yeelight (eine Tochtergesellschaft von Xiaomi) oder Xiaomi (jedoch nur diejenigen, die Yeelight im Namen erwähnen). Dies bedeutet, dass das Gerät in das Xiaomi Smart Home-Ökosystem eingebettet ist und über die Yeelight-App gesteuert wird.







Meiner Meinung nach ist die adaptive Hintergrundbeleuchtung keine Funktion, für die jemand eine Xiaomi-Smart-Lampe kaufen wird (übrigens für einen erheblichen Geldbetrag). Für mich ist dies jedoch eine gute Gelegenheit, die Funktionalität einer vorhandenen Lampe zu Hause zu erweitern. Auf jeden Fall kann ich als Besitzer von zwei Xiaomi-Lampen sagen, dass ich nach zwei Monaten Gebrauch nur angenehme Eindrücke habe.



Die Yeelight-Anwendung spielt eine wichtige Rolle bei der Implementierung dieses Projekts, da sie einen nützlichen Parameter hat - den Entwicklermodus .





In den letzten Updates wurde es in "LAN Control" umbenannt.



Das moderne Smart-Home-Ökosystem basiert auf dem Datenaustausch zwischen Geräten über das Wi-Fi-Protokoll. Jedes Smart-Gerät verfügt über ein integriertes Wi-Fi-Modul, mit dem Sie eine Verbindung zu einem lokalen drahtlosen Netzwerk herstellen können. Dank dessen wird das Gerät über den Cloud-Service des Smart Home gesteuert. Im Entwicklermodus können Sie jedoch direkt mit dem Gerät kommunizieren, indem Sie Anforderungen an die dem Gerät zugewiesene IP-Adresse senden (die Geräteadresse finden Sie in der Yeelight-App in den Geräteinformationen). Dieser Modus garantiert den Empfang von Daten von Geräten, die sich im selben lokalen Netzwerk wie die Smart Lampe befinden. Die Yeelight-Website bietet eine kleine Demo der Funktionen des Entwicklermodus.



Dank dieser Option ist es möglich, die Funktion der adaptiven Beleuchtung zu implementieren und in den Open Source Player einzubetten.



Funktionsdefinition



Ein weiterer Beitrag befasst sich mit den Schwierigkeiten (und Möglichkeiten, sie zu lösen), auf die ein Ingenieur stoßen kann, wenn er über die Gestaltung eines solchen Dokuments nachdenkt, sowie mit den allgemeinen Fortschritten bei der Umsetzung dieses Plans.



Wenn Sie nur an einem vorgefertigten Programm interessiert sind, können Sie direkt zum Punkt "Für diejenigen, die nur einen vorgefertigten Player verwenden möchten" gehen.



Lassen Sie uns zunächst entscheiden, welche Aufgaben das zu entwickelnde Projekt lösen soll. Die wichtigsten Punkte des TOR für dieses Projekt:



  • Es ist erforderlich, Funktionen zu entwickeln, mit denen Sie die Parameter (Farbe oder Helligkeit / Temperatur des Lichts bei Verwendung eines Geräts ohne RGB-LEDs) der Smart-Lampe je nach aktuellem Bild im Media Player-Fenster dynamisch ändern können.
  • .
  • , «» .
  • .
  • .




,



Wenn Sie die Implementierung der adaptiven Beleuchtung nicht verstehen möchten und nur einen vorgefertigten Player verwenden möchten, können Sie die bereits zusammengestellte JAR-Datei aus dem Repository herunterladen und dann den Abschnitt Bevor Sie beginnen in der README-Datei aus dem Repository lesen .









Die erste Phase der Projektentwicklung wird die Definition eines Players zum Einbetten einer Funktion und einer Bibliothek zur Kommunikation mit einer intelligenten Lampe sein.



Meine Wahl fiel auf den vlcj-Player und die in Java geschriebene Yapi- Bibliothek . Maven wurde als Build-Tool verwendet . Vlcj ist ein Framework, mit dem Sie einen nativen VLC-Player in eine Java-Anwendung einbetten und den Lebenszyklus des Players über Java-Code verwalten können. Der Autor des Frameworks hat auch eine Demoversion des Players , die die Benutzeroberfläche und Funktionalität des VLC-Players fast vollständig wiederholt. Die derzeit stabilste Version des Players ist Version 3. Sie wird im Projekt verwendet.









Vlcj-Player-Oberfläche mit geöffneten zusätzlichen Fenstern



Vorteile des Vlcj-Players:



  • eine große Anzahl unterstützter Videoformate, was ein langjähriges Merkmal des VLC-Players ist;
  • Java als PL, mit dem Sie den Player auf einer Vielzahl von Betriebssystemen öffnen können (in diesem Fall sind wir nur durch die Implementierung des VLC-Players eingeschränkt, der untrennbar mit einer Java-Anwendung verbunden ist).


Nachteile:



  • veraltetes Design des Players, das durch die eigene Implementierung der Schnittstelle gelöst wird;
  • Bevor Sie das Programm verwenden können, müssen Sie einen VLC-Player und Java Version 8 oder höher installieren, was definitiv ein Nachteil ist.


Die Verwendung von Yapi als Bibliothek für die Verbindung mit intelligenten Yeelight-Geräten kann vor allem durch die Einfachheit und zweitens durch den Mangel an vorgefertigten Lösungen gerechtfertigt werden. Derzeit gibt es nicht viele Tools von Drittanbietern zur Steuerung intelligenter Lampen, insbesondere in der Java-Sprache.



Der Hauptnachteil der Yapi-Bibliothek besteht darin, dass keine ihrer Versionen im Maven-Repository vorhanden ist. Bevor Sie den Projektcode kompilieren, müssen Sie Yapi manuell im lokalen Repository installieren (die gesamte Installation wird in der README-Datei im Repository beschrieben).



Bildanalysealgorithmus



Das Prinzip der dynamischen Beleuchtung basiert auf einer regelmäßigen Farbanalyse des aktuellen Rahmens.



Als Ergebnis der Versuchs- und Fehlerphase wurde das folgende Prinzip der Bildanalyse entwickelt:



Mit der angegebenen Häufigkeit macht das Programm einen Screenshot des Media Players und empfängt ein Objekt der BufferedImage-Klasse. Als nächstes wird mit dem schnellsten integrierten Algorithmus die Größe des Originalbilds auf 20 x 20 Pixel geändert.



Dies ist für die Geschwindigkeit des Algorithmus erforderlich, um die Genauigkeit bei der Bestimmung der Farbe zu beeinträchtigen. Dies ist auch erforderlich, um die Abhängigkeit der Bildverarbeitungszeit von der Auflösung der aktuellen Mediendatei zu minimieren.



Als nächstes unterteilt der Algorithmus das resultierende Bild in vier "Basis" -Zonen (oben links, unten links usw.) mit einer Größe von 10 x 10 Pixel.





"Grundlegende" Zonen



Dieser Mechanismus bietet eine unabhängige Analyse verschiedener Bildzonen, mit der Sie das Beleuchtungsgerät in Zukunft an einer bestimmten Stelle im Raum platzieren und angeben können, welche Bildzone "verfolgt" werden muss. In Verbindung mit einem Mehrlampenprogramm macht diese Funktionalität die dynamische Beleuchtung viel atmosphärischer.



Dann wird für jeden Bereich des Bildes eine durchschnittliche Farbe berechnet, indem das arithmetische Mittel für die drei Farbkomponenten (rot, grün, blau) jedes Pixels separat berechnet und die resultierenden Daten in einem einzigen Farbwert angeordnet werden.



Dank der vier resultierenden Werte können wir:



  • 5 : , , , ( «» );
  • :

    ((r0,2126+G0,7152+b0,0722)/.255100

    r, g, b – //
  • :

    {0,rb,((r- -b)/.255100,r>b

    r, b – /


Für eine effiziente und skalierbare Mechanik der Berechnung von Bildparametern werden alle zusätzlichen Daten (nicht "Basis" -Zonen, Temperatur und Farbhelligkeit) "träge" berechnet, d. H. wie benötigt.



Der gesamte Bildverarbeitungscode passt in eine ImageHandler-Klasse:



public class ImageHandler {
    private static List<ScreenArea> mainAreas = Arrays.asList(ScreenArea.TOP_LEFT, ScreenArea.TOP_RIGHT, ScreenArea.BOTTOM_LEFT, ScreenArea.BOTTOM_RIGHT);
    private static int scaledWidth = 20;
    private static int scaledHeight = 20;
    private static int scaledWidthCenter = scaledWidth / 2;
    private static int scaledHeightCenter = scaledHeight / 2;
    private Map<ScreenArea, Integer> screenData;
    private LightConfig config;

    //        
    private int[] getDimensions(ScreenArea area) {
        int[] dimensions = new int[4];
        if (!mainAreas.contains(area)) {
            return dimensions;
        }
        String name = area.name().toLowerCase();
        dimensions[0] = (name.contains("left")) ? 0 : scaledWidthCenter;
        dimensions[1] = (name.contains("top")) ? 0 : scaledHeightCenter;
        dimensions[2] = scaledWidthCenter;
        dimensions[3] = scaledHeightCenter;
        return dimensions;
    }

    //    
    private BufferedImage getScaledImage(BufferedImage image, int width, int height) {
        Image tmp = image.getScaledInstance(width, height, Image.SCALE_FAST);
        BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = scaledImage.createGraphics();
        g2d.drawImage(tmp, 0, 0, null);
        g2d.dispose();
        return scaledImage;
    }

    // ,   ,   ,   
    private void proceedImage(BufferedImage image) {
        BufferedImage scaledImage = getScaledImage(image, scaledWidth, scaledHeight);

        screenData = new HashMap<>();
        mainAreas.forEach(area -> {
            int[] dimensions = getDimensions(area);
            BufferedImage subImage = scaledImage.getSubimage(dimensions[0], dimensions[1], dimensions[2], dimensions[3]);

            int average = IntStream.range(0, dimensions[3])
                    .flatMap(row -> IntStream.range(0, dimensions[2]).map(col -> subImage.getRGB(col, row))).boxed()
                    .reduce(new ColorAveragerer(), (t, u) -> {
                        t.accept(u);
                        return t;
                    }, (t, u) -> {
                        t.combine(u);
                        return t;
                    }).average();

            screenData.put(area, average);
        });
    }

    public ImageHandler(BufferedImage image, LightConfig config) {
        this.config = config;
        proceedImage(image);
    }

    //       ,  considerRate   (    )
    public int getValue(ScreenArea area, Feature feature, Boolean considerRate) {
        Integer intValue = screenData.get(area);
        if (intValue != null) {
            Color color = new Color(intValue);
            if (feature == Feature.COLOR) {
                return color.getRGB();
            } else if (feature == Feature.BRIGHTNESS || feature == Feature.TEMPERATURE) {
                int value = (feature == Feature.BRIGHTNESS) ? getBrightness(color) : getTemperature(color);
                double rate = (feature == Feature.BRIGHTNESS) ? config.getBrightnessRate() : config.getTemperatureRate();
                value = (value < 0) ? 0 : value;
                if (considerRate) {
                    value = 10 + (int) (value * rate);
                }
                return (value > 100) ? 100 : value;
            } else {
                return 0;
            }
        } else {
            calculateArea(area);
            return getValue(area, feature, considerRate);
        }
    }
   
    //    
    private int getBrightness(Color color) {
        return (int) ((color.getRed() * 0.2126f + color.getGreen() * 0.7152f + color.getBlue() * 0.0722f) / 255 * 100);
    }

    //    
    private int getTemperature(Color color) {
        return (int) ((float) (color.getRed() - color.getBlue()) / 255 * 100);
    }

    //   "" 
    private void calculateArea(ScreenArea area) {
        int value = 0;
        switch (area) {
            case TOP:
                value = getAverage(ScreenArea.TOP_LEFT, ScreenArea.TOP_RIGHT);
                break;
            case BOTTOM:
                value = getAverage(ScreenArea.BOTTOM_LEFT, ScreenArea.BOTTOM_RIGHT);
                break;
            case LEFT:
                value = getAverage(ScreenArea.BOTTOM_LEFT, ScreenArea.TOP_LEFT);
                break;
            case RIGHT:
                value = getAverage(ScreenArea.BOTTOM_RIGHT, ScreenArea.TOP_RIGHT);
                break;
            case WHOLE_SCREEN:
                value = getAverage(mainAreas.toArray(new ScreenArea[0]));
                break;
        }
        screenData.put(area, value);
    }

    //      
    private int getAverage(ScreenArea... areas) {
        return Arrays.stream(areas).map(color -> screenData.get(color))
                .reduce(new ColorAveragerer(), (t, u) -> {
                    t.accept(u);
                    return t;
                }, (t, u) -> {
                    t.combine(u);
                    return t;
                }).average();
    }

    //  rgb  int-  
    public static int[] getRgbArray(int color) {
        int[] rgb = new int[3];
        rgb[0] = (color >>> 16) & 0xFF;
        rgb[1] = (color >>> 8) & 0xFF;
        rgb[2] = (color >>> 0) & 0xFF;
        return rgb;
    }

    // int-     rgb
    public static int getRgbInt(int[] pixel) {
        int value = ((255 & 0xFF) << 24) |
                ((pixel[0] & 0xFF) << 16) |
                ((pixel[1] & 0xFF) << 8) |
                ((pixel[2] & 0xFF) << 0);
        return value;
    }

   //         stream API
    private class ColorAveragerer {
        private int[] total = new int[]{0, 0, 0};
        private int count = 0;

        private ColorAveragerer() {
        }

        private int average() {
            int[] rgb = new int[3];
            for (int it = 0; it < total.length; it++) {
                rgb[it] = total[it] / count;
            }

            return count > 0 ? getRgbInt(rgb) : 0;
        }

        private void accept(int i) {
            int[] rgb = getRgbArray(i);
            for (int it = 0; it < total.length; it++) {
                total[it] += rgb[it];
            }
            count++;
        }

        private void combine(ColorAveragerer other) {
            for (int it = 0; it < total.length; it++) {
                total[it] += other.total[it];
            }
            count += other.count;
        }
    }
}




Um zu verhindern, dass das häufige Flackern der Lampe das Auge reizt, wurde eine Schwelle zur Änderung der Parameter eingeführt. Beispielsweise ändert die Lampe den Helligkeitswert nur, wenn die aktuelle Szene im Film mehr als 10 Prozent heller als die vorherige ist.



Vergleich mit einer anderen Analysemethode



Sie könnten fragen: "Warum nicht einfach das Bild auf 2x2 Pixel verkleinern und die resultierenden Werte zählen?" ...

Die Antwort lautet wie folgt: „Basierend auf meinen Experimenten erwies sich der Algorithmus zur Bestimmung der durchschnittlichen Farbe durch Reduzieren der Größe des Bildes (oder seiner Zonen) als weniger stabil und weniger zuverlässig (insbesondere bei der Analyse dunkler Bereiche des Bildes) als der Algorithmus zur Bestimmung des arithmetischen Mittels aller Pixel " .



Es wurden verschiedene Methoden versucht, um die Größe von Bildern zu ändern. Es war möglich, die openCV-Bibliothek für ernsthaftere Arbeiten mit dem Image zu verwenden, aber ich hielt dies für eine Überentwicklung für diese Aufgabe. Zum Vergleich finden Sie unten ein Beispiel für die Definition einer Farbe mithilfe der integrierten schnellen Skalierung der BufferedImage-Klasse und die Berechnung des arithmetischen Mittelwerts. Ich halte Kommentare für überflüssig.







Konfigurieren



Derzeit wird das Programm mithilfe einer JSON-Datei konfiguriert. JSON.simple wurde als Bibliothek zum Parsen der Konfigurationsdatei verwendet .



Die Json-Datei muss den Namen "config.json" haben und sich zur automatischen Konfigurationserkennung im selben Ordner wie das Programm befinden. Wenn die adaptive Helligkeitsfunktion aktiviert ist, werden Sie vom Programm aufgefordert, die Konfigurationsdatei selbst anzugeben, indem Sie das Dateiauswahlfenster öffnen. In der Datei müssen Sie die IP-Adressen der Beleuchtungsgeräte, die "überwachten" Bildzonen für jedes Gerät, die Helligkeits- und Farbtemperaturkoeffizienten oder den Zeitraum ihrer automatischen Installation angeben (der im nächsten Absatz beschrieben wird). Die Regeln zum Ausfüllen der JSON-Datei sind in der README-Datei des Projekts beschrieben.





Alle Änderungen in der Benutzeroberfläche (Lichttaste). Wenn die Taste gedrückt wird, wird die verfügbare Konfigurationsdatei angewendet oder ein Fenster zur Auswahl geöffnet.



Die Koeffizienten sind für eine genauere Einstellung der Bildanalyse erforderlich, um beispielsweise die Lampe etwas dunkler oder umgekehrt heller zu machen. Alle diese Parameter sind optional. Der einzige erforderliche Parameter sind hier die Werte der IP-Adressen der Beleuchtungskörper.



Automatische Einstellung der Gewinnchancen



Das Programm implementiert auch die Funktion der automatischen Anpassung der Koeffizienten in Abhängigkeit von der aktuellen Raumbeleuchtung. Dies geschieht folgendermaßen: Die Webcam Ihres Laptops erstellt eine Momentaufnahme der Umgebung mit einer ausgewählten Frequenz, analysiert deren Helligkeit mithilfe der bereits beschriebenen Algorithmen und stellt dann den Koeffizienten gemäß der folgenden Formel ein:

l=1+x/.100

Dabei ist x die aktuelle Raumhelligkeit in Prozent.



Diese Funktion wird aktiviert, indem ein spezielles Tag in die Konfigurationsdatei geschrieben wird.



Funktionsbeispiel





Fazit



Als Ergebnis der Problemlösung wurde eine Funktionalität entwickelt, mit der Sie Yeelight Smart Lamps als adaptive Hintergrundbeleuchtung von Mediendateien verwenden können. Zusätzlich wurde die Funktion zur Analyse der aktuellen Raumbeleuchtung implementiert. Der gesamte Quellcode ist über einen Link in meinem Github-Repository verfügbar .



Vielen Dank für Ihre Aufmerksamkeit!



PS Ich freue mich über Ergänzungen, Bemerkungen und Hinweise auf Fehler.



All Articles