前段時間公司要開發(fā)一個自拍換背景的證件照軟件,之前從來沒有接觸過這個方面。于是看了很多相關(guān)文章,慢慢的有了思路。開始搞事情。。
2019.1.3 補(bǔ)充了uiimage和cv::mat相互轉(zhuǎn)換,以及放大鏡和擦除
簡單的介紹一下流程,只需要做以下三步:
第一步:在原圖上面畫線,得到 mask 圖



第二步:調(diào)用已經(jīng)下載好的摳圖算法,原圖+mask圖 ?融合出

第三步:用原圖+融合圖+背景圖(以藍(lán)色背景為例) ?做融合

好了 更換背景成功,是不是覺得很神奇。
那下面我們來詳細(xì)的講解一下以上三步在iOS中是怎么實(shí)現(xiàn)的(大家如果有更好的思路可以提出來互相學(xué)習(xí))
第一步:在原圖上面畫線,得到 mask 圖(詳解)
1.畫線(標(biāo)記需要處理的區(qū)域)
在這里 講解一下得到mask圖原理,以便于理解下面詳細(xì)的步驟
原理: 一張圖片,分為前景和背景,想更換背景,就要把前景和背景分離。已經(jīng)確認(rèn)的前景和背景我們不對它進(jìn)行處理,真正要分離的就是背景和前景的交界處。那么這塊要處理的區(qū)域我們通過畫線來標(biāo)記,標(biāo)記為待處理區(qū)域。畫完線之后,那些沒有被畫線的點(diǎn)我們?nèi)绾蝸順?biāo)記為前景和背景呢。這里就用到了種子生長算法(不知道的可以去百度一下),首先我們在左上角取一個生長點(diǎn)進(jìn)行區(qū)域生長,生長過的區(qū)域我們把它標(biāo)記為背景,遇到待處理區(qū)域,就停止生長。沒有生長過 ,也沒有標(biāo)記過的地方把它標(biāo)記為前景。這樣mask圖就出來了。說這么多,來一張圖吧,如圖

前期工作:創(chuàng)建一個全局的可變二維數(shù)組和原圖矩陣(就原圖image轉(zhuǎn)換cv::mat)
思路:創(chuàng)建一個touchview 。根據(jù)手勢劃過的地方,如果設(shè)置線寬為20,取到 touchmove 走過的每一個點(diǎn)為中心邊長為20的正方形內(nèi)的點(diǎn)。同時去計(jì)算正方形中所有的點(diǎn)距離中心點(diǎn)的距離,把原圖矩陣上 距離小于等于10的所有點(diǎn)的rgb值置為你想要設(shè)置的顏色,同時在二維數(shù)組上面也將這些點(diǎn)置為128。這樣兩個矩陣中就形成了和貝塞爾曲線 一樣的線?,F(xiàn)在我們創(chuàng)建一個矩陣大小的cv::mat格式的空白區(qū)域,要求8bit,無符號整形, 4通道。然后遍歷數(shù)組,把矩陣置為跟二維數(shù)組一樣的值。說到這里你肯定有點(diǎn)懵,來一段代碼清醒一下
補(bǔ)充一下畫線方法:這里沒有采用貝塞爾曲線,而是直接在原圖上面修改像素點(diǎn)。將劃過的像素點(diǎn)置為255,0,0。 至于線的粗細(xì),可以通過for循環(huán)來置。例如線寬10,那么循環(huán)就是 x-10--->x+10, ?y-10--->y+10
得到的是正方形。
處理一下變成圓形:計(jì)算當(dāng)前點(diǎn) 距離中心點(diǎn)(x,y)這個點(diǎn)的距離,小于半徑就可以了。只置半徑內(nèi)的像素點(diǎn)

在畫線的事件里面有一點(diǎn)需要非常注意的:
在劃的過程中一定要去判斷這個點(diǎn)是否被置過,如果置過就不要重復(fù)再置了。如果半徑為30,每劃過一個點(diǎn)都要置3600個點(diǎn)。判斷之后只需要置60個點(diǎn)。
2019.1.3 補(bǔ)充:
有很多人問UIimage轉(zhuǎn)cv::mat 和 cv::mat 轉(zhuǎn)UIimage怎么轉(zhuǎn) ? ?貼一下我的轉(zhuǎn)換代碼


現(xiàn)在需要處理的區(qū)域是標(biāo)記了,我們來標(biāo)記前景和背景
2.種子生長標(biāo)記前景和背景
選取一個左上角的點(diǎn)對mask矩陣進(jìn)行種子生長。把生長過的區(qū)域置為0,把沒有生長過,也沒有劃過的部分置為255。mask的矩陣就出來了??创a

(喜歡學(xué)習(xí)的人可以看一下)錯誤的思路:創(chuàng)建一個touchview ,創(chuàng)建和圖片一樣大小的矩陣每個點(diǎn)置為255。根據(jù)手勢劃過的地方用貝塞爾曲線連接,如果設(shè)置線寬為20,以 touchmove 走過的每一個點(diǎn)為中心畫邊長為20的正方形。同時去計(jì)算正方形中所有的點(diǎn)距離中心點(diǎn)的距離,把距離小于等于10的所有點(diǎn)置為128。這樣矩陣中就形成了和touchview上貝塞爾曲線 一樣的線。選取一個左上角的點(diǎn)對矩陣進(jìn)行種子生長。把生長過的區(qū)域置為0,把沒有生長過,也沒有劃過的部分置為255。mask的矩陣就出來了。然后創(chuàng)建一個矩陣大小的cv::mat格式的空白區(qū)域,要求8bit,無符號整形, 4通道。這個思路為什么是錯的,因?yàn)橛秘惾麪柷€的思路,如果要實(shí)現(xiàn)擦除功能是可以的,但是原圖和mask上的點(diǎn)需要一一對應(yīng)去執(zhí)行,這點(diǎn)就比較難做到。
第一步走完了,不知道我說明白了沒有。第一步能理解,很重要。讓我們進(jìn)入第二步
第二步:調(diào)用已經(jīng)下載好的摳圖算法,原圖+mask圖 =融合圖
SharedMatting sm;
sm.loadImage(pathToImage); // load image from pathToImage
sm.loadTrimap(pathToTrimap); // load Trimap from pathToTrimap
sm.solveAlpha(); // do the shared matting algorithm
sm.save(pathToSave); // save result image
以上就是github上面的算法提供的接口,什么意思呢。
傳入原圖-->傳入mask圖-->經(jīng)過吧啦吧啦一系列處理-->得到融合圖
傳入的是照片本地地址,我看了下它里面還是轉(zhuǎn)成cv::mat格式去執(zhí)行了,建議修改一下它里面的源碼,讓這幾個接口直接傳入cv::mat。這樣我們就可以不用保存到本地再傳入了。最后一個接口是做本地存儲,不想做存儲怎么辦,在它的代碼里面可以新加一個接口。直接把得到的cv::mat返回回來。轉(zhuǎn)換成uiimage就可以展示了。來一張效果圖

這一步需要注意的地方:cv::mat格式的原圖和mask圖在大小,字節(jié)和通道上一定要保持一致,不然報(bào)錯了找都找不到。
第三步:用原圖+融合圖+背景圖(以藍(lán)色背景為例) ?做融合
列一下融合公式(以下都是cv::mat格式的矩陣)
最終的結(jié)果圖=原圖矩陣 ?x( ?融合圖矩陣 / 255矩陣) + 背景矩陣 x(255矩陣-融合圖矩陣)/255矩陣
簡單的解釋一下:因?yàn)椋??融合圖矩陣 / 255矩陣)只有0和1. ? ??號之前得到的是前景,?號之后得到的是背景。 相加就是全景。
該踩的坑都踩過了,應(yīng)該會簡便一些。
因?yàn)楫嬀€的時候 手指會擋住圖片,需要畫線的時候放大鏡顯示,以及畫錯之后小面積擦除。本人已經(jīng)做好了,下次找個時間更新文章吧。難以掩蓋即將要過元旦節(jié)的激動,提前祝大家新年快樂。
2018.12.29 ? ?下午 5:27 ?下班了
2019.1.3 ?下午3:38 更新
如何實(shí)現(xiàn)畫線過程中的放大鏡(效果圖如下)

第一步:在touchbegan中截屏,在截屏的圖上取手勢劃過的地方(范圍自己?。╋@示在放大鏡控件(uiimageview)中
第二步:在touchmove中取手勢劃過的地方(范圍自己取),顯示在放大鏡控件(uiimageview)中,在事件中不斷改變放大鏡的位置。這一步關(guān)鍵在于原圖已經(jīng)畫線了,放大的部位是從截屏上取的沒有畫線的,那怎么處理這一步呢。不要重復(fù)的去截屏來保持同步顯示。在截屏的圖中同步畫線就行了,這一步很關(guān)鍵
(畫線的方法在之前說過了)。
簡單的貼下代碼

實(shí)現(xiàn)擦除功能(這一步比較簡單)
實(shí)際上就是手勢觸及到的部位要恢復(fù)成原圖。
1.做操作之前,保存原圖,保存截屏圖(用于放大鏡的截屏圖)
2.手勢觸及的部位(范圍自己取),從原圖上取這一塊的rgb值,通過for循環(huán)來修改已經(jīng)畫線的圖對應(yīng)的位置。
貼一下for循環(huán)里面的代碼,應(yīng)該比較好理解

因?yàn)榇a是屬于公司的,不方便透漏,所以就沒上傳代碼。如果我有什么地方說得不明白可以私信我。很樂意一起學(xué)習(xí)。覺得有用的話,順手點(diǎn)個贊。謝謝??