Web 端即時防擋臉彈幕(基於機器學習)

王林
發布: 2023-06-10 13:03:46
轉載
1244 人瀏覽過

防擋臉彈幕,也就是大量彈幕飄過,但不會遮擋影片畫面中的人物,看起來像是從人物背後飄過去的。

機器學習已經火了好幾年了,但很多人都不知道瀏覽器中也能運行這些能力;

本文介紹在視頻彈幕方面的實踐優化過程,文末列舉了一些本方案可適用的場景,期望能開啟一些腦洞。

mediapipe Demo(https://google.github.io/mediapipe/)展示

Web 端实时防挡脸弹幕(基于机器学习)

主流防擋臉彈幕實現原理

點播

up 上傳影片

伺服器後台計算提取影片畫面中的人像區域,轉換成svg 儲存

客戶端播放影片的同時,從伺服器下載svg與彈幕合成,人像區域不顯示彈幕

 直播

  1. 主播推流時,即時(主播設備)從畫面提取人像區域,轉換成svg
  2. 將svg 資料合併到視訊串流中(SEI),推流至伺服器
  3. 客戶端播放影片同時,從視訊串流(SEI)解析出svg
  4. 將svg 與彈幕合成,人像區域不顯示彈幕

本文實現方案

客戶端播放視頻同時,實時從畫面提取人像區域信息,將人像區域信息導出成圖片與彈幕合成,人像區域不顯示彈幕。

實作原理

  1. 採用機器學習開源程式庫從視訊畫面即時擷取人像輪廓,如Body Segmentation(https://github.com/tensorflow/tfjs-models/blob/ master/body-segmentation/README.md)
  2. 將人像輪廓轉導出為圖片,設定彈幕層的 mask-image(https://developer.mozilla.org/zh-CN/docs/ Web/CSS/mask-image)

 比較傳統(直播SEI實時)方案

優點:

  • 易於實現;只需要Video標籤一個參數,無需多端協同配合
  • 無網路頻寬消耗

缺點:

  • 理論效能極限劣於傳統方案;相當於效能資源換網絡資源

面臨的問題

眾所周知,JavaScript 的效能較差,因此不適合用於處理CPU 密集型任務。由官方demo變成工程實踐,最大的挑戰就是-效能。

這次實作最終將 CPU 佔用優化到 5% 左右(2020 M1 Macbook),達到生產可用狀態。

實踐調優過程

選擇機器學習模型

BodyPix (https://github.com/tensorflow/tfjs-models/blob/master/body-segmentation/ src/body_pix/README.md)

精確度太差,臉部狹窄,有很明顯的彈幕與人物臉部邊緣重疊現象

Web 端实时防挡脸弹幕(基于机器学习)

# BlazePose(https://github.com/tensorflow/tfjs-models/blob/master/pose-detection/src/blazepose_mediapipe/README.md)

#精確度優秀,且提供了肢體點位資訊,但效能較差

Web 端实时防挡脸弹幕(基于机器学习)

傳回資料結構範例

[{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(): ...}}}]
登入後複製

MediaPipe SelfieSegmentation (https://github.com/tensorflow/tfjs-models/blob/master /body-segmentation/src/selfie_segmentation_mediapipe/README.md)

精確度優秀(跟BlazePose 模型效果一致),CPU 佔用相對BlazePose 模型降低15% 左右,效能取勝,但返回資料中不提供肢體點位資訊

返回資料結構範例

{maskValueToLabel: (maskValue: number) => { return 'person' },mask: {toCanvasImageSource(): ...toImageData(): ...toTensor(): ...getUnderlyingType(): ...}}
登入後複製

初版實作

參考MediaPipe SelfieSegmentation 模型 官方實作(https://github.com/tensorflow/tfjs-models/blob /master/body-segmentation/README.md#bodysegmentationdrawmask),未做優化的情況下 CPU 佔用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)
登入後複製

降低提取頻率,平衡性能-體驗

#一般視頻30FPS,嘗試彈幕遮罩(後稱Mask)刷新頻率降為15FPS,體驗上還能接受

window.setTimeout(detect, 66) // 33 => 66
登入後複製

此時,CPU 佔用50% 左右

解決效能瓶頸

Web 端实时防挡脸弹幕(基于机器学习)

分析火焰圖可發現,效能瓶頸在 toBinaryMask 和 toDataURL

重寫toBinaryMask

分析原始碼,結合列印segmentation的訊息,發現segmentation.mask .toCanvasImageSource可取得原始ImageBitmap對象,即是模型擷取出來的資訊。嘗試自己編寫程式碼,將ImageBitmap轉換為遮罩(Mask),以取代使用開源程式庫所提供的預設實作。

實現原理

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)}
登入後複製

第2、3 步相當於給人像區域外的內容填充黑色(反向填充ImageBitmap),是為了配合css(mask-image), 不然只有當彈幕飄到人像區域才可見(與目標效果正好相反)。

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)
登入後複製

Web 端实时防挡脸弹幕(基于机器学习)

可以看到两个耗时的操作消失了

此时,CPU 占用 15% 左右

降低分辨率

继续分析,上图重新计算样式(紫色部分)耗时约 3ms

Demo 足够简单很容易推测到是这行代码导致的,发现 imgStr 大概 100kb 左右(视频分辨率 1280x720)。

danmakuContainer.style.webkitMaskImage = `url(${imgStr})
登入後複製

通过canvas缩小图片尺寸(360P甚至更低),再进行推理。

优化后,导出的 imgStr 大概 12kb,重新计算样式耗时约 0.5ms。

此时,CPU 占用 5% 左右

Web 端实时防挡脸弹幕(基于机器学习)

启动条件优化

虽然提取 Mask 整个过程的 CPU 占用已优化到可喜程度。

当在画面没人的时候,或没有弹幕时候,可以停止计算,实现 0 CPU 占用。

无弹幕判断比较简单(比如 10s 内收超过两条弹幕则启动计算),也不在该 SDK 实现范围,略过

 判定画面是否有人

第一步中为了高性能,选择的模型只有ImageBitmap,并没有提供肢体点位信息,所以只能使用getImageData返回的像素点值来判断画面是否有人。

画面无人时,CPU 占用接近 0%

发布构建优化

依赖包的提交较大,构建出的 bundle 体积:684.75 KiB / gzip: 125.83 KiB

所以,可以进行异步加载SDK,提升页面加载性能。

  1. 分别打包一个 loader,一个主体
  2. 由业务方 import loader,首次启用时异步加载主体

这个两步前端工程已经非常成熟了,略过细节。

运行效果

Web 端实时防挡脸弹幕(基于机器学习)

总结

过程

  • 选择高性能模型后,初始状态 CPU 70%
  • 降低 Mask 刷新频率(15FPS),CPU 50%
  • 重写开源库实现(toBinaryMask),CPU 33%
  • 多线程优化,CPU 15%
  • 降低分辨率,CPU 5%
  • 判断画面是否有人,无人时 CPU 接近 0%

CPU 数值指主线程占用

注意事项

  • 兼容性:Chrome 79及以上,不支持 Firefox、Safari。因为使用了OffscreenCanvas
  • 不应创建多个或多次创建segmenter实例(bodySegmentation.createSegmenter),如需复用请保存实例引用,因为:
  • 创建实例时低性能设备会有明显的卡顿现象
  • 会内存泄露;如果无法避免,这是mediapipe 内存泄露 解决方法(https://github.com/google/mediapipe/issues/2819#issuecomment-1160335349)

经验

  • 优化完成之后,提取并应用 Mask 关键计算量在 GPU (30%左右),而不是 CPU
  • 性能优化需要业务场景分析,防挡弹幕场景可以使用低分辨率、低刷新率的 mask-image,能大幅减少计算量
  • 该方案其他应用场景:
  • 替换/模糊人物背景
  • 人像马赛克
  • 人像抠图
  • 卡通头套,虚拟饰品,如猫耳朵、兔耳朵、带花、戴眼镜什么的(换一个模型,略改)
  • 关注Web 神经网络 API (https://mp.weixin.qq.com/s/v7-xwYJqOfFDIAvwIVZVdg)进展,以后实现相关功能也许会更简单

本期作者

Web 端实时防挡脸弹幕(基于机器学习)

                                                                               刘俊   

嗶哩嗶哩資深開發工程師

以上是Web 端即時防擋臉彈幕(基於機器學習)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:51cto.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!