Les barrages anti-visage, c'est-à-dire qu'un grand nombre de barrages flottent, mais ne bloquent pas les personnes sur l'écran vidéo. On dirait qu'ils flottent derrière les gens.
Le Machine Learning est populaire depuis plusieurs années, mais beaucoup de gens ne savent pas que ces fonctionnalités peuvent également être exécutées dans les navigateurs
Cet article présente l'optimisation pratique de la vidéo ; processus de barrages, la fin de l'article répertorie quelques scénarios applicables pour cette solution, dans l'espoir d'ouvrir quelques idées.
mediapipe Demo (https://google.github.io/mediapipe/) montre
up Télécharger la vidéo
Le calcul de l'arrière-plan du serveur extrait la zone du portrait dans l'écran vidéo et la convertit en svg pour le stockage
Pendant que le client lit la vidéo, le svg est téléchargé depuis le serveur et combiné avec le barrage. Le barrage n'est pas affiché dans la zone portrait. En même temps, en temps réel. (appareil hôte) extrait la zone de portrait de l'écran, la convertit en svg
Exportez le contour du portrait dans une image et définissez l'image de masque du calque de barrage (https:/ /developer.mozilla.org /zh-CN/docs/Web/CSS/mask-image)
Par rapport à la solution traditionnelle (live SEI en temps réel)Cette pratique a finalement optimisé l'utilisation du processeur à environ 5 % (Macbook M1 2020), atteignant un état prêt pour la production.
La précision est trop faible, le visage est étroit et il y a un chevauchement évident entre le barrage et les bords du visage du personnage
#🎜🎜 #[{score: 0.8,keypoints: [{x: 230, y: 220, score: 0.9, score: 0.99, name: "nose"},{x: 212, y: 190, score: 0.8, score: 0.91, name: "left_eye"},...],keypoints3D: [{x: 0.65, y: 0.11, z: 0.05, score: 0.99, name: "nose"},...],segmentation: {maskValueToLabel: (maskValue: number) => { return 'person' },mask: {toCanvasImageSource(): ...toImageData(): ...toTensor(): ...getUnderlyingType(): ...}}}]
Excellente précision (avec le modèle BlazePose L'effet est cohérent), l'utilisation du processeur est réduite d'environ 15 % par rapport au modèle BlazePose et les performances sont supérieures, mais les informations sur les points du corps ne sont pas fournies dans les données renvoyées
Exemple de structure de données de retour#🎜 🎜#{maskValueToLabel: (maskValue: number) => { return 'person' },mask: {toCanvasImageSource(): ...toImageData(): ...toTensor(): ...getUnderlyingType(): ...}}
Implémentation de la première version
Reportez-vous à l'implémentation officielle du modèle MediaPipe SelfieSegmentation (https://github.com/tensorflow/tfjs-models/blob/master/body-segmentation /README.md#bodysegmentationdrawmask), sans optimisation Le CPU occupe environ 70%
const canvas = document.createElement('canvas')canvas.width = videoEl.videoWidthcanvas.height = videoEl.videoHeightasync function detect (): Promise<void> {const segmentation = await segmenter.segmentPeople(videoEl)const foregroundColor = { r: 0, g: 0, b: 0, a: 0 }const backgroundColor = { r: 0, g: 0, b: 0, a: 255 } const mask = await toBinaryMask(segmentation, foregroundColor, backgroundColor) await drawMask(canvas, canvas, mask, 1, 9)// 导出Mask图片,需要的是轮廓,图片质量设为最低handler(canvas.toDataURL('image/png', 0)) window.setTimeout(detect, 33)} detect().catch(console.error)
Réduire la fréquence d'extraction et équilibrer l'expérience-performance
Vidéo générale 30FPS, essayez le barrage fréquence de rafraîchissement du masque (ci-après dénommé Masque) à 15FPS, l'expérience est toujours acceptablewindow.setTimeout(detect, 66) // 33 => 66
L'analyse du code source, combinée à l'impression des informations de segmentation, révèle que segmentation.mask.toCanvasImageSource peut obtenir l'objet ImageBitmap d'origine, qui correspond aux informations extraites par le modèle. Essayez d'écrire votre propre code pour convertir un ImageBitmap en masque au lieu d'utiliser l'implémentation par défaut fournie par la bibliothèque open source.
Principe de mise en œuvre
async function detect (): Promise<void> {const segmentation = await segmenter.segmentPeople(videoEl) context.clearRect(0, 0, canvas.width, canvas.height)// 1. 将`ImageBitmap`绘制到 Canvas 上context.drawImage(// 经验证 即使出现多人,也只有一个 segmentationawait segmentation[0].mask.toCanvasImageSource(),0, 0,canvas.width, canvas.height)// 2. 设置混合模式context.globalCompositeOperation = 'source-out'// 3. 反向填充黑色context.fillRect(0, 0, canvas.width, canvas.height)// 导出Mask图片,需要的是轮廓,图片质量设为最低handler(canvas.toDataURL('image/png', 0)) window.setTimeout(detect, 66)}
Les étapes 2 et 3 sont équivalentes à remplir le contenu en dehors de la zone du portrait avec du noir (remplissage inversé ImageBitmap), afin de coopérer avec le css (masque- image), sinon il ne sera visible que lorsque le barrage flottera vers la zone du portrait (exactement à l'opposé de l'effet cible).
globalCompositeOperation MDN(https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)
此时,CPU 占用 33% 左右
我原先认为toDataURL是由浏览器内部实现的,无法再进行优化,现在只有优化toDataURL这个耗时操作了。
虽没有替换实现,但可使用 OffscreenCanvas (https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas)+ Worker,将耗时任务转移到 Worker 中去, 避免占用主线程,就不会影响用户体验了。
并且ImageBitmap实现了Transferable接口,可被转移所有权,跨 Worker 传递也没有性能损耗(https://hughfenghen.github.io/fe-basic-course/js-concurrent.html#%E4%B8%A4%E4%B8%AA%E6%96%B9%E6%B3%95%E5%AF%B9%E6%AF%94)。
// 前文 detect 的反向填充 ImageBitmap 也可以转移到 Worker 中// 用 OffscreenCanvas 实现, 此处略过 const reader = new FileReaderSync()// OffscreenCanvas 不支持 toDataURL,使用 convertToBlob 代替offsecreenCvsEl.convertToBlob({type: 'image/png',quality: 0}).then((blob) => {const dataURL = reader.readAsDataURL(blob)self.postMessage({msgType: 'mask',val: dataURL})}).catch(console.error)
可以看到两个耗时的操作消失了
此时,CPU 占用 15% 左右
继续分析,上图重新计算样式(紫色部分)耗时约 3ms
Demo 足够简单很容易推测到是这行代码导致的,发现 imgStr 大概 100kb 左右(视频分辨率 1280x720)。
danmakuContainer.style.webkitMaskImage = `url(${imgStr})
通过canvas缩小图片尺寸(360P甚至更低),再进行推理。
优化后,导出的 imgStr 大概 12kb,重新计算样式耗时约 0.5ms。
此时,CPU 占用 5% 左右
虽然提取 Mask 整个过程的 CPU 占用已优化到可喜程度。
当在画面没人的时候,或没有弹幕时候,可以停止计算,实现 0 CPU 占用。
无弹幕判断比较简单(比如 10s 内收超过两条弹幕则启动计算),也不在该 SDK 实现范围,略过
第一步中为了高性能,选择的模型只有ImageBitmap,并没有提供肢体点位信息,所以只能使用getImageData返回的像素点值来判断画面是否有人。
画面无人时,CPU 占用接近 0%
依赖包的提交较大,构建出的 bundle 体积:684.75 KiB / gzip: 125.83 KiB
所以,可以进行异步加载SDK,提升页面加载性能。
这个两步前端工程已经非常成熟了,略过细节。
注意事项
本期作者
刘俊
Ingénieur Développement Senior chez Bilibili
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!