前言

WebAssembly 出来已经很久了,但是一直都没有实践过,实在是不应该,所以就趁这次国庆假期浅学一下吧。毛主席说过,“实践是检验真理的唯一标准”,所以我们今天就实现一个“视频实时滤镜效果”的功能。可直接看代码。

基本原理介绍

视频处理

我们知道,视频其实是由一幅幅的图像组成的,每一幅图像称为一“帧”。所以,我们可以通过 canvas 来获取视频的图像数据,对图像数据进行处理完后再绘制到 canvas 上去,然后通过 requestAnimationFrame 让图像动起来,代码大致如下:

function draw() {...// 将当前视频播放的“帧”绘制到 canvas 上面context2D.drawImage(video,0,0,video.videoWidth,video.videoHeight,0,0,canvas.width,canvas.height)// 得到图像数据const pixels = context2D.getImageData(0, 0, canvas.width, canvas.height)// 处理const newData = filter(...)// 修改 canvas 上的内容pixels.data.set(newData)context2D.putImageData(pixels, 0, 0)...requestAnimationFrame(draw)
}

帧率

为了衡量我们的滤镜算法效率,我们需要计算图像的帧率(FPS),大致思想是先取最近 20 次 draw 函数的平均执行时间,然后用其除 1000:

function draw() {const timeStart = performance.now()...let timeUsed = performance.now() - timeStartarr.push(timeUsed)calcFPS(arr)requestAnimationFrame(draw)}function calcFPS(arr: number[]) {const n = 20if (arr.length > n) {arr.shift()} else {return NaN}let averageTime =arr.reduce((pre, item) => {return pre + item}, 0) / nreturn 1000 / averageTime
}

滤镜算法

本文采用图像处理技术中常用的卷积操作来对图像添加滤镜,卷积操作中需要使用“卷积核”,比如下面这个卷积核可以对图像起到锐化的效果:

[[-1, -1, -1],[-1, 9, -1],[-1, -1, -1],
]

理解了卷积操作的含义后,实现一个卷积算法:

function filterByJS( data: Uint8ClampedArray,width: number,height: number,kernel: number[][] ) {const h = kernel.length,w = hconst half = Math.floor(h / 2)for (let y = half; y < height - half; ++y) {for (let x = half; x < width - half; ++x) {const px = (y * width + x) * 4let r = 0,g = 0,b = 0for (let cy = 0; cy < h; ++cy) {for (let cx = 0; cx < w; ++cx) {const cpx = ((y + (cy - half)) * width + (x + (cx - half))) * 4r += data[cpx + 0] * kernel[cy][cx]g += data[cpx + 1] * kernel[cy][cx]b += data[cpx + 2] * kernel[cy][cx]}}data[px + 0] = r > 255 ? 255 : r < 0 ? 0 : rdata[px + 1] = g > 255 ? 255 : g < 0 ? 0 : gdata[px + 2] = b > 255 ? 255 : b < 0 ? 0 : b}}return data
}

有了上面的知识做铺垫后,我们就可以实现一个 JS 版本的滤镜功能了:

不过接下来才是我们的重点,实现一个 WebAssembly 的版本。

WebAssembly 版本

首先得选一门语言,C/C++ 和 Rust 是个不错的选择,奈何臣妾实在是不会,所以只能选好学又易上手的 Golang 了。

首先,我们新建一个 Golang 项目,并添加我们的代码:

package mainimport ("reflect""syscall/js""unsafe"
)// 转换一下
func parseKernel(kernel js.Value) [3][3]int {var arr [3][3]intfor i := 0; i < 3; i++ {for j := 0; j < 3; j++ {arr[i][j] = kernel.Index(i).Index(j).Int()}}return arr
}// 对大于 255,小于 0 的像素值进行处理
func getVal(val int) uint8 {...
}// 开辟一块内存空间,并返回指针给 JS 侧,JS 侧使用方式:
// const {internalPtr: ptr} = window.initShareMemory(size)
func initShareMemory(this js.Value, args []js.Value) any {size := args[0].Int()buffer := make([]uint8, size)boxedPtr := unsafe.Pointer(&buffer)boxedPtrMap := map[string]interface{}{"internalptr": boxedPtr,}return js.ValueOf(boxedPtrMap)
}// 滤镜算法,JS 侧使用方式:
// window.filterByGO(ptr, canvas.width, canvas.height, kernel)
func filterByGO(this js.Value, args []js.Value) any {width := args[1].Int()height := args[2].Int()size := width * height * 4sliceHeader := &reflect.SliceHeader{Data: uintptr(args[0].Int()),Len:size,Cap:size,}ptr := (*[]uint8)(unsafe.Pointer(sliceHeader))kernel := parseKernel(args[3])w := len(kernel)half := w / 2for y := half; y < height-half; y++ {for x := half; x < width-half; x++ {px := (y*width + x) * 4r := 0g := 0b := 0for cy := 0; cy < w; cy++ {for cx := 0; cx < w; cx++ {cpx := ((y+(cy-half))*width + (x + (cx - half))) * 4r += int((*ptr)[cpx+0]) * kernel[cy][cx]g += int((*ptr)[cpx+1]) * kernel[cy][cx]b += int((*ptr)[cpx+2]) * kernel[cy][cx]}}(*ptr)[px+0] = getVal(r)(*ptr)[px+1] = getVal(g)(*ptr)[px+2] = getVal(b)}}return nil
}func main() {quit := make(chan interface{})js.Global().Set("filterByGO", js.FuncOf(filterByGO))js.Global().Set("initShareMemory", js.FuncOf(initShareMemory))<-quit
}

Golang 部分提供了两个方法供 JS 调用,为了避免修改图像数据的时候 JS 每次都向 Golang 拷贝数据,我们这里采用共享内存的方式来传递数据,实现方法如 initShareMemory 所示。而 filterByGO 是我们的滤镜算法,其代码跟之前介绍的 JS 版类似。

然后 JS 侧就可以按照如下方式来使用:

WebAssembly.instantiateStreaming(fetch('/main.wasm'), go.importObject).then((result) => {const goInstance = result.instancego.run(goInstance)const size = canvas.height * canvas.width * 4// 得到内存的指针const {internalptr: ptr} = window.initShareMemory(size)// 通过这块内存实例化一个 Uint8ClampedArray 对象 mem,mem 和 ptr 都指向这一块内存const mem = new Uint8ClampedArray(goInstance.exports.mem.buffer, ptr, size)// 将图像数据赋值给共享的内存mem.set(pixels.data)// 对图像进行滤镜window.filterByGO(ptr, width, height, kernel)}
)

好了,一切就绪后,我们调试一下,结果报错了:

这个问题搜索了很久也没有得到完美的解决方案,怀疑是内存不够,将 canvas 的宽高减小后就不报错了,但是最后网页只能像这样了:

即便如此,Golang 版的 FPS 也反而还不如 JS 版的,这就有点尴尬了。

出师不利,难道是假期不适合学习?不过暂时先告一段落吧,下次试试用 Rust 实现一个。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

浅学 WebAssembly(实现视频实时滤镜效果)相关推荐

  1. 真-浅浅了解下音视频文件格式和相关概念

    真-浅浅了解下音视频文件格式和相关概念 散装知识,只是突然对这类知识感兴趣,想简单了解下,找到啥就记录啥,没有深入研究文件内部组成构造和底层实现技术和相关标准,毕竟内容挺多的,我也不是必须得学透,况且 ...

  2. 浅学Matlab:确定比赛的胜负问题

    女朋友突然找我帮她做线代的期末大作业,说是要用 matlab 写篇论文.我倒也一下懵了,虽说我的专业和打代码能沾点边,但我目前也只不熟练地了解 C++一门语言(各位大佬原谅我这个小白大一新生吧),完全 ...

  3. 4K60帧视频实时抠图,连头发丝都根根分明

    看这一头蓬松的秀发,加上帅气的动作,你以为是在绿幕前拍大片? No.No.No 这其实是AI拿来视频实时抠图后的效果. 没想到吧,实时视频抠图,现在能精细到每一根发丝. 换到alpha通道再看一眼,不 ...

  4. TencentOS浅学过程记录

    TencentOS浅学过程记录 前言 一.RTOS 二.学习资料来源 三.初步学习过程中的疑难问题解决 任务调度以及轮询时间片 消息队列与邮箱队列 互斥锁 任务中为什么一定要加while(1)循环 内 ...

  5. 完美抠图王冰冰!字节实习生开发的AI,实现4K60帧视频实时抠图,连头发丝都根根分明...

    鱼羊 明敏 发自 凹非寺 量子位 报道 | 公众号 QbitAI 看这一头蓬松的秀发,加上帅气的动作,你以为是在绿幕前拍大片? No.No.No 这其实是AI拿来视频实时抠图后的效果. 没想到吧,实时 ...

  6. H.265网页播放器EasyPlayer实现WebRTC视频实时录像功能

    我们在此前的文章中给大家分享过关于EasyPlayer已经实现了实时录像的功能,感兴趣的用户可以戳这篇文章:H5网页播放器EasyPlayer.js如何实现直播视频实时录像? 经过我们不断地摸索和研发 ...

  7. 浅学一下PPT吧 —— 真的很浅

    浅学一下PPT吧 -- 真的很浅 背景内容 详情摘要 工具介绍 工具使用 常见设置 Office背景 Office主题 无限撤回 自动保存 图片压缩 字体嵌入 多格式导出 PPT界面 大小背景 文字段 ...

  8. Halcon慢慢来(浅学Halcon)

    浅学Halcon 首先乱七八糟了解了下Halcon是个啥? 怎么下载,配置Halcon? 入门第一天需要了解: 紧跟一个分割处理并且把处理信息存在文件中: Halcon直接接入摄像头处理也是十分方便: ...

  9. Xilinx低比特率高品质 ABR 视频实时转码(HPE 参考架构)

    Xilinx低比特率高品质 ABR 视频实时转码(HPE 参考架构) 介 绍 对实时视频流的需求给视频服务提供商带来了严峻挑战,必须在管理基础设施和互联网带宽运营成本,还要为客户提供高质量体验.鉴于视 ...

最新文章

  1. 独家 | 数据转换:标准化vs 归一化(附代码链接)
  2. android NDK 二、编译方法
  3. 基于vue-cli配置移动端自适应
  4. 图文结合分析Spring的面向切面编程--AOP
  5. linux系统是否支持gpt分区,Linux下进行GPT分区
  6. canvas 将图形设置为button_将Excel设置为存放文件的文件夹:自动提醒直观摘要免压缩发微信...
  7. PAT_B_1085_Java(25分)
  8. python 取余_玩转Python源码(一) quot;%squot;与“%d”
  9. 计算机通信事业单位专业知识点,事业单位计算机专业知识招考大纲
  10. Layui 表格渲染
  11. 从事计算机工作的应该,未来想从事计算机方面的工作,现在应该学习些什么东西?...
  12. linux内核空间open,linux kernel之详解从用户态open到内核驱动实现流程
  13. Leetcode561.Array Partition I数组拆分1
  14. 弧度制和角度制的换算
  15. 视觉心理物理学(2)matlab与ptb3
  16. MATLAB 制作gif动态图
  17. 使用jira管理Scrum敏捷项目实战(四)jira自定义电子看板、敏捷看板、KANBAN配置
  18. 【工具篇】EasyExcel的应用
  19. 一次linux oops分析
  20. Topcoder 2016 TCO Algorithm Algo Semifinal 2 Hard

热门文章

  1. 常见激活函数持续更新)sigmoid Tanh Relu Leaky-ReLU RReLU
  2. vRealize Automation(VRA8.1)升级到VRA8.2(二)
  3. 中国哪些公司在做大数据
  4. Word插入参考文献(不是WPS)
  5. 小型NAS搭建实例①——TrueNAS-SCALE-22.02.4安装Ubuntu Server 22.04 LTS虚拟机,包含网卡TrueNAS桥接,直通,Ubuntu硬盘分区等详细介绍
  6. pandas 读取csv : ‘utf-8‘ codec can‘t decode byte 0xff in position 0: invalid start byte
  7. Nokia N9/N10将使用MeeGo操作系统
  8. wwe拳王争霸赛搞笑报名表源码
  9. 利用配置组策略解决Chrome浏览器第三方扩展被停用的问题
  10. php图片生成文字源码下载,PHP文字生成图片