Joyber 发布的文章

自动填充大多数情况下可以使用使用者效率提升,但是有些特殊情况下,浏览器会识别错误或者开发者根本不需要自动填充内容的时候,也是比较头痛的事情,还有可能导致页面代码产生异常动作

以下是网上找的方法:第三,四种方法有效,建议使用第四种

方法一:设置autocomplete=‘off’,适用于普通文本框

//用户名
<input type="text" autoComplete="off" name='name'/>

方法二:设置autocomplete=‘new-password’,适用于密码输入框

//密码
<input type="password" autoComplete="new_password" name='password'/>

方法三:设置autocomplete=‘off’,在input上面各加上一个隐藏的input,type设置成text,点击后改成password,但是容易造成表单提交时候数据混乱的问题,需要看一下js是否处理了提交数据,根据ID或者class取值,而不是根据name取值.

//密码
<input type="text" name="name" style="display: none"/>
<input type="text" name="username" AUTOCOMPLETE="off" />

<input type="password" style="display: none"/>
<input type="text" name="password" AUTOCOMPLETE="off" onfocus="this.type='password'" />

方法四:将input的属性设置成只读,onfocus函数,当点击input时候将只读属性去掉

//用户名
<input type="text" readonly onfocus="this.removeAttribute('readonly');" name="username" />
//密码
<input type="password" readonly onfocus="this.removeAttribute('readonly')" name='password'/>

现在很多视频网站都上线了弹幕防遮挡方案,对于视频中的人物,弹幕会在其下方展示,而不会遮挡住人物。还有的应用针对弹幕遮挡进行了新的探索,即成为付费会员后,可以选择只有自己喜欢的爱豆不被遮挡,其他人依然被遮挡。
语义分割
根据业务场景我们分析,首先需要把人像部分分割出来,获取到人像的位置之后才能做后续的操作。所以人像分割的部分采取语义分割的方式实现,提前对视频关键帧进行标注,这个工作量是很庞大的,所以需要一个专门的标注团队去完成。根据标注后的模型,通过机器学习的方式,让计算机可以准确的识别出人的位置,并导出多边形路径。
这里面还涉及一个问题,就是近景识别和远景识别的问题,机器进行识别时只需要识别近景人物,远景人物并不需要进行识别,否则弹幕展示效果会受到很大影响。语义分割可以通过Google的Mask_RCNN来实现。
客户端实现方案
客户端的实现方案是通过人像的多边形路径,对原视频抠出人像并导出一个新的视频。在播放的时候实际上是前后两个播放器在播放,弹幕夹在两个播放器中间来实现的。并且前面的人像层需要做边缘虚化,让弹幕的过渡显得自然些,否则会太突兀。
这种方案的过渡效果会好一些。因为对每一帧视频进行切割的时候,每一帧并不能保证相邻帧切割的边缘相差都不大,也就是相邻近的帧边缘不能保证很好的衔接,这样就容易出现视频连续性的问题。前后两个播放器叠加的方案,两个层的视频内容实际上是衔接很紧密的,把弹幕层去掉你根本看不出来这是两层播放器,所以连续性的问题就不明显了。
前端实现方案
前端的实现方案是服务端将多边形路径放在一个svg文件中,并将文件下发给前端,前端通过css的mask‑image遮罩实现的。通过遮罩把人像部分抠出来,人像之外依然是黑色区域,黑色是可显示区域,和iOS的mask属性类似。
B站是最开始做弹幕防挡的,现在B站已经不局限于真人弹幕防挡了,现在很多番剧中的动漫人物也支持弹幕防挡。可以看下面的视频感受一下。
B站番剧弹幕防挡

作者:刘小壮
链接:https://juejin.cn/post/6948319081351086087
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

机器学习已经火了好几年了,但很多人的直觉仍然是前端实现不了这些能力,期望本文能打破一些“思维禁区”。
# 主流实现原理介绍
# 点播

up 上传视频
服务器后台计算提取视频画面中的人像区域,转换成 svg 存储
客户端播放视频的同时,从服务器下载 svg 与弹幕合成,人像区域不显示弹幕

# 直播

主播推流时,实时(主播设备)从画面提取人像区域,转换成 svg
将 svg 数据合并到视频流中(SEI),推流至服务器
客户端播放视频同时,从视频流中(SEI)解析出 svg
将 svg 与弹幕合成,人像区域不显示弹幕

# 本文实现方案

客户端播放视频同时,实时从画面提取人像区域信息
将人像区域信息导出成图片,与弹幕合成,人像区域不显示弹幕

# 实现原理

采用机器学习开源库从视频画面实时提取人像轮廓,如Body Segmentation
将人像轮廓转导出为图片,设置弹幕层的 mask-image

# 面临的问题
由官方demo转换成工程实践,并非调一下API就行了,最大的挑战就是——性能。
一开始我也不敢相信实时计算,能将 CPU 占用优化到 5% 左右(2020 M1 Macbook)
甚至低于主流实现中,单在客户端上的性能损耗(解析 svg,与弹幕合成)

------------------------------ 正片开始,以下是调优过程 ------------------------------

选择机器学习模型

BodyPix X
精确度太差,有很明显的弹幕与面部重合现象

BlazePose X
精确度跟后面的MediaPipe SelfieSegmentation差不多,因为提供了肢体点位信息,CPU 占用相对高出 15% 左右

MediaPipe SelfieSegmentation √
精确度优秀,只提供了人像区域信息,性能取胜

参考官方实现 ,未做优化的情况下 CPU 占用 70% 左右

const canvas = document.createElement('canvas')
canvas.width = videoEl.videoWidth
canvas.height = videoEl.videoHeight
async 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)
  
  handler(canvas.toDataURL('image/png', 0))

  window.setTimeout(detect, 33)
}

detect().catch(console.error)

降低提取频率,平衡 性能-体验

一般视频 30FPS,尝试弹幕遮罩(后称 Mask)刷新频率降为 15FPS,体验上还能接受
(再低就影响体验了)

window.setTimeout(detect, 66) 

此时,CPU 占用 50% 左右
解决性能瓶颈代码

分析火焰图可发现,性能瓶颈在 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)
  
  context.drawImage(
    
    await segmentation[0].mask.toCanvasImageSource(),
    0, 0,
    canvas.width, canvas.height
  )
  
  context.globalCompositeOperation = 'source-out'
  
  context.fillRect(0, 0, canvas.width, canvas.height)
  
  handler(canvas.toDataURL('image/png', 0)) 

  window.setTimeout(detect, 66)
}

第 2、3 步相当于给人像区域外的内容填充黑色(反向填充ImageBitmap),是为了配合css(mask-image), 不然只有当弹幕飘到人像区域才可见(与目标效果正好相反)。
globalCompositeOperation MDN

此时,CPU 占用 33% 左右

多线程优化

只剩下toDataURL这个耗时操作了,本以为toDataURL是浏览器内部实现,无法再进行优化了。
虽没有替换实现,但可使用 OffscreenCanvas + Worker,将耗时任务转移到 Worker 中去, 避免占用主线程,就不会影响用户体验了。
并且ImageBitmap实现了Transferable接口,可被转移所有权,跨 Worker 传递也没有性能损耗。


const reader = new FileReaderSync()

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)。
ini复制代码danmakuContainer.style.webkitMaskImage = url(${imgStr})

优化实现

danmakuContainer.style.webkitMaskSize = '100%, 100%' 

const cvsEl = document.createElement('canvas')

const approWidth = 300
if (videoEl.videoWidth > approWidth) {
  cvsEl.width = approWidth
  
  cvsEl.height = videoEl.videoHeight * approWidth / videoEl.videoWidth
}
const ctx = cvsEl.getContext('2d')

async function detect (): Promise<void> {
  
  ctx.drawImage(videoEl, 0, 0, cvsEl.width, cvsEl.height)
  
  const segmention = await segmenter.segmentPeople(cvsEl)
  
}

优化后,导出的 imgStr 大概 12kb,重新计算样式耗时约 0.5ms。
此时,CPU 占用 5% 左右

启动条件优化

虽然提取 Mask 整个过程的 CPU 占用已优化到可喜程度。
当在画面没人的时候,或没有弹幕时候,可以停止计算,实现 0 CPU 占用。
无弹幕判断比较简单(比如 10s 内收超过两条弹幕则启动计算),也不在该 SDK 实现范围,略过
判定画面是否有人
第一步中为了高性能,选择的模型只有ImageBitmap,并没有提供肢体点位信息。
所以只能使用ImageBitmap来判断是否有人。
画面中的人物大概率是画面中间且是连续的区域,所以从中间开始往左右反复横跳检查像素值,碰到一个alpha通道为零的像素点就表示画面有人。

const dataURL = reader.readAsDataURL(blob)


const hasBody = checkHasBody(ctx)

if (!hasBody) {
  
}

function checkHasBody (ctx): boolean {
  const imgData = ctx.getImageData(0, 0, imgW, imgH)
  
  
  const img = new Uint32Array(imgData.data.buffer)
  const len = img.length
  
  const p = Math.floor(len / 2)
  const maxDistance = Math.floor(len / 2)
  let distance = 0
  while (true) {
    if (distance > maxDistance) return false
    
    
    if (img[p + distance] === 0) return true
    if (img[p - distance] === 0) return true

    
    
    distance += 10
  }
}

画面无人时,CPU 占用接近 0%
发布构建优化

依赖包的体积较大,构建出的 bundle 体积:684.75 KiB / gzip: 125.83 KiB
所以,可以进行异步加载SDK,提升页面加载性能。

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

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

# 过程

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

CPU 数值指主线程占用
# 注意事项

兼容性:Chrome 79及以上,不支持 Firefox、Safari。因为使用了OffsccenCanvas
不应创建多个或多次创建segmenter实例(bodySegmentation.createSegmenter),如需复用请保存实例引用,因为:

创建实例时低性能设备会有明显的卡顿现象
会内存泄露;如果无可避免,这是mediapipe 内存泄露 解决方法

# 经验

结合业务场景特征进行分析优化,往往有更多的思路和途径
优化完成之后,提取并应用 Mask 关键计算量在 GPU (30%左右),而不是 CPU
性能优化需要业务场景分析,防档弹幕场景可以使用低分辨率、低刷新率的 mask-image,能大幅减少计算量
该方案其他应用场景:

替换/模糊人物背景
人像马赛克
人像抠图
卡通头套,虚拟饰品,如猫耳朵、兔耳朵、带花、戴眼镜什么的(换一个模型,略改)

关注 WebNN 、WebGPU 的进展和应用,端智能将覆盖更多的应用场景

作者:风痕_hughfenghen
链接:https://juejin.cn/post/7257048145232674877
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在原生或者中,触发日期选择只能够通过输入框的最右侧图标点击触发日期选择,对于使用可能不友好。


    .month-check {
        position: relative;//添加定位,显示右侧图标
    }
    //修改日期图标样式
    .month-check::-webkit-calendar-picker-indicator{
        position: absolute;
        right: 0;
        padding-left: 100%;//布满整个文本框
    }

 <input type="month" class="month-check" name="month">

自文章:https://learnku.com/articles/64097

手动DNS申请SSL证书

# 手动DNS申请
acme.sh --issue -d www.xxx.com,m.xxx.com --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
#会报错,提示需要解析
#手动解析后再尝试执行命令,加上 --renew  参数
acme.sh --renew -d www.xxx.com,m.xxx.com --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
#如果同时申请多个域名,--renew出现以下报错的话,可能需要改成单个域名才能找到申请的目录
#报错:
'm.xx.com,www.xx.com' is not an issued domain, skipping.
#换成单个域名(第一个域名,因为申请的时候key放到这个目录了m.xx.com_ecc/m.xx.com.key,renew的时候去m.xx.com,www.xx.com这个目录找,找不到文件)
acme.sh --renew -d m.xx.com --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
  1. 安装 acme.sh#
    以下三种任选一种即可,把 my@example.com 修改成自己的邮箱。

安装过程不会污染任何功能和文件,所有的修改都限制在安装目录中:~/.acme.sh/。

(1)通过 https://get.acme.sh 安装
curl https://get.acme.sh | sh -s email=my@example.com
或者

wget -O - https://get.acme.sh | sh -s email=my@example.com
(1)通过 GitHub 安装
curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online -m my@example.com
或者

wget -O - https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online -m my@example.com
(3)通过 git clone 安装

使用加速通道

git clone https://github.com.cnpmjs.org/acmesh-official/acme.sh.git
cd acme.sh
./acme.sh --install -m my@example.com
安装过程进行了以下几步:
(1)acme.sh 安装到 home 目录下

~/.acme.sh/
(2)创建一个 bash 的 alias

alias acme.sh=~/.acme.sh/acme.sh
(3)创建 cronjob,每天 0:00 自动检测所有证书,如果快过期了,会自动更新证书。

  1. 生成证书
    以下两种任选一种即可,把 mydomain.com 更换成自己的域名。

如何生成泛域名证书,请参阅 使用 acme.sh 生成免费的泛域名证书

(1)http 方式
指定域名,并指定网站根目录。 acme.sh 会全自动生成验证文件,并放到网站的根目录,自动完成验证。最后会删除验证文件,整个过程没有任何副作用。

acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/mydomain.com/
如果使用 apache 服务器, acme.sh 可以智能的从 apache 的配置中自动完成验证,不需要指定网站根目录:

acme.sh --issue -d mydomain.com --apache
如果使用 nginx 服务器,或者反向代理,acme.sh 可以智能的从 nginx 的配置中自动完成验证,不需要指定网站根目录:

acme.sh --issue -d mydomain.com --nginx
注意!无论是 apache 还是 nginx 模式,acme.sh 不会自动修改配置文件,需要手动修改配置文件,否则无法访问 https

如果还没有运行任何 web 服务,80 端口是空闲的,那么 acme.sh 还能假装自己是一个 webserver,临时监听 80 端口,完成验证:

acme.sh --issue -d mydomain.com --standalone
(2)DNS 方式
好处:不需要任何服务器,不需要任何公网 IP, 只需要 DNS 的解析记录即可完成验证。

坏处:如果不同时配置 Automatic DNS API 则 acme.sh 将无法自动更新证书。

acme.sh --issue --dns -d mydomain.com \
--yes-I-know-dns-manual-mode-enough-go-ahead-please
acme.sh 会生成相应的解析记录,到域名解析中添加 TXT 记录,解析成功后,重新生成证书。

acme.sh --renew -d mydomain.com \
--yes-I-know-dns-manual-mode-enough-go-ahead-please
DNS 方式的真正强大之处在于可以使用域名解析商提供的 api 自动添加 TXT 记录完成验证。

acme.sh 目前支持几十种域名服务商:dnsapi

以 dnspod 为例,需要先登录到 dnspod 账号,生成 api id 和 api key

export DP_Id="1234"

export DP_Key="qwertyuiopasdfghjkl"

acme.sh --issue --dns dns_dp -d mydomain.com -d www.mydomain.com
acme.sh 会自动生成证书,并且会记录 api id 和 api key 以后再使用 dnspod api 时就不需要再指定了。

  1. 复制 / 安装 证书
    注意!默认生成的证书都放在安装目录下:~/.acme.sh/,不要直接使用此目录下的文件,这里面的文件都是内部使用,而且目录结构可能会变化。

正确的使用方法是使用 --install-cert 命令,指定目标位置,证书文件会被复制到相应的位置。

Apache
acme.sh --install-cert -d example.com \
--cert-file /path/to/certfile/in/apache/cert.pem \
--key-file /path/to/keyfile/in/apache/key.pem \
--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \
--reloadcmd "service apache2 force-reload"
Nginx
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/in/nginx/key.pem \
--fullchain-file /path/to/fullchain/nginx/cert.pem \
--reloadcmd "service nginx force-reload"

  1. 更新证书
    证书在 60 天以后会自动更新,无需任何操作。
  2. 更新 acme.sh
    升级 acme.sh 到最新版

acme.sh --upgrade
如果不想手动升级,也可以开启自动升级

acme.sh --upgrade --auto-upgrade
关闭自动更新

acme.sh --upgrade --auto-upgrade 0

  1. 出错怎么办
    在命令后添加 --debug 或 --debug 2 比如:

acme.sh --issue -d mydomain.com --nginx --debug

acme.sh --issue -d mydomain.com --nginx --debug 2
或者查阅日志

~/.acme.sh/acme.sh.log