0.2.0 : ce qu’une vignette webcam fait à la détection de diapositives
Nous publions aujourd’hui sur npm la version 0.2.0 de video-slide-extractor. Ce paquet est un détecteur qui compare l’image par blocs : il échantillonne la vidéo à intervalle fixe, puis décide image par image si le contenu affiché est passé à la diapositive suivante. C’est exactement le détecteur que Video2Any exécute dans votre navigateur. Le code est ouvert, sans dépendance, sous licence MIT, et fonctionne aussi bien dans le navigateur que sous Node.
La 0.1.x publiée en juillet était une copie du code produit prise le 16 juillet. Elle décide correctement sur un enregistrement d’écran propre, mais se trompe sur une bonne partie des enregistrements dont les gens disposent réellement. Deux situations la mettent en défaut ; le produit a traité les deux au cours du mois suivant, et le paquet ne les connaissait pas. La 0.2.0 intègre ces deux situations.
Nous expliquons ci-dessous trois choses, dans cet ordre : à quoi ressemblaient vraiment les vidéos envoyées, quelle conclusion le banc d’essai donnait déjà avant toute modification, et ce que nous avons mesuré après.
Sur cette page
À quoi ressemblaient vraiment les vidéos envoyées
Video2Any enregistre les événements d’extraction depuis le 16 juillet. À ce jour, 96 personnes ont lancé 411 conversions ; 354 sont allées au bout et ont produit 291 fichiers : 188 PowerPoint, 95 PDF et 8 archives d’images. Il s’agit presque toujours de fichiers locaux : dans 398 des 411 conversions, l’utilisateur a choisi une vidéo déjà présente sur la machine où l’onglet était ouvert. (Les deux journées consacrées à nos propres tests automatisés sont exclues de tous les chiffres de cet article.)
Pour les 106 conversions dont le fichier indiquait sa durée, la vidéo durait en moyenne 53 minutes. Ce ne sont pas des démonstrations produit de cinq minutes : ce sont des cours, des réunions et des enregistrements de formation. Le détecteur de juillet n’avait jamais été mesuré sur ce type de vidéo.
Le point le plus intéressant est le nombre de diapositives produites par conversion. Les conversions terminées en ont rendu 64 en moyenne, un chiffre qui paraît normal et qui masque la distribution : 64 conversions ont dépassé 100 diapositives, sept ont dépassé 300, et quatre en ont rendu exactement 900. 900 est le nombre maximal d’échantillons que le balayage peut prendre ; dans ces quatre cas, chaque échantillon a donc été considéré comme une diapositive nouvelle et le détecteur n’en a écarté aucun. L’une de ces vidéos durait 30 minutes.
Le débit n’est pas non plus une valeur fixe : au même réglage, les vidéos de moins d’une heure ont donné environ 6.5 diapositives par minute, celles de plus d’une heure environ 1.0. Une partie de cet écart est réelle, puisqu’une réunion de deux heures change d’écran moins souvent par minute qu’une démonstration courte. Le problème est que le chiffre seul ne dit pas quel côté a raison. Les quatre conversions arrivées au plafond, elles, le disent : si tous les échantillons sont conservés, le détecteur ne décide rien.
Le banc d’essai donnait déjà cette conclusion
Avant toute correction, la 0.2.0 a ajouté au paquet le répertoire bench/. Les vidéos de test sont produites à partir d’une séquence de diapositives connue : la seconde exacte de chaque changement est donc fixée au rendu, et personne n’a besoin de l’étiqueter à la main. Chaque présentation est rendue de trois façons : une version propre, une version compressée à crf 45 pour obtenir du bruit de compression, et une version avec une vignette webcam animée dans un coin.
Sur la présentation du MIT (46 diapositives, un échantillon toutes les 2 secondes en 160×90), la version propre obtient un F1 de 0.966. Avec la vignette webcam par-dessus, le F1 tombe à 0.538 : le détecteur a sorti 125 captures d’un contenu de 46 diapositives, la précision est de 0.368, et 63 % de ce qu’il a renvoyé était une diapositive déjà capturée.
La raison est arithmétique, pas une question de chance. La condition par défaut est que plus de 2 % des blocs changent. Une vignette webcam, un curseur ou un logo animé en boucle occupent à eux seuls plus de 2 % des blocs tant qu’ils continuent de bouger. Chaque image échantillonnée franchit donc le seuil même si aucune diapositive n’a changé. Au bout du compte, le nombre de diapositives que vous obtenez dépend de la caméra de l’intervenant, et non du nombre de pages de la présentation.
Nous avons publié cette ligne telle quelle. Les quatre conversions à 900 diapositives sont cette même ligne observée sur du matériau réel, et c’est elle que la 0.2.0 corrige.
Ce qu’ajoute la 0.2.0
Trois nouvelles fonctions exportées, une option, et les déclarations de types de l’ensemble.
buildActivityMask
Cette fonction reçoit les images échantillonnées, repère les blocs qui changent dans presque toutes les paires d’images consécutives — la vignette, le curseur, l’horloge dans un coin — et renvoie un masque qui les exclut entièrement de la comparaison. Si le masque devait couvrir la majeure partie de l’image, la fonction renvoie
nullau lieu d’un masque. Ce cas compte : lorsque presque toute l’image bouge, ce qui bouge est précisément le contenu à observer, et l’exclure revient à renoncer à détecter.chooseThreshold
Un même seuil fixe ne peut pas convenir à une présentation et à un gros plan de quelqu’un qui parle. Une présentation est immobile : un vrai changement se distingue nettement du bruit. Une image de caméra bouge en permanence, et là 0.02 fait de chaque image une diapositive nouvelle. Cette fonction analyse la distribution des écarts entre images successives, puis renvoie
{ changedRatio, mode }. Le champmodeest exposé volontairement : il vautbimodal,static,motionoudefaultet indique quel type de matériau le détecteur estime avoir devant lui, de sorte que si le résultat est mauvais, vous voyez sur quoi il s’est appuyé au lieu de le supposer.analyzeSamples
Cette fonction exécute les deux étapes précédentes sur les mêmes images et renvoie
{ mask, choice }. C’est ce dont la plupart des appelants ont besoin : vous disposez d’échantillons et vous voulez savoir avec quels paramètres détecter, sans avoir à découvrir au préalable que « avec quels paramètres » recouvre en réalité deux décisions indépendantes.frameDiff({ collect: true })
Avec cette option,
frameDiffrenvoie égalementflags: un octet par bloc, indiquant à quels endroits cette image a changé. C’est utile pour dessiner les zones que le détecteur a considérées comme modifiées. Le champ n’est renvoyé que si vous passezcollect, car un champ qui n’existe normalement pas s’utilise plus correctement qu’un champ qui vaut presque toujours null.
Dans l’ordre où vous les appelleriez, voici tout :
import { analyzeSamples, createSlideDetector } from 'video-slide-extractor';
// frames : échantillons RGBA, un toutes les ~2 s, réduits en 160x90
const { mask, choice } = analyzeSamples(frames, 160, 90);
console.log(choice.mode); // 'bimodal' | 'static' | 'motion' | 'default'
const detect = createSlideDetector(160, 90, {
mask,
changedRatio: choice.changedRatio
});
const kept = [];
frames.forEach((frame, i) => {
if (detect(frame).keep) kept.push(i); // échantillons à capturer en pleine résolution
});Ce que cela apporte, mesuré
Mêmes vidéos de test et même protocole : présentation du MIT, 46 diapositives, version avec vignette, un échantillon 160×90 toutes les 2 s ; une détection est comptée juste si elle tombe à moins de ±2.5 s du changement étiqueté.
| Détection | Captures | Précision | Rappel | F1 | Doublons |
|---|---|---|---|---|---|
| Valeurs par défaut 0.1.x (changedRatio 0.02) | 125 | 0.368 | 1.000 | 0.538 | 0.632 |
| Avec masque d’activité | 54 | 0.815 | 0.957 | 0.880 | 0.185 |
| Avec masque et seuil automatique | 35 | 1.000 | 0.761 | 0.864 | 0.000 |
Le masque seul fait passer les captures de 125 à 54, les doublons de 63 % à 19 %, et le F1 de 0.538 à 0.880. De plus, il n’affecte pas le matériau qui n’en a pas besoin : sur les versions propre et bruitée il renvoie null, donc ces deux lignes donnent exactement le résultat de la 0.1.1.
Le seuil, lui, présente des avantages et des inconvénients, et pour l’expliquer il vaut mieux commencer par la ligne qui se passe mal : sur la présentation propre du MIT, le seuil calculé est plus prudent que l’ancienne constante et le rappel tombe de 0.935 à 0.761. C’est précisément pour cela que DEFAULTS n’a pas changé dans cette version. analyzeSamples sert quand vous ignorez de quel type est le matériau qu’on vous a confié. Si vous le savez, les constantes sont toujours là et c’est vous qui décidez.
Nous avons mesuré ces deux nouvelles lignes avec l’outil du banc d’essai, sur le commit de la publication. Le RESULTS.md publié ne rapporte encore que le jeu de méthodes de la 0.1.x ; y intégrer le masque comme méthode à part entière est la prochaine chose à faire dans bench/.
Ce que nous avons délibérément laissé de côté
Le regroupement global des doublons et le lissage des transitions ne sont pas dans le paquet et n’y seront pas ajoutés. Les deux exigent de voir la vidéo entière d’un coup : quel plan est revenu plus tard, ou si trois captures provenaient en réalité d’un même fondu. C’est le travail de la chaîne de traitement. Un détecteur qui répond seulement si l’image a changé ne devrait pas en plus conserver la vidéo entière.
La densité de diapositives n’y est pas non plus : combien de diapositives un cours de 90 minutes devrait produire est une décision produit, pas une décision de détection. Video2Any a son critère et propose un réglage pour l’ajuster. Une bibliothèque n’a pas à prendre cette décision à votre place ; il lui suffit de vous dire où l’image a changé.
Trois lectures liées :
- Trop de diapositives pour une seule vidéo — comment Video2Any traite la densité de diapositives
- Décoder une vidéo en parallèle, dans un onglet — la chaîne complète dans laquelle ce détecteur est installé
- D’un enregistrement Zoom vers PowerPoint — la vignette webcam apparaît surtout dans ce type d’enregistrement
La suite
- Une durée de maintien. Un contenu nouveau devra rester immobile deux bonnes secondes avant de compter comme une diapositive. C’est cela qui met vraiment fin aux conversions à 900 : le masque retire la vignette, mais un fondu rapide ou un déplacement dans la barre de temps déclenchent encore la condition. Dans Video2Any, cela fonctionne déjà. Cela arrivera dans le paquet quand son interface sera arrêtée, car une durée de maintien n’a pas de sens sans une cadence d’échantillonnage, et le paquet ignore volontairement à quelle fréquence vous avez échantillonné.
- Remplacer le matériau synthétique par une vraie caméra. La vidéo de test actuelle est une animation rendue dans un coin. Une webcam filmant une personne réelle constitue un test plus difficile et plus proche de la réalité, et c’est là-dessus que le masque devrait être mesuré.
- La défaillance qu’aucune des deux méthodes ne résout. Lorsque deux diapositives consécutives ne diffèrent que par une puce ajoutée, en 160×90 cet écart reste collé au minimum qui déclenche la condition : les diapositives construites par étapes sont donc perdues entièrement. Le banc d’essai le montre même sur un rendu synthétique. C’est un problème de résolution et de règle de notation, et
docs/evaluation.mdfait désormais de la manière de compter ces constructions un paramètre à déclarer et à publier, plutôt qu’une chose laissée à l’interprétation de chaque évaluateur. - Où la calibration devrait avoir lieu. Video2Any répartit la vidéo sur quatre workers et chacun calibre son seuil sur sa propre tranche : le même fichier donne donc des présentations légèrement différentes selon le nombre de cœurs de la machine. Ce n’est pas une propriété souhaitable. La solution évidente serait de calibrer une seule fois, globalement. Nous l’avons construite et mesurée : sur une réunion de 29 minutes où ffmpeg compte sept coupes réelles, la calibration par tranche en a trouvé six et la calibration globale seulement quatre. Calibrer globalement n’est pas nécessairement meilleur ; le paquet ne l’adoptera donc pas par défaut tant qu’il n’existera pas une version qui le soit.
Essayez
L’installation tient en une commande, sans aucune dépendance, et cela fonctionne sur n’importe quel enregistrement que vous avez sous la main :
npm i video-slide-extractorSi vous construisez quelque chose avec, ou si vous disposez d’un matériau qui le met en défaut, envoyez-le au dépôt. Une vidéo qui laisse le détecteur sans réponse nous vaut plus qu’une étoile : c’est ainsi que la vidéo de test avec webcam a fini par exister.