浅学 WebAssembly(实现视频实时滤镜效果)
前言
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(实现视频实时滤镜效果)相关推荐
- 真-浅浅了解下音视频文件格式和相关概念
真-浅浅了解下音视频文件格式和相关概念 散装知识,只是突然对这类知识感兴趣,想简单了解下,找到啥就记录啥,没有深入研究文件内部组成构造和底层实现技术和相关标准,毕竟内容挺多的,我也不是必须得学透,况且 ...
- 浅学Matlab:确定比赛的胜负问题
女朋友突然找我帮她做线代的期末大作业,说是要用 matlab 写篇论文.我倒也一下懵了,虽说我的专业和打代码能沾点边,但我目前也只不熟练地了解 C++一门语言(各位大佬原谅我这个小白大一新生吧),完全 ...
- 4K60帧视频实时抠图,连头发丝都根根分明
看这一头蓬松的秀发,加上帅气的动作,你以为是在绿幕前拍大片? No.No.No 这其实是AI拿来视频实时抠图后的效果. 没想到吧,实时视频抠图,现在能精细到每一根发丝. 换到alpha通道再看一眼,不 ...
- TencentOS浅学过程记录
TencentOS浅学过程记录 前言 一.RTOS 二.学习资料来源 三.初步学习过程中的疑难问题解决 任务调度以及轮询时间片 消息队列与邮箱队列 互斥锁 任务中为什么一定要加while(1)循环 内 ...
- 完美抠图王冰冰!字节实习生开发的AI,实现4K60帧视频实时抠图,连头发丝都根根分明...
鱼羊 明敏 发自 凹非寺 量子位 报道 | 公众号 QbitAI 看这一头蓬松的秀发,加上帅气的动作,你以为是在绿幕前拍大片? No.No.No 这其实是AI拿来视频实时抠图后的效果. 没想到吧,实时 ...
- H.265网页播放器EasyPlayer实现WebRTC视频实时录像功能
我们在此前的文章中给大家分享过关于EasyPlayer已经实现了实时录像的功能,感兴趣的用户可以戳这篇文章:H5网页播放器EasyPlayer.js如何实现直播视频实时录像? 经过我们不断地摸索和研发 ...
- 浅学一下PPT吧 —— 真的很浅
浅学一下PPT吧 -- 真的很浅 背景内容 详情摘要 工具介绍 工具使用 常见设置 Office背景 Office主题 无限撤回 自动保存 图片压缩 字体嵌入 多格式导出 PPT界面 大小背景 文字段 ...
- Halcon慢慢来(浅学Halcon)
浅学Halcon 首先乱七八糟了解了下Halcon是个啥? 怎么下载,配置Halcon? 入门第一天需要了解: 紧跟一个分割处理并且把处理信息存在文件中: Halcon直接接入摄像头处理也是十分方便: ...
- Xilinx低比特率高品质 ABR 视频实时转码(HPE 参考架构)
Xilinx低比特率高品质 ABR 视频实时转码(HPE 参考架构) 介 绍 对实时视频流的需求给视频服务提供商带来了严峻挑战,必须在管理基础设施和互联网带宽运营成本,还要为客户提供高质量体验.鉴于视 ...
最新文章
- 独家 | 数据转换:标准化vs 归一化(附代码链接)
- android NDK 二、编译方法
- 基于vue-cli配置移动端自适应
- 图文结合分析Spring的面向切面编程--AOP
- linux系统是否支持gpt分区,Linux下进行GPT分区
- canvas 将图形设置为button_将Excel设置为存放文件的文件夹:自动提醒直观摘要免压缩发微信...
- PAT_B_1085_Java(25分)
- python 取余_玩转Python源码(一) quot;%squot;与“%d”
- 计算机通信事业单位专业知识点,事业单位计算机专业知识招考大纲
- Layui 表格渲染
- 从事计算机工作的应该,未来想从事计算机方面的工作,现在应该学习些什么东西?...
- linux内核空间open,linux kernel之详解从用户态open到内核驱动实现流程
- Leetcode561.Array Partition I数组拆分1
- 弧度制和角度制的换算
- 视觉心理物理学(2)matlab与ptb3
- MATLAB 制作gif动态图
- 使用jira管理Scrum敏捷项目实战(四)jira自定义电子看板、敏捷看板、KANBAN配置
- 【工具篇】EasyExcel的应用
- 一次linux oops分析
- Topcoder 2016 TCO Algorithm Algo Semifinal 2 Hard
热门文章
- 常见激活函数持续更新)sigmoid Tanh Relu Leaky-ReLU RReLU
- vRealize Automation(VRA8.1)升级到VRA8.2(二)
- 中国哪些公司在做大数据
- Word插入参考文献(不是WPS)
- 小型NAS搭建实例①——TrueNAS-SCALE-22.02.4安装Ubuntu Server 22.04 LTS虚拟机,包含网卡TrueNAS桥接,直通,Ubuntu硬盘分区等详细介绍
- pandas 读取csv : ‘utf-8‘ codec can‘t decode byte 0xff in position 0: invalid start byte
- Nokia N9/N10将使用MeeGo操作系统
- wwe拳王争霸赛搞笑报名表源码
- 利用配置组策略解决Chrome浏览器第三方扩展被停用的问题
- php图片生成文字源码下载,PHP文字生成图片