美文网首页iOS 开发每天分享优质文章
iOS 用opencv实现抠人物头像(最新)

iOS 用opencv实现抠人物头像(最新)

作者: 吾乃长沙赵子龙 | 来源:发表于2018-12-29 11:56 被阅读446次

    前段时间公司要开发一个自拍换背景的证件照软件,之前从来没有接触过这个方面。于是看了很多相关文章,在github上面找到了一个大哥写的关于抠图算法的封装(详情可以查看github上面这篇文章 https://github.com/np-csu/AlphaMatting)慢慢的有了思路。开始搞事情。。

    2019.1.3 补充了uiimage和cv::mat相互转换,以及放大镜和擦除

    简单的介绍一下流程,只需要做以下三步:

    第一步:在原图上面画线,得到 mask 图

    原图 画线图 mask 图

    第二步:调用已经下载好的抠图算法,原图+mask图  融合出

    融合图

    第三步:用原图+融合图+背景图(以蓝色背景为例)  做融合

    结果图

    好了 更换背景成功,是不是觉得很神奇。



    那下面我们来详细的讲解一下以上三步在iOS中是怎么实现的(大家如果有更好的思路可以提出来互相学习)

    第一步:在原图上面画线,得到 mask 图(详解)

    1.画线(标记需要处理的区域)

    在这里 讲解一下得到mask图原理,以便于理解下面详细的步骤

    原理: 一张图片,分为前景和背景,想更换背景,就要把前景和背景分离。已经确认的前景和背景我们不对它进行处理,真正要分离的就是背景和前景的交界处。那么这块要处理的区域我们通过画线来标记,标记为待处理区域。画完线之后,那些没有被画线的点我们如何来标记为前景和背景呢。这里就用到了种子生长算法(不知道的可以去百度一下),首先我们在左上角取一个生长点进行区域生长,生长过的区域我们把它标记为背景,遇到待处理区域,就停止生长。没有生长过 ,也没有标记过的地方把它标记为前景。这样mask图就出来了。说这么多,来一张图吧,如图

    mask

    前期工作:创建一个全局的可变二维数组和原图矩阵(就原图image转换cv::mat)

    思路:创建一个touchview 。根据手势划过的地方,如果设置线宽为20,取到 touchmove 走过的每一个点为中心边长为20的正方形内的点。同时去计算正方形中所有的点距离中心点的距离,把原图矩阵上 距离小于等于10的所有点的rgb值置为你想要设置的颜色,同时在二维数组上面也将这些点置为128。这样两个矩阵中就形成了和贝塞尔曲线 一样的线。现在我们创建一个矩阵大小的cv::mat格式的空白区域,要求8bit,无符号整形, 4通道。然后遍历数组,把矩阵置为跟二维数组一样的值。说到这里你肯定有点懵,来一段代码清醒一下

    补充一下画线方法:这里没有采用贝塞尔曲线,而是直接在原图上面修改像素点。将划过的像素点置为255,0,0。 至于线的粗细,可以通过for循环来置。例如线宽10,那么循环就是 x-10--->x+10,  y-10--->y+10

    得到的是正方形。

    处理一下变成圆形:计算当前点 距离中心点(x,y)这个点的距离,小于半径就可以了。只置半径内的像素点

    在画线的事件里面有一点需要非常注意的:

    在划的过程中一定要去判断这个点是否被置过,如果置过就不要重复再置了。如果半径为30,每划过一个点都要置3600个点。判断之后只需要置60个点。

    2019.1.3 补充:

    有很多人问UIimage转cv::mat 和 cv::mat 转UIimage怎么转    贴一下我的转换代码

    uiimage转cv::mat cv::mat转uiimage

    现在需要处理的区域是标记了,我们来标记前景和背景

    2.种子生长标记前景和背景

    选取一个左上角的点对mask矩阵进行种子生长。把生长过的区域置为0,把没有生长过,也没有划过的部分置为255。mask的矩阵就出来了。看代码

    (喜欢学习的人可以看一下)错误的思路:创建一个touchview ,创建和图片一样大小的矩阵每个点置为255。根据手势划过的地方用贝塞尔曲线连接,如果设置线宽为20,以 touchmove 走过的每一个点为中心画边长为20的正方形。同时去计算正方形中所有的点距离中心点的距离,把距离小于等于10的所有点置为128。这样矩阵中就形成了和touchview上贝塞尔曲线 一样的线。选取一个左上角的点对矩阵进行种子生长。把生长过的区域置为0,把没有生长过,也没有划过的部分置为255。mask的矩阵就出来了。然后创建一个矩阵大小的cv::mat格式的空白区域,要求8bit,无符号整形, 4通道。这个思路为什么是错的,因为用贝塞尔曲线的思路,如果要实现擦除功能是可以的,但是原图和mask上的点需要一一对应去执行,这点就比较难做到。

    第一步走完了,不知道我说明白了没有。第一步能理解,很重要。让我们进入第二步



    第二步:调用已经下载好的抠图算法,原图+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图-->经过吧啦吧啦一系列处理-->得到融合图

    传入的是照片本地地址,我看了下它里面还是转成cv::mat格式去执行了,建议修改一下它里面的源码,让这几个接口直接传入cv::mat。这样我们就可以不用保存到本地再传入了。最后一个接口是做本地存储,不想做存储怎么办,在它的代码里面可以新加一个接口。直接把得到的cv::mat返回回来。转换成uiimage就可以展示了。来一张效果图

    融合出来的图

    这一步需要注意的地方:cv::mat格式的原图和mask图在大小,字节和通道上一定要保持一致,不然报错了找都找不到。


    第三步:用原图+融合图+背景图(以蓝色背景为例)  做融合

    列一下融合公式(以下都是cv::mat格式的矩阵)

    最终的结果图=原图矩阵  x(  融合图矩阵 / 255矩阵) + 背景矩阵 x(255矩阵-融合图矩阵)/255矩阵

    简单的解释一下:因为(  融合图矩阵 / 255矩阵)只有0和1.    ➕号之前得到的是前景,➕号之后得到的是背景。 相加就是全景。


    该踩的坑都踩过了,应该会简便一些。

    因为画线的时候 手指会挡住图片,需要画线的时候放大镜显示,以及画错之后小面积擦除。本人已经做好了,下次找个时间更新文章吧。难以掩盖即将要过元旦节的激动,提前祝大家新年快乐。

    2018.12.29    下午 5:27  下班了

    2019.1.3  下午3:38 更新

    如何实现画线过程中的放大镜(效果图如下)

    放大镜演示

    第一步:在touchbegan中截屏,在截屏的图上取手势划过的地方(范围自己取)显示在放大镜控件(uiimageview)中

    第二步:在touchmove中取手势划过的地方(范围自己取),显示在放大镜控件(uiimageview)中,在事件中不断改变放大镜的位置。这一步关键在于原图已经画线了,放大的部位是从截屏上取的没有画线的,那怎么处理这一步呢。不要重复的去截屏来保持同步显示。在截屏的图中同步画线就行了,这一步很关键

    (画线的方法在之前说过了)。

    简单的贴下代码

    实现擦除功能(这一步比较简单)

    实际上就是手势触及到的部位要恢复成原图。

    1.做操作之前,保存原图,保存截屏图(用于放大镜的截屏图)

    2.手势触及的部位(范围自己取),从原图上取这一块的rgb值,通过for循环来修改已经画线的图对应的位置。

    贴一下for循环里面的代码,应该比较好理解

    因为代码是属于公司的,不方便透漏,所以就没上传代码。如果我有什么地方说得不明白可以私信我。很乐意一起学习。觉得有用的话,顺手点个赞。谢谢🙏

    相关文章

      网友评论

        本文标题:iOS 用opencv实现抠人物头像(最新)

        本文链接:https://www.haomeiwen.com/subject/pjqovxtx.html