Android 修圖(換證件照背景,汙點修復)

北斗星_And發表於2019-09-11

背景

前段時間的一個週末,一個女生讓我幫她換一下他的證件照背景,我又沒帶電腦。我又不好意思拒接,怎麼辦呢?應用商店下載一個證件照換背景的APP,瞬間換完,我正準備儲存時,跳出來一個支付框,如果你要儲存,支付2元錢,出於面子,我只好掏了2塊錢,儲存了。於是我就想,這種技術活,還給別人付錢,自己來擼吧.我是一個專職Android開發,那麼就用Android來擼吧.

先來了解一下Android裡原生API對圖片操作,一般有兩種方式,

  1. 一種是利用好Canvas繪製圖片,
  2. 一種是利用Bitmap的原生API,獲取畫素進行操作

這兩種操作我都寫了對應的文章,可以快速檢視

Android  修圖(換證件照背景,汙點修復)

Android  修圖(換證件照背景,汙點修復)

今天的主題是在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 或者用側邊的輔助工具
    Android  修圖(換證件照背景,汙點修復)
  • 整合到你要使用OpenCv的專案,如下

Android  修圖(換證件照背景,汙點修復)

影像灰度測試

整合完成後,進行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了,是不是很簡單.

Android  修圖(換證件照背景,汙點修復)

換證件照背景 (從藍色到紅色)

換證件照演算法,直接使用了一個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

Android  修圖(換證件照背景,汙點修復)

效果

Android  修圖(換證件照背景,汙點修復)

汙點修復

修復原理

先來說一下汙點修復的演算法,一篇論文提到的 《An ImageInpainting Technique Based On the Fast Marching Method》

Android  修圖(換證件照背景,汙點修復)

可以簡單理解為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);
    }
複製程式碼

效果

Android  修圖(換證件照背景,汙點修復)

圖片來源:www.cnblogs.com/hellowooorl…

總結

本篇文章,主要介紹了OpenCV怎麼快速使用,並結合了兩個實際的例子,來進一步說明藉助OpenCV裡的API,可以實現很多不錯的效果.

文中圖片來源網路,若又侵權,請聯絡作者,立刻刪除!

本篇文章的兩個例子程式碼地址:github ,如果你喜歡迎star,後續關於圖片的操作,都會在此庫裡更新.

推薦閱讀

Android:讓你的“女神”逆襲,程式碼擼彩妝(畫妝)
Flutter PIP(畫中畫)效果的實現
Android 繪製原理淺析【乾貨】

相關文章