salut!
Je pense que beaucoup de ceux qui s'intéressent à une maison intelligente ou simplement à un agencement technologique de leur maison ont pensé au système d'éclairage «atmosphérique» et non standard.
Une façon d'éclairer une pièce d'une manière aussi "inhabituelle" tout en regardant des films est proposée par Philips avec la technologie Ambilight intégrée aux téléviseurs
Dans cet article, vous découvrirez l'implémentation d'Ambilight avec les ampoules intelligentes Xiaomi Yeelight!
À propos d'Ambilight
Qui ne sait pas - La technologie Ambilight est un rétroéclairage intégré aux téléviseurs, qui analyse l'image couleur du cadre sur l'écran du téléviseur et reproduit la lumière diffusée autour du périmètre du téléviseur.
Avantages d'Ambilight:
- , ;
- ;
- , .
En général, Ambilight est une technologie assez intéressante, et la confirmation de ce fait est la présence d'un grand nombre d'options différentes pour sa mise en œuvre «artisanale», présentées sur Internet. Cependant, ils sont majoritairement basés sur l'utilisation d'une bande LED adressable collée à l'arrière du couvercle du téléviseur / moniteur / ordinateur portable. Pour une telle mise en œuvre, il est nécessaire de disposer d'au moins un contrôleur externe physique chargé de contrôler les LED. Cela nécessite des connaissances spécifiques de la part d'une personne qui souhaite installer un tel système. Par conséquent, comme alternative, je propose la version la plus "proger" et plutôt simple d'un tel rétroéclairage utilisant des lampes intelligentes.
Quelles sont ces lampes intelligentes?
Pour créer cette option d'éclairage, vous aurez besoin de tout appareil d'éclairage de la marque Yeelight (une filiale de Xiaomi) ou Xiaomi (mais uniquement ceux qui mentionnent Yeelight dans le nom). Cela signifie que l'appareil est intégré dans l'écosystème de la maison intelligente Xiaomi et est contrôlé via l'application Yeelight.
À mon avis, le rétroéclairage adaptatif n'est pas la chose pour laquelle quelqu'un courra acheter une lampe intelligente Xiaomi (pour une somme d'argent substantielle, d'ailleurs). Cependant, comme pour moi, c'est une bonne opportunité d'étendre la fonctionnalité d'une lampe existante à la maison. Dans tous les cas, en tant que propriétaire de deux lampes Xiaomi, je peux dire qu'après deux mois d'utilisation, je n'ai que des impressions agréables.
L'application Yeelight joue un rôle important dans la mise en œuvre de ce projet, car elle a un paramètre utile - le mode développeur .
Dans les dernières mises à jour, il a été renommé «Contrôle LAN»
L'écosystème de la maison intelligente moderne est basé sur l'échange de données entre appareils via le protocole wi-fi. Chaque appareil intelligent dispose d'un module Wi-Fi intégré qui vous permet de vous connecter à un réseau sans fil local. Grâce à cela, l'appareil est contrôlé via le service cloud de la maison intelligente. Cependant, le mode développeur vous permet de communiquer directement avec l'appareil en envoyant des demandes à l'adresse IP allouée à l'appareil (l'adresse de l'appareil se trouve dans l'application Yeelight dans les informations de l'appareil). Ce mode garantit la réception des données des appareils qui se trouvent dans le même réseau local que la lampe intelligente. Le site Web Yeelight propose une petite démonstration de la fonctionnalité du mode développeur.
Grâce à cette option, il est possible d'implémenter la fonction d'éclairage adaptatif et de l'intégrer dans le player open source.
Définition fonctionnelle
Un autre article sera consacré aux difficultés (et aux moyens de les résoudre) qu'un ingénieur peut rencontrer lorsqu'il envisage de concevoir une telle chose, ainsi que les progrès généraux dans la mise en œuvre du plan.
Si vous vous intéressez exclusivement à un programme prêt à l'emploi, vous pouvez accéder directement à l'élément "Pour ceux qui veulent juste utiliser un lecteur prêt à l'emploi".
Tout d'abord, décidons des tâches que le projet en cours de développement doit résoudre. Les principaux points du mandat de ce projet:
- Il est nécessaire de développer des fonctionnalités qui permettent de modifier dynamiquement les paramètres (couleur ou luminosité / température de la lumière dans le cas de l'utilisation d'un appareil sans LED RVB) de la lampe intelligente, en fonction de l'image actuelle dans la fenêtre du lecteur multimédia.
- .
- , «» .
- .
- .
,
Si vous ne souhaitez pas comprendre la mise en œuvre de l'éclairage adaptatif et que vous souhaitez simplement utiliser un lecteur prêt à l'emploi, vous pouvez télécharger le fichier jar déjà assemblé à partir du référentiel , puis assurez-vous de lire la section Avant de commencer dans le fichier README à partir du référentiel.
L'étape initiale du développement du projet sera la définition d'un player pour intégrer une fonction et d'une bibliothèque pour la communication avec une lampe intelligente.
Mon choix s'est porté sur le lecteur vlcj et la bibliothèque Yapi , écrits en Java . Maven a été utilisé comme outil de construction .
Vlcj est un framework qui vous permet d'intégrer un lecteur VLC natif dans une application Java, ainsi que de gérer le cycle de vie du lecteur via du code java. L'auteur du framework dispose également d'une version de démonstration du lecteur , qui répète presque complètement l'interface et les fonctionnalités du lecteur VLC. La version la plus stable du lecteur pour le moment est la version 3. Elle sera utilisée dans le projet.
Interface du lecteur Vlcj avec fenêtres supplémentaires ouvertes
Avantages du lecteur vlcj:
- un grand nombre de formats vidéo pris en charge, qui est une fonctionnalité de longue date du lecteur VLC;
- Java en PL, qui permet d'ouvrir le player sur un grand nombre de systèmes d'exploitation (dans ce cas, nous ne sommes limités que par l'implémentation du player VLC, qui est inextricablement lié à une application java).
Désavantages:
- conception obsolète du lecteur, qui est résolue par sa propre implémentation de l'interface;
- Avant d'utiliser le programme, vous devez installer un lecteur VLC et Java version 8 ou supérieure, ce qui est certainement un inconvénient.
L'utilisation de Yapi comme bibliothèque pour se connecter aux gadgets intelligents Yeelight peut être justifiée principalement par la simplicité et, deuxièmement, par la rareté des solutions toutes faites. Pour le moment, il n'existe pas beaucoup d'outils tiers pour contrôler les lampes intelligentes, en particulier dans le langage Java.
Le principal inconvénient de la bibliothèque Yapi est qu'aucune de ses versions n'est présente dans le référentiel Maven, donc avant de compiler le code du projet, vous devez installer manuellement Yapi dans le référentiel local (l'ensemble de l'installation est décrit dans le fichier README du référentiel).
Algorithme d'analyse d'image
Le principe de l'éclairage dynamique sera basé sur une analyse périodique des couleurs du cadre actuel.
À la suite de l'étape d'essai et d'erreur, le principe suivant d'analyse d'image a été développé:
À la fréquence spécifiée, le programme prend une capture d'écran du lecteur multimédia et reçoit un objet de la classe BufferedImage. Ensuite, avec l'algorithme intégré le plus rapide, l'image d'origine est redimensionnée à 20x20 pixels.
Ceci est nécessaire pour la vitesse de l'algorithme, au nom de laquelle nous pouvons sacrifier une certaine précision dans la détermination de la couleur. Il est également nécessaire afin de minimiser la dépendance du temps de traitement de l'image sur la résolution du fichier multimédia actuel.
Ensuite, l'algorithme divise l'image résultante en quatre zones "de base" (en haut à gauche, en bas à gauche, etc.) d'une taille de 10x10 pixels.
Zones «de base»
Ce mécanisme est mis en œuvre pour fournir une analyse indépendante des différentes zones d'image, ce qui vous permet de placer le dispositif d'éclairage à un certain endroit de la pièce dans le futur et d'indiquer quelle zone d'image il doit «suivre». Lorsqu'elle est utilisée avec un programme multi-lampes, cette fonctionnalité rend l'éclairage dynamique beaucoup plus atmosphérique.
Ensuite, pour chaque zone de l'image, une couleur moyenne est calculée en calculant la moyenne arithmétique séparément pour les trois composantes de couleur (rouge, vert, bleu) de chaque pixel et en agençant les données résultantes en une seule valeur de couleur.
Grâce aux quatre valeurs résultantes, nous pouvons:
- 5 : , , , ( «» );
- :
r, g, b – // -
Pour une mécanique efficace et évolutive de calcul des paramètres d'image, toutes les données supplémentaires (pas les zones «de base», la température et la luminosité des couleurs) sont calculées «paresseusement», c'est-à-dire comme requis.
Tout le code de traitement d'image tient dans une classe ImageHandler:
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;
}
}
}
Afin d'éviter que le scintillement fréquent de la lampe n'irrite l'œil, un seuil de modification des paramètres a été introduit. Par exemple, la lampe changera la valeur de luminosité uniquement si la scène actuelle du film est plus de 10% plus lumineuse que la précédente.
Comparaison avec une autre méthode d'analyse
Vous pourriez vous demander: "Pourquoi ne pas simplement réduire l'image à 2x2 pixels et compter les valeurs résultantes?" ...
La réponse serait: «Sur la base de mes expériences, l'algorithme de détermination de la couleur moyenne en réduisant la taille de l'image (ou de ses zones) s'est avéré moins stable et moins fiable (surtout lors de l'analyse des zones sombres de l'image) que l'algorithme basé sur la détermination de la moyenne arithmétique de tous les pixels " .
Plusieurs méthodes ont été essayées pour redimensionner les images. Il était possible d'utiliser la bibliothèque openCV pour un travail plus sérieux avec l'image, mais j'ai considéré que c'était une sur-ingénierie pour cette tâche. À titre de comparaison, voici un exemple de définition d'une couleur à l'aide de la mise à l'échelle rapide intégrée de la classe BufferedImage et de calcul de la moyenne arithmétique. Je pense que les commentaires sont superflus.
Configurer
Pour le moment, le programme est configuré à l'aide d'un fichier json. JSON.simple a été utilisé comme bibliothèque pour analyser le fichier de configuration .
Le fichier json doit être nommé «config.json» et placé dans le même dossier que le programme pour la détection automatique de la configuration, sinon, lorsque la fonction de luminosité adaptative est activée, le programme vous demandera de spécifier le fichier de configuration vous-même en ouvrant la fenêtre de sélection de fichier. Dans le fichier, vous devez spécifier les adresses IP des appareils d'éclairage, les zones d'image «surveillées» pour chaque appareil, les coefficients de luminosité et de température de couleur, ou la période de leur installation automatique (qui sera décrite dans le paragraphe suivant). Les règles de remplissage du fichier json sont décrites dans le fichier README du projet.
Tous les changements dans l'interface (bouton lumineux). Lorsqu'on appuie sur le bouton, le fichier de configuration disponible sera appliqué ou une fenêtre de sélection s'ouvrira.
Les coefficients sont nécessaires pour un réglage plus précis de l'analyse d'image, par exemple, pour rendre la lampe légèrement plus sombre ou au contraire plus claire. Tous ces paramètres sont facultatifs. Les seuls paramètres requis ici sont les valeurs des adresses IP des luminaires.
Réglage automatique des cotes
De plus, le programme met en œuvre la fonction d'ajustement automatique des coefficients en fonction de l'éclairage actuel de la pièce. Cela se passe comme ceci: la webcam de votre ordinateur portable à une fréquence sélectionnée prend une photo de l'environnement, analyse sa luminosité selon les algorithmes déjà décrits, puis règle le coefficient selon la formule:
Cette fonction est activée en écrivant une balise spéciale dans le fichier de configuration.
Exemple fonctionnel
Conclusion
Suite à la résolution du problème, une fonctionnalité a été développée qui vous permet d'utiliser les lampes intelligentes Yeelight comme rétroéclairage adaptatif des fichiers multimédias. De plus, la fonction d'analyse de l'éclairage actuel de la pièce a été mise en œuvre. Tout le code source est disponible à partir d'un lien dans mon référentiel github .
Merci à tous pour votre attention!
PS Je serai heureux de tous les ajouts, remarques et indications d'erreurs.