原生Js Canvas去除視頻綠幕背景的方法實(shí)現(xiàn)
注: 這里的去除視頻背景并不是對(duì)視頻文件進(jìn)行操作去除背景
如果需要對(duì)視頻扣除背景并導(dǎo)出可以使用 ffmpeg
等庫(kù),這里僅作播放用所以采用這種方法
由于uniapp中的canvas經(jīng)過(guò)封裝,且 uniapp 的
drawImage
無(wú)法繪制視頻幀畫(huà)面,因此uniapp中不適用
實(shí)現(xiàn)過(guò)程是將視頻使用canvas逐幀截下來(lái)對(duì)截取的圖片進(jìn)行處理,然后在canvas中顯示處理好的圖片
最后通過(guò)定時(shí)器高速處理替換,形成視頻播放的效果,效果如下圖?
邊緣仍然會(huì)有些綠幕的像素,可以通過(guò)其他的處理進(jìn)行優(yōu)化
原理
首先使用canvas的 drawImage
方法將video的當(dāng)前幀畫(huà)面繪制到canvas中
然后再通過(guò) getImageData
方法獲取當(dāng)前canvas的所有像素的 rgba
值組成的數(shù)組
獲取到的值為 [r,g,b,a,r,g,b,a,...]
,每一組 rgba
的值就是一個(gè)像素,所以獲取到的數(shù)組長(zhǎng)度是canvas的像素的數(shù)量 * 4
通過(guò)判斷每一組 rgb
的值是否為綠幕像素,然后設(shè)置其透明通道的 alpha
的值為0實(shí)現(xiàn)效果
代碼
因?yàn)閏anvas會(huì)受到跨域的影響導(dǎo)致畫(huà)布被污染,因此首先需要將測(cè)試視頻下載到本地
如果直接本地打開(kāi)html的話同樣會(huì)因?yàn)楸镜芈窂綀?bào)跨域錯(cuò)誤,需要將html,js,測(cè)試視頻放在文件夾中部署一個(gè)本地服務(wù)器
可以使用 http-server
npm i http-server -g # 切換到存放html,js,測(cè)試視頻的文件夾 運(yùn)行命令即可部署本地服務(wù)器 http-server
或者
vsCode的 Live server
插件均可
<!DOCTYPE html> <html lang="en"> <head> <style> video{ width: 480px; height: 270px; } </style> </head> <body> <video id="video" src="./63e1dd7ddd2b0.mp4" loop autoplay muted></video> <canvas id="output-canvas" width="480" height="270" willReadFrequently="true"></canvas> <script type="text/javascript" src="processor2.js"></script> </body> </html>
// processor2.js let video, canvas, ctx, canvas_tmp, ctx_tmp; function init () { video = document.getElementById('video'); canvas = document.getElementById('output-canvas'); ctx = canvas.getContext('2d'); // 創(chuàng)建的canvas寬高最好與顯示圖片的canvas、video寬高一致 canvas_tmp = document.createElement('canvas'); canvas_tmp.setAttribute('width', 480); canvas_tmp.setAttribute('height', 270); ctx_tmp = canvas_tmp.getContext('2d'); video.addEventListener('play', computeFrame); } function computeFrame () { if (video) { if (video.paused || video.ended) return; } // 如果視頻比例和canvas比例不正確可能會(huì)出現(xiàn)顯示形變, 調(diào)整除的值進(jìn)行比例調(diào)整 ctx_tmp.drawImage(video, 0, 0, video.clientWidth / 1, video.clientHeight / 1); // 獲取到繪制的canvas的所有像素rgba值組成的數(shù)組 let frame = ctx_tmp.getImageData(0, 0, video.clientWidth, video.clientHeight); // 共有多少像素點(diǎn) const pointLens = frame.data.length / 4; for (let i = 0; i < pointLens; i++) { let r = frame.data[i * 4]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; // 判斷如果rgb值在這個(gè)范圍內(nèi)則是綠幕背景,設(shè)置alpha值為0 // 同理不同顏色的背景調(diào)整rgb的判斷范圍即可 if (r < 100 && g > 120 && b < 200) { frame.data[i * 4 + 3] = 0; } } // 重新繪制到canvas中顯示 ctx.putImageData(frame, 0, 0); // 遞歸調(diào)用 setTimeout(computeFrame, 0); } document.addEventListener("DOMContentLoaded", () => { init(); });
使用本地服務(wù)器訪問(wèn)html即可看到效果,可以看到邊緣仍有綠色像素閃爍
一般情況這種就可以了,使用算法進(jìn)行處理的話效果會(huì)更好,但相應(yīng)的資源的消耗也會(huì)提升,造成幀率下降
下面展示通過(guò)一些算法進(jìn)行羽化和顏色過(guò)渡
羽化
// 返回canvas中第num個(gè)像素點(diǎn)所在的坐標(biāo) 12 -> [1, 12] function numToPoint (num, width) { let col = num % width; let row = Math.floor(num / width); row = col === 0 ? row : row + 1; col = col === 0 ? width : col; return [row, col]; } // 返回canvas中所在坐標(biāo)的num(index + 1)值 [1, 12] -> 12 function pointToNum (point, width) { let [row, col] = point; return (row - 1) * width + col } // 獲取輸入的坐標(biāo)周?chē)?像素內(nèi)的所有像素的坐標(biāo)組成的數(shù)組 [1, 1] -> [[1, 2], [2, 1], [2, 2]] function getAroundPoint (point, width, height, area) { let [row, col] = point; let allAround = []; if (row > height || col > width || row < 0 || col < 0) return allAround; for (let i = 0; i < area; i++) { let pRow = row - 1 + i; for (let j = 0; j < area; j++) { let pCol = col - 1 + j; if (i === area % 2 && j === area % 2) continue; allAround.push([pRow, pCol]); } } return allAround.filter(([iRow, iCol]) => { return (iRow > 0 && iCol > 0) && (iRow <= height && iCol <= width); }) }
通過(guò)上面的函數(shù)獲取到一個(gè)選定的不透明的像素周?chē)南袼睾螅袛嘀車(chē)南袼氐腶lpha值
如果周?chē)南袼赜写嬖谕该鞯南袼?,則重新計(jì)算選定像素的alpha值
顏色過(guò)渡
計(jì)算修改alpha值連帶計(jì)算周?chē)袼刂衦gb的各項(xiàng)平均值給選定像素
最終處理結(jié)果如下
代碼
// 新增羽化和顏色過(guò)渡 // processor2.js let video, canvas, ctx, canvas_tmp, ctx_tmp; function init () { video = document.getElementById('video'); canvas = document.getElementById('output-canvas'); ctx = canvas.getContext('2d'); // 創(chuàng)建的canvas寬高最好與顯示圖片的canvas、video寬高一致 canvas_tmp = document.createElement('canvas'); canvas_tmp.setAttribute('width', 480); canvas_tmp.setAttribute('height', 270); ctx_tmp = canvas_tmp.getContext('2d'); video.addEventListener('play', computeFrame); } function numToPoint (num, width) { let col = num % width; let row = Math.floor(num / width); row = col === 0 ? row : row + 1; col = col === 0 ? width : col; return [row, col]; } function pointToNum (point, width) { let [row, col] = point; return (row - 1) * width + col } function getAroundPoint (point, width, height, area) { let [row, col] = point; let allAround = []; if (row > height || col > width || row < 0 || col < 0) return allAround; for (let i = 0; i < area; i++) { let pRow = row - 1 + i; for (let j = 0; j < area; j++) { let pCol = col - 1 + j; if (i === area % 2 && j === area % 2) continue; allAround.push([pRow, pCol]); } } return allAround.filter(([iRow, iCol]) => { return (iRow > 0 && iCol > 0) && (iRow <= height && iCol <= width); }) } function computeFrame () { if (video) { if (video.paused || video.ended) return; } ctx_tmp.drawImage(video, 0, 0, video.clientWidth, video.clientHeight); let frame = ctx_tmp.getImageData(0, 0, video.clientWidth, video.clientHeight); //----- emergence ---------- const height = frame.height; const width = frame.width; const pointLens = frame.data.length / 4; for (let i = 0; i < pointLens; i++) { let r = frame.data[i * 4]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (r < 150 && g > 200 && b < 150) { frame.data[i * 4 + 3] = 0; } } const tempData = [...frame.data] for (let i = 0; i < pointLens; i++) { if (frame.data[i * 4 + 3] === 0) continue const currentPoint = numToPoint(i + 1, width); const arroundPoint = getAroundPoint(currentPoint, width, height, 3); let opNum = 0; let rSum = 0; let gSum = 0; let bSum = 0; arroundPoint.forEach((position) => { const index = pointToNum(position, width); rSum = rSum + tempData[(index - 1) * 4]; gSum = gSum + tempData[(index - 1) * 4 + 1]; bSum = bSum + tempData[(index - 1) * 4 + 2]; if (tempData[(index - 1) * 4 + 3] !== 255) opNum++; }) let alpha = (255 / arroundPoint.length) * (arroundPoint.length - opNum); if (alpha !== 255) { // debugger frame.data[i * 4] = parseInt(rSum / arroundPoint.length); frame.data[i * 4 + 1] = parseInt(gSum / arroundPoint.length); frame.data[i * 4 + 2] = parseInt(bSum / arroundPoint.length); frame.data[i * 4 + 3] = parseInt(alpha); } } //------------------------ ctx.putImageData(frame, 0, 0); setTimeout(computeFrame, 0); } document.addEventListener("DOMContentLoaded", () => { init(); });
到此這篇關(guān)于原生Js Canvas去除視頻綠幕背景的方法實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Js Canvas去除視頻綠幕背景內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
仿iPhone通訊錄制作小程序自定義選擇組件的實(shí)現(xiàn)
這篇文章主要介紹了仿iPhone通訊錄制作小程序自定義選擇組件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05Boostrap入門(mén)準(zhǔn)備之border box
之前在學(xué)習(xí)Bootstrap的過(guò)程中,遇到各種奇葩的坑,如果在學(xué)習(xí)bootstrap之前,準(zhǔn)備工作先做好,就可以或多或少的避開(kāi)一些坑。下面小編開(kāi)始給大家介紹border-box這個(gè)屬性的知識(shí)。感興趣的朋友一起學(xué)習(xí)吧2016-05-05用javascript判斷IE版本號(hào)簡(jiǎn)單實(shí)用且向后兼容
項(xiàng)目中需要判斷IE版本號(hào),又因?yàn)?jQuery 2.0 去除了對(duì)瀏覽器版本號(hào)的判斷于是就看到一老外寫(xiě)的一段代碼,下面與大家分享下2013-09-09javascript 在網(wǎng)頁(yè)中的運(yùn)用(asp.net)
javascript在網(wǎng)頁(yè)中的運(yùn)用實(shí)現(xiàn),需要的朋友可以參考下。2009-11-11javascript:;與javascript:void(0)使用介紹
有時(shí)候我們?cè)诰帉?xiě)js過(guò)程中,需要觸發(fā)事件而不需要返回值,那么就可能需要這樣的寫(xiě)法2013-06-06JavaScript中兩個(gè)感嘆號(hào)的作用說(shuō)明
用兩個(gè)感嘆號(hào)的作用就在于,如果明確設(shè)置了o中flag的值(非null/undefined/0""/等值),自然test就會(huì)取跟o.flag一樣的值;如果沒(méi)有設(shè)置,test就會(huì)默認(rèn)為false,而不是null或undefined2011-12-12layui 表格操作列按鈕動(dòng)態(tài)顯示的實(shí)現(xiàn)方法
今天小編就為大家分享一篇layui 表格操作列按鈕動(dòng)態(tài)顯示的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09js實(shí)現(xiàn)彈出框的拖拽效果實(shí)例代碼詳解
本文通過(guò)實(shí)例代碼給大家介紹了js實(shí)現(xiàn)彈出框的拖拽效果,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-04-04