效果圖(1080P處理)
因為攝像頭開啟自動曝光,畫面變動時,亮度變化導致扣像在轉動時如上。
原始碼地址vulkan_extratest
這個demo主要測試二點,一是測試ndk camera整合效果,二是本專案對接外部實現的vulkan層是否方便,用於以後移植GPUImage裡的實現。
我簡化了在android下vulkan與opengles紋理互通裡的處理,沒有vulkan視窗與交換鏈這些邏輯,只用到vulkan compute shader計算管線得到結果然後交換給opengl裡的紋理。
NDK Camera整合
主要參考 NdkCamera Sample的實現,然後封裝成滿足Aoce定義裝置介面。
說下遇到的坑。
-
AIMAGE_FORMAT_YUV_420_888 可能是YUV420P,也可能是NV12,需要在AImageReader_ImageListener裡拿到image通過AImage_getPlanePixelStride裡的UV的plan是否為1來判斷是否為YUV420P,或者看data[u]-data[y]=1來看是否為NV12.具體可以看getVideoFrame的實現。
-
AImageReader_new裡的maxImages比較重要,簡單理解為預先申請幾張圖,這個值越大,顯示越平滑。
AImageReader_new如果不開執行緒,則影像處理加到這個執行緒裡,導致讀取影像變慢。開啟執行緒處理,
我用的Redmi K10 pro,可以讀40003000,在AImageReader_ImageListener回撥不做特殊處理,如下錯誤。
首先是Unable to acquire a lockedBuffer, very likely client tries to lock more than.
可以看到,執行四次後報的,就是我設的maxImages,通過比對程式碼邏輯,應該是AImageReader_new讀四次後,我還沒處理完一楨,沒有AImage_delete,也就讀不了資料了.
然後檢查 AImageReader_acquireNextImage 這個狀態,不對不讀,然後繼續引發讀取不可用記憶體問題,分析應該是處理資料的亂序執行緒AImage_delete可能釋放別的處理執行緒上的image,然後處理影像執行緒上加上lock_guard(mutex),不會引發問題,但是會導致每maxImages卡一下,可以理解,讀的執行緒快,處理的慢,後面想了下,直接讓thread.join,圖片讀取很大時慢(比不開執行緒要快很多,40003000快二倍多,平均45ms),但是平滑的,暫時先這樣,後面看能不能直接拿AImage的harderbuffer去處理,讓處理速度追上讀取速度。
Chroma Key
如上所說,專案對接外部實現的vulkan層是否方便,在這重新生成一個模組aoce_vulkan_extra,在這我選擇UE4 Matting裡的邏輯來測試,因為這個邏輯非常簡單,也算讓我對手機的效能有個初步的瞭解。
首先把相關邏輯整理下,UE4上有相關節點,看下實現整理成glsl compute shader實現。
#version 450
// https://www.unrealengine.com/en-US/tech-blog/setting-up-a-chroma-key-material-in-ue4
layout (local_size_x = 16, local_size_y = 16) in;// gl_WorkGroupSize
layout (binding = 0, rgba8) uniform readonly image2D inTex;
layout (binding = 1, rgba8) uniform image2D outTex;
layout (std140, binding = 2) uniform UBO {
// 0.2 控制亮度的強度係數
float lumaMask;
float chromaColorX;
float chromaColorY;
float chromaColorZ;
// 用環境光補受藍綠幕影響的畫素(簡單理解釦像結果要放入的環境光的顏色)
float ambientScale;
float ambientColorX;
float ambientColorY;
float ambientColorZ;
// 0.4
float alphaCutoffMin;
// 0.5
float alphaCutoffMax;
float alphaExponent;
// 0.8
float despillCuttofMax;
float despillExponent;
} ubo;
const float PI = 3.1415926;
vec3 extractColor(vec3 color,float lumaMask){
float luma = dot(color,vec3(1.0f));
// 亮度指數
float colorMask = exp(-luma*2*PI/lumaMask);
// color*(1-colorMask)+color*luma
color = mix(color,vec3(luma),colorMask);
// 生成基於亮度的飽和度圖
return color / dot(color,vec3(2.0));
}
void main(){
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(outTex);
if(uv.x >= size.x || uv.y >= size.y){
return;
}
vec3 inputColor = imageLoad(inTex,uv).rgb;
vec3 chromaColor = vec3(ubo.chromaColorX,ubo.chromaColorY,ubo.chromaColorZ);
vec3 ambientColor = vec3(ubo.ambientColorX,ubo.ambientColorY,ubo.ambientColorZ);
vec3 color1 = extractColor(chromaColor,ubo.lumaMask);
vec3 color2 = extractColor(inputColor,ubo.lumaMask);
vec3 subColor = color1 - color2;
float diffSize = length(subColor);
float minClamp = diffSize-ubo.alphaCutoffMin;
float dist = ubo.alphaCutoffMax - ubo.alphaCutoffMin;
// 扣像alpha
float alpha= clamp(pow(max(minClamp/dist,0),ubo.alphaExponent),0.0,1.0);
// 受扣像背景影響的顏色alpha
float inputClamp = ubo.despillCuttofMax - ubo.alphaCutoffMin;
float despillAlpha = 1.0f- clamp(pow(max(minClamp/inputClamp,0),ubo.despillExponent),0.0,1.0);
// 亮度係數
vec3 lumaFactor = vec3(0.3f,0.59f,0.11f);
// 新增環境光收益
vec3 dcolor = inputColor*lumaFactor*ambientColor*ubo.ambientScale*despillAlpha;
// 去除扣像背景
dcolor -= inputColor*chromaColor*despillAlpha;
dcolor += inputColor;
// 為了顯示檢視效果,後面遮蔽
dcolor = inputColor*alpha + ambientColor*(1.0-alpha);
imageStore(outTex,uv,vec4(dcolor,alpha));
}
這裡面程式碼最後倒數第二句實現混合背景時去掉,在這只是為了顯示檢視效果。
然後引用aoce_vulkan裡給的基類VkLayer,根據介面完成本身具體實現,相關VkChromKeyLayer的實現可以說是非常簡單,至少我認為達到我想要的方便。
還是一樣,先說遇到的坑,
-
開始在glsl中的UBO,我特意把一個float,vec3放一起,想當然的認為是按照vec4排列,這裡注意,vec3不管前後接什麼,大部分結構定義下,都至少佔vec4,所以後面為了和C++結構align一樣,全部用float.
-
層啟用/不啟用會導致整個運算graph重置,一般情況下,運算執行緒與結果輸出執行緒不在一起,在重置時,運算執行緒相關資源會重新生成,而此時輸出執行緒還在使用相關資源就會導致device lost錯誤,在這使用VkEvent用來表示是否在資源重置中。
然後就是與android UI層對接,android的UI沒怎麼用過,醜也就先這樣吧。