Android 修图(换证件照背景,污点修复)

背景

前段时间的一个周末,一个女生让我帮她换一下他的证件照背景,我又没带电脑。我又不好意思拒接,怎么办呢?应用商店下载一个证件照换背景的APP,瞬间换完,我正准备保存时,跳出来一个支付框,如果你要保存,支付2元钱,出于面子,我只好掏了2块钱,保存了。于是我就想,这种技术活,还给别人付钱,自己来撸吧.我是一个专职Android开发,那么就用Android来撸吧.

先来了解一下Android里原生API对图片操作,一般有两种方式,

  1. 一种是利用好Canvas绘制图片,
  2. 一种是利用Bitmap的原生API,获取像素进行操作

这两种操作我都写了对应的文章,可以快速查看

image
image

今天的主题是在Android里使用OpenCv来操作图片,并实现两个不同的效果,换证件照背景和污点修复.

代码已经托管在Github上,和上两篇文章代码地址一样,分支with-photo-changecolor

Github ,如果你喜欢,欢迎star 谢谢

Android OpenCv 快速入门

环境搭建

原生的API对图片的操作有限,并且一些颜色空间转化麻烦,效率低,那我们使用一个专业的图片操作库来操作图片,会变得容易些.

OpenCv有很多语言版本,当然底层是c/c++,他支持Android/IOS,Windows,Mac等,我们直接选择Android版本. 那么来搭建一下环境,有两部

  • 下载OpenCv SDK 地址,将SDK 打包成aar,集成到项目中,快速获取aar,可以直接到我打好的包里获取 Github中获取. 打aar包很简单,用Android Studio打开下载好的SDK,然后到其目录下,执行./gradlew assembleRelease 或者用侧边的辅助工具
    image
  • 集成到你要使用OpenCv的项目,如下
image

图像灰度测试

集成完成后,进行OpenCV SDK接入成功测试

private void initLoaderOpenCV() {
        boolean success = OpenCVLoader.initDebug();
        if (!success) {
            Log.d(TAG, "初始化失败");
        }
}

 public void gray(View view) {
        Mat src = new Mat();
        Mat dst = new Mat();
        Utils.bitmapToMat(bitmap, src);
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGRA2GRAY);
        Bitmap resultBitmap = getResultBitmap();
        Utils.matToBitmap(dst, resultBitmap);
        src.release();
        dst.release();

        showCompare(resultBitmap);
}

如果接入没问题,就可以愉快的使用OpenCV了,是不是很简单.


image

换证件照背景 (从蓝色到红色)

换证件照算法,直接使用了一个c++ 版本算法的,翻译为Android的. c++文章地址

主要步骤:

  1. 把RGB图像转换到HSV空间
  2. 取背景的一小块20*20,计算蓝色背景的平均色调和饱和度
  3. 设置阈值,取出蓝色背景替换为红色背景
  4. 把HSV图像转换会RGB空间
  5. 滤波器去除边缘效应

Android 代码如下:

 private void startDetail() {
        Mat image = new Mat();
        Utils.bitmapToMat(bitmap, image);

        Mat hsvImg = new Mat();
        Imgproc.cvtColor(image, hsvImg, Imgproc.COLOR_BGR2HSV);
        List<Mat> list = new ArrayList<>();
        Core.split(hsvImg, list);

        Mat roiH = list.get(0).submat(new Rect(0, 0, 20, 20));
        Mat roiS = list.get(1).submat(new Rect(0, 0, 20, 20));

        Log.i(TAG,"start sum bg");
        int SumH = 0;
        int SumS = 0;
        byte[] h = new byte[1];
        byte[] s = new byte[1];
        //取一块蓝色背景,计算出它的平均色调和平均饱和度
        for (int i = 0; i < 20; i++) {
            for (int j = 0; j < 20; j++) {
                roiH.get(j, i, h);
                roiS.get(j, i, s);

                SumH = h[0] + SumH;
                SumS = s[0] + SumS;
            }
        }
        int avgH, avgS;//蓝底的平均色调和平均饱和度
        avgH = SumH / 400;
        avgS = SumS / 400;
        
        Log.i(TAG,"depth="+list.get(0).depth());
        Log.i(TAG,"start sum detail all photo");
        //遍历整个图像
        int nl = hsvImg.height();
        int nc = hsvImg.width();
//        byte[] changeColor = new byte[]{127};

        byte[] hArray = new byte[nl * nc];
        byte[] sArray = new byte[nl * nc];
        byte[] vArray = new byte[nl * nc];

        list.get(0).get(0,0,hArray);
        list.get(1).get(0,0,sArray);
//        list.get(2).get(0,0,vArray);

        int row,index;
        for (int j = 0; j < nl; j++) {
            row = j * nc;
            for (int i = 0; i < nc; i++) {
                index = row + i;

                if(hArray[index] <= (avgH + 20) && hArray[index] >= (avgH - 20)
                        && sArray[index] <= (avgS + 150)
                        && sArray[index] >= (avgS -150)
                ){
                    hArray[index] = 127;
//                    sArray[index] = 0;
//                    vArray[index] = (byte) 255;
                }
            }
        }

        list.get(0).put(0,0,hArray);
        list.get(1).put(0,0,sArray);
//        list.get(2).put(0,0,vArray);


        Log.i(TAG,"merge photo");
        Core.merge(list,hsvImg);

        Imgproc.cvtColor(hsvImg,image, Imgproc.COLOR_HSV2BGR);

        Bitmap resultBitmap = getResultBitmap();
        Utils.matToBitmap(image,resultBitmap);
        Message obtain = Message.obtain();
        obtain.obj = resultBitmap;
        handler.sendMessage(obtain);
    }

Mat 为OpenCV中图像的保存,很类似Android里的Bitmap,他和Bitmap转化需要借助OpenCv的Utils进行,OpenCV的核心API可以查看官网,此处主要使用了Imgproc

image

效果

image

污点修复

修复原理

先来说一下污点修复的算法,一篇论文提到的 《An ImageInpainting Technique Based On the Fast Marching Method》

image

可以简单理解为p点为待修复区域,ε为修复半径,把ε的值区域的值计算出来,用于修复P点,直到修复整个Ω区域.

详细可以查看论文:论文地址

实际修复

OpenCV 里面已经实现了此算法,具体方法如下:

//OpenCV Photo.java
 /**
     * Restores the selected region in an image using the region neighborhood.
     *
     * @param src Input 8-bit, 16-bit unsigned or 32-bit float 1-channel or 8-bit 3-channel image.
     * @param inpaintMask Inpainting mask, 8-bit 1-channel image. Non-zero pixels indicate the area that
     * needs to be inpainted.
     * @param dst Output image with the same size and type as src .
     * @param inpaintRadius Radius of a circular neighborhood of each point inpainted that is considered
     * by the algorithm.
     * @param flags Inpainting method that could be cv::INPAINT_NS or cv::INPAINT_TELEA
     *
     * The function reconstructs the selected image area from the pixel near the area boundary. The
     * function may be used to remove dust and scratches from a scanned photo, or to remove undesirable
     * objects from still images or video. See &lt;http://en.wikipedia.org/wiki/Inpainting&gt; for more details.
     *
     * <b>Note:</b>
     * <ul>
     *   <li>
     *       An example using the inpainting technique can be found at
     *         opencv_source_code/samples/cpp/inpaint.cpp
     *   </li>
     *   <li>
     *       (Python) An example using the inpainting technique can be found at
     *         opencv_source_code/samples/python/inpaint.py
     *   </li>
     * </ul>
     */
    public static void inpaint(Mat src, Mat inpaintMask, Mat dst, double inpaintRadius, int flags) {
        inpaint_0(src.nativeObj, inpaintMask.nativeObj, dst.nativeObj, inpaintRadius, flags);
    }

其中上面提到的原理算法为,INPAINT_TELEA.

来一张实际的图操作修复一下,如下:

      private void startInpaint() {
        bitmap = BitmapUtils.getBitmapByAssetsNameRGB(this,"test.png");
        Mat desc = new Mat(bitmap.getHeight(),bitmap.getWidth(),CvType.CV_8UC3);
        //转化为mat对象
        Utils.bitmapToMat(bitmap, desc,true);
        //转化为3通道图像
        Mat src = new Mat();
        Imgproc.cvtColor(desc,src,Imgproc.COLOR_RGBA2RGB);
        //灰度图像
        Mat srcGray = new Mat();
        Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_RGB2GRAY);
        //中值滤波去燥
        Imgproc.medianBlur(srcGray,srcGray,3);
        //获取污点的二值化图像
        Mat srcThresh = new Mat();
        Imgproc.threshold(srcGray,srcThresh,242,255,Imgproc.THRESH_BINARY);

        Log.i("test","srcThresh channels:"+srcThresh.channels() + ",type:"+ CvType.typeToString(CvType.depth(srcThresh.type())));
        Log.i("test","src channels:"+src.channels() + ",type:"+ CvType.typeToString(CvType.depth(src.type())));
//        Bitmap resultBitmap = getResultBitmap();
//        Utils.matToBitmap(srcThresh, resultBitmap);
        
        //修复图像
        Mat inpaintResult = new Mat();
        Photo.inpaint(src,srcThresh,inpaintResult,3,Photo.INPAINT_TELEA);
        //把结果转化为bitmap 用于显示
        Bitmap resultBitmap = getResultBitmap();
        Utils.matToBitmap(inpaintResult, resultBitmap);
        Message obtain = Message.obtain();
        obtain.obj = resultBitmap;
        handler.sendMessage(obtain);
    }

效果

image

图片来源:https://www.cnblogs.com/hellowooorld/p/7048614.html

总结

本篇文章,主要介绍了OpenCV怎么快速使用,并结合了两个实际的例子,来进一步说明借助OpenCV里的API,可以实现很多不错的效果.

文中图片来源网络,若又侵权,请联系作者,立刻删除!

本篇文章的两个例子代码地址:github ,如果你喜欢迎star,后续关于图片的操作,都会在此库里更新.

推荐阅读

Android:让你的“女神”逆袭,代码撸彩妆(画妆)
Flutter PIP(画中画)效果的实现
Android 绘制原理浅析【干货】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容