前不久支付寶推出了AR紅包的功能,風靡一時,最近剛好完成了這樣的需求,特此整理分享,以求相互學習、共同進步。有鑒于公司接下來可能的商用,就不在這里貼源碼了,僅簡單說一下實現思路和原理。
話不多講,先上流程圖:
藏紅包
一、初始化相機
相機類android.hardware.Camera
,但是很遺憾它過時了,Google在Android5.0后推出了更強大的android.hardware.camera2
包,但是為了兼容低版本這里依然使用android.hardware.Camera
類(有興趣的同學可以通過Android Camera2 拍照入門學習這篇了解camera2的實現方式)。
-
Camera.open()
開啟相機; -
Camera.getParameters()
獲取參數類并設置,缺少這一步相機的預覽畫面會變成橫向的、模糊的。
需要設置的內容:
parameters.setPictureSize
圖片分辨率,
parameters.setPreviewSize
預覽圖像分辨率,這兩項需要根據設備支持的列表具體計算這里不再贅述parameters.setPreviewFormat
預覽圖像轉碼格式,
parameters.setJpegQuality
照片質量,這里并不需要拍照可以忽略,
parameters.setFocusMode
對焦模式,連續對焦需要加camera.cancelAutoFocus()
camera.setDisplayOrientation
預覽畫面方向,這里旋轉90。
記得權限(Android6.0后需要動態申請。):
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera.flash" />
二、綁定surfaceView
- 找到surfaceView后
getHolder()
獲得surfaceHolder引用; -
holder.setType
設置類型; -
holder.addCallback
添加回調; -
Camera.setPreviewDisplay(holder)
將surfaceHolder傳入Camera; -
Camera.startPreview()
開啟預覽圖像。
三、獲取一幀預覽圖像
Camera.setOneShotPreviewCallback(PreviewCallback cb)
方法可以獲取一幀預覽圖像,數據會返回到PreviewCallback 回調中的 onPreviewFrame(byte[] data,Camera camera)
方法中,data即為幀數據。
四、預處理圖像
首先明確一點,處理圖像和接下來的其他耗時計算都要放到子線程中,不要問我為什么,編譯器會告訴你答案。onPreviewFrame方法中的data并不能直接直接轉為bitmap,需要先進行轉碼:
Camera.Size size = camera.getParameters().getPreviewSize();
YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);
Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
五、奇數幀?偶數幀?
為什么要分奇數偶數呢?當做好相機的準備工作后,要藏紅包,實際上需要解決的就是排除不需要的圖像,首先要做到的就是舍棄用戶移動時獲取的圖像,最先的想法是利用加速度傳感器,但是隨后發現手機勻速移動的時候是沒有加速度的,而這時獲取的圖像仍需舍棄。后來找到的解決方案是:不停獲取幀圖像,比較相鄰兩幀的相似度,當相似度達到某個閾值時視為相似,連續數次比較皆為相似可知用戶所對位置即用戶想要藏紅包的地方,將此時獲取的幀圖保存即可。
- 設置一個計數器記錄獲取幀圖的次數,計數器%2即可區分奇偶;
- 奇數幀存到圖1,偶數幀存圖2,比較圖1圖2;
- 不足兩圖、比較不相似就獲取新的一幀,即可實現不停的比較相鄰兩幀圖像。
六、相似?
比較相似的方法網上有挺多,有直接拿像素比較的,有處理成灰度圖像比較的,有轉成哈希值比較的,比較方法不同得到的數值也不同,所要設定的閾值也隨之改變,具體看需求能達到目的就好。細心的同學會發現支付寶在藏紅包的時候并不是所有地方都可以藏,有些沒有意義、過度單調的地方是沒辦法藏的,這里實現的方式是計算圖片像素“豐富度”(缺少圖像學知識暫且這么叫吧)。得到圖片所有像素點的值,去除重復,所得到的所有不同像素值的個數越小說明圖片顏色越“單調”,然后根據這個個數值設置閾值進行判斷。
七、確定紅包位置
得到滿足要求的幀圖后,停止獲取幀圖,停止相機預覽Camera.stopPreview()
,將圖片直接上傳服務器或者轉成哈希值傳給服務器,也可以同時傳些位置坐標等參數,這個看具體需求,這些數據即為所藏紅包的“指紋”。
找紅包
找紅包相對簡單,相同的邏輯不在贅述,只需要:
- 獲取已藏紅包的位置圖片,
- 與獲取的預覽幀圖比較,
- 不相似繼續取幀圖,相似停止取幀,即找到紅包。
可以根據需求增加位置坐標范圍判斷和用戶身份判斷。
一點思考
首先我們對比下掃描二維碼和掃描AR紅包的實現原理:
不難發現,整個掃描流程十分相似。其實在我看來,AR紅包可以理解為是一個特殊的“二維碼”,只不過這個“二維碼”上不再是特殊的幾何圖形,而是一張圖片。支付寶“掃一掃”中左邊是“二維碼”右邊是“AR”,有力地支持了我的邏輯。那么按照這個邏輯再來看整個AR紅包,可以發現“藏紅包”其實就是一個生成“二維碼”的過程,“找紅包”自然就是掃描“二維碼”了。
那么AR紅包又與二維碼有什么區別?
我們知道二維碼是按照一定規律生成的特殊幾何圖形,可以進行編碼解析,即二維碼中是內含信息的;而AR紅包實際上只是一張圖片,不包含信息。這也是為什么支付寶要通過紅包地圖和用戶的方式將AR紅包區分開的原因,否則如果兩個用戶在同一個位置藏了紅包,掃描時將無法區分它們。既然如此那為什么還要有AR紅包的存在呢?
AR紅包相對于二維碼的優缺點
優點:
- AR紅包藏匿于現實生活中。不同于二維碼:“我是一個二維碼,你們快來掃我呀O(∩_∩)O~~”,只要你不公開你的紅包位置圖,沒人知道這有紅包,當然你也可以讓部分人看到。媽媽再也不用擔心兒子的私房錢被老婆發現了!
- AR紅包來源于現實生活。所以就省去了張貼“二維碼”和更換“二維碼”的麻煩,同時也減少了二維碼給環境帶來的污染。試想一下:今天《金剛》首映掃“帝國大廈”可獲得電影票一張,掃“央視大褲衩”看今日新聞是不是很cooool~
- AR紅包可以一地兒多用。同樣一個“央視大褲衩”,央視掃是新聞聯播,網易掃是網易頭條,UC掃是UC震驚。
- 良好的用戶體驗。舉個栗子:相比于掃公司logo打卡和掃公司logo上的二維碼打卡,我選擇前者。
缺點:
- 本身不包含信息,需要其他參數輔助。沒有位置坐標和用戶信息,AR紅包幾乎無法運行。
- 人不能倆次踏入同一條河流。你藏紅包的地方可能隨時間變化再也無法復現,比如碎掉的存錢罐、昨天的夕陽和年少的你。
- 算法不夠完善。AR紅包剛出現不久,尚沒有統一的算法,每種比較的算法根據需求不同結果相差很大,識別效果和效率不夠完美,無法像二維碼一樣瞬間識別,還有待進一步完善。
實現AR紅包的過程中,讓我深深的感到對圖像處理知識的缺乏,這篇文章權當總結,一些淺見難免有疏忽紕漏,還往大家不吝賜教,共同進步。
參考:
Android二維碼掃描開發(一):實現思路與原理
Android實現圖片相似度
Android 手把手帶你玩轉自定義相機
Android Camera2 拍照入門學習