在羣裏面有人提到了這麼一個實現:現有一段素材視頻,想要對視頻中的某個內容進行替換,換成本身的圖片,這個怎麼用 OpenGL 去實現呢?java
首先要明確的是,視頻是由一幀一幀圖像組成的,它利用了人眼的視覺暫留效應,一秒內播放足夠幀數的圖片纔會感受到是連續的。git
而想要對視頻的內容進行替換,也就是要將每一幀圖像的內容都進行替換了,通常來講這應該是屬於視頻後期處理了,用專業的 AE (Adobe After Effects)軟件來處理會比較好。github
若是用 OpenGL 來處理,有這樣的一個思路:微信
首先經過 MediaCodec 對每一幀圖像內容進行解碼,而後再經過 OpenGL 對當前解碼的一幀圖像進行處理,在原圖像上加一個透明的遮罩層,遮罩層的要求就是對於要替換的內容區域是非透明的,其餘區域透明,將遮罩層和原圖像進行融合,最後獲得的就是一幀被替換過內容圖像了,再將處理過的一幀圖像進行編碼,從新編碼成新的視頻內容。學習
一直重複 解碼 -> 處理 -> 編碼這個過程,直到視頻的每一幀內容都處理完了,就實現了對視頻內容替換。ui
固然這僅僅是個思路,難點在於如何找到合適的遮罩層,若是視頻圖像內容是變更的,要替換的內容不是固定的,那麼對於遮罩層要求更高了,每一幀處理都得有個合適的遮罩。編碼
下面會針對視頻的一幀圖像內容進行處理,如何將一幀的圖像內容替換了。spa
效果以下:線程
代碼實現的效果,左上方的內容被右上方內容替換了,最後成了右下角的圖片。設計
不會作設計的開發不是好碼農
是時候掏出個人大寶石軟件 Sketch 切個圖了:
準備一張待替換內容:
而後再切一張同等大小,並把中間圓形位置的圖片替換成想要的圖片,其餘周邊內容設置透明度爲 0 。
接下來的事情就是將兩張圖片融合,分別介紹基於着色器和顏色混合來替換內容。
這兩個方案都有一個共同點,就是要將帶遮罩的圖片覆蓋在原圖上,不一樣的是如何處理兩個圖片之間的覆蓋,透明度就是一個比較好的切入點。
在 OpenGL 的渲染管線中,會先構建圖形,而後進行光柵化,光柵化後對每個片元着色,在這個着色過程當中能夠根據須要對片元進行處理,包括拋棄某些片元等,簡單說在 OpenGL 中就是先有形後有色,而在有形有色的過程當中能夠搞點小操做~~
對片元進行處理就是咱們的片元着色器腳本了。
precision mediump float;
varying vec2 vTextureCoord; //接收從頂點着色器過來的參數
uniform sampler2D sTexture;//紋理內容數據
void main() {
vec4 bcolor = texture2D(sTexture, vTextureCoord);//給此片元從紋理中採樣出顏色值
if(bcolor.a<0.6) {
discard;
} else {
gl_FragColor=bcolor;
}}
複製代碼
咱們的遮罩圖除了要替換的內容,其餘地方都是透明的,根據採樣出的透明度值小於閾值,就拋棄該片元,直接就不顯示了。
而透明度知足要求的就會顯示,而且在最後映射到視口上時,直接覆蓋了原有的顏色。
經過這種方式就實現了內容替換。
使用顏色混合的方式不像着色器那樣簡單粗暴,要麼拋棄某些片元,要麼直接覆蓋了。
它是根據必定的計算規則,來計算兩個顏色之間的融合。
在 OpenGL 中使用顏色混合要設置合理的混合因子。
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
// 繪製
glDisable(GL_BLEND)
複製代碼
混合因子的設置使得若是遮罩圖是透明的,使用被遮罩圖的顏色,若是不是透明的,使用遮罩圖的顏色,這樣就不是直接拋棄某些片元了。
在具體的代碼實現中,採用了 EGL 來實現離屏的渲染。
在非主線程中,初始化 EGL 環境,而後準備好繪製的必要工做,接着執行繪製,最後把繪製的結果經過 glReadPixels 讀取出來。
Observable.fromCallable {
// 初始化 EGL 環境
return@fromCallable initEgl()
}.map {
// 設置各類矩陣
prepare(width, height)
return@map it
}.map {
// 執行繪製
replaceContent(isBlend)
return@map it
}.map {
// 讀取像素
val result = readPixel(width, height)
it.destroy()
return@map result
}.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// 設置效果
mResultImage.setImageBitmap(it)
}, {
showToast("replace failed")
})
複製代碼
具體的繪製過程比較簡單,若是採用了顏色混合就執行顏色混合的繪製,不然採用着色器的繪製,也體現了就是將遮罩圖直接覆蓋在原圖上的思想。
private fun replaceContent(isBlend: Boolean) {
glClearColor(1f, 1f, 1f, 1f)
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
mOriginImage?.drawSelf(mOriginTextureId)
if (isBlend) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
mReplaceImage?.drawSelf(mReplaceTextureId)
glDisable(GL_BLEND)
} else {
mAlphaTextureRect?.drawSelf(mReplaceTextureId)
}
}
複製代碼
在最後讀取像素內容時要注意,glReadPixels 讀取的內容是上下顛倒的,須要將它翻轉過來。
for (i in 0 until height) {
for (j in 0 until width) {
pixelMirroredArray[(height - i - 1) * width + j] = pixelArray[i * width + j]
}
}
複製代碼
具體的實現能夠參考個人 Github 項目,求一波 Star 。
對於視頻內容替換,這裏僅僅是給出了一幀圖像內容的替換,並且仍是基於透明度的。
看到好萊塢有些電影場景拍攝時,後面都會給出一塊純色的幕布,而後在後期處理時把幕布內容替換成背景,這種替換經過着色器比較顏色的範圍應該也是能夠實現的。
固然了,要是搭配圖像識別來替換內容玩法就更加豐富了。
俗話說:
獨學而無友,則孤陋而寡聞
學習的路上是須要交流和分享的,這裏有個二維碼,若是你對 OpenGL ES 感興趣歡迎一塊兒來交流討論。
要是二維碼過時了,加微信 ezglumes 好友,備註 OpenGL ,拉你入羣~
這裏還有個二維碼,歡迎掃描關注,及時閱讀最新文章內容~~