b
效果如下
原图
背景图片
处理效果图片
1、简介
1-1、使用 Opencv 实现单一背景的自动抠图功能,用 Opencv 去画出对应的要扣出来的图像的 mask 区域,
1-2、把这块黑白的 mask 进行模糊处理和腐蚀,目的就是为了平滑边缘,和后面进行原图元素点和背景元素点进行元素融合,达到比较好点的抠图效果(就是尽量减少噪点)
1-3、然后从原图(要进行抠图的图像)上抠出来对应的 mask 这块的原图图像和边缘元素点和背景图元素点进行元素点融合处理。
2、准备工具
2-1、Opencv 环境
如果没有安装的过的请参考我这篇博客
3、代码区
3-1、核心代码区域
/**
* @Description 进行图片抠图
* @author 姚旭民
* @date 2019/12/30 15:07
*/
@SuppressLint("ResourceType")
public void testCutImage(Bitmap bitmap) {
try {
//这个是我写的测试用的背景图,你可以后面修改成参数传递进来
Resources r = getApplicationContext().getResources();
InputStream is = r.openRawResource(R.drawable.test4);
BitmapDrawable bmpDraw = new BitmapDrawable(is);
Bitmap backgroundBitmap = bmpDraw.getBitmap();
backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap, bitmap.getWidth(), bitmap.getHeight(), true);
Log.d(TAG, "testCutImage| 开始变换 bitmap : " + bitmap);
/*FileInputStream fis = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/CutOutPeople/test2.jpg");
Bitmap bitmap = BitmapFactory.decodeStream(fis);*/
Log.d(TAG, "testCutImage| bitmap : " + bitmap);
Mat img = new Mat();
//缩小图片尺寸
// Bitmap bm = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), true);
//bitmap->mat
Utils.bitmapToMat(bitmap, img);
//转成CV_8UC3格式
Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2RGB);
//这个是抠图范围左上角和右下角,如果有问题修改这里
Rect rect = new Rect(10, 10, bitmap.getWidth() - 50, bitmap.getHeight() - 10);
//生成遮板
Mat mask = new Mat();
Mat bgModel = new Mat();
Mat fgModel = new Mat(0, 0, CvType.CV_8U, new Scalar(155, 15, 10));
Mat source = new Mat(1, 1, CvType.CV_8U, new Scalar(Imgproc.GC_PR_FGD));
// firstMask.create(img.size(), CvType.CV_8UC1, new Scalar(0));
// bgModel.create(img.size(),CvType.CV_8UC1);
// fgModel.create(img.size(),CvType.CV_8UC1);
Imgproc.grabCut(img, mask, rect, bgModel, fgModel, 5, Imgproc.GC_INIT_WITH_RECT);
Core.compare(mask, source, mask, Core.CMP_EQ);
// 边缘模糊处理
Mat blurMask = new Mat();
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1));
//膨胀
Imgproc.dilate(mask, mask, element, new Point(-1, -1), 1);
//腐蚀
Mat kernelErode = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1));
Imgproc.erode(mask, mask, kernelErode);
// 双边模糊
// Imgproc.bilateralFilter(mask, blurMask, 3, 200, 200);
//高斯模糊
Imgproc.GaussianBlur(mask, blurMask, new Size(3, 3), 0, 0);
//保存模糊后的mask图像
Bitmap b = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(blurMask, b);
//图片保存
saveBitmap(b, FileConts.PATH_CUT_IMAGE + "mask" + System.currentTimeMillis() + ".png");
//抠图
// Mat foreground = new Mat(img.size(), CvType.CV_8U, new Scalar(196, 15, 24, 0));
Mat destMat = new Mat(img.size(), CvType.CV_8UC3, new Scalar(255, 255, 255));
Mat backgroundMat = new Mat();
Utils.bitmapToMat(backgroundBitmap, backgroundMat);
Imgproc.cvtColor(backgroundMat, backgroundMat, Imgproc.COLOR_RGBA2RGB);
Bitmap c = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(destMat, c);
//将img图像的mask选中的图像扣出来放在foreground中
img.copyTo(destMat, blurMask);
//进行像素替换
double[] m, backgroundColor, imgColor;
int blackSum = 0;
int whiteSum = 0;
int otherSum = 0;
//用来计算权重
double w;
double imgColor1, imgColor2, imgColor3;
double backgroundColor1, backgroundColor2, backgroundColor3;
double[] mixColor = new double[3];
int length1 = 0;
int length2 = 0;
int length3 = 0;
int imgLength1 = 0;
int imgLength2 = 0;
int imgLength3 = 0;
int backgroundLength1 = 0;
int backgroundLength2 = 0;
int backgroundLength3 = 0;
//这里是比较重要,进行图像的替换和原图边缘和背景图的元素融合处理
for (int i = 0; i < img.rows(); i++) {
for (int j = 0; j < img.cols(); j++) {
//当前元素数组
m = blurMask.get(i, j);
if (m[0] == 255) {//抠图部分
destMat.put(i, j, img.get(i, j));
whiteSum++;
} else if (m[0] == 0) {//背景黑色部分用别的图片代替
destMat.put(i, j, backgroundMat.get(i, j));
blackSum++;
} else {//mask 被模糊处理之后的人像边缘区域,要进行元素融合处理,让抠图效果更好一点
otherSum++;
//计算权重
w = m[0] / 255.0;
//获取前景元素
imgColor = img.get(i, j);
//获取背景元素
backgroundColor = backgroundMat.get(i, j);
//前景元素
imgColor1 = imgColor[0];
imgColor2 = imgColor[1];
imgColor3 = imgColor[2];
//背景元素
backgroundColor1 = backgroundColor[0];
backgroundColor2 = backgroundColor[1];
backgroundColor3 = backgroundColor[2];
//元素点混合
mixColor[0] = imgColor1 * w + backgroundColor1 * (1.0 - w);
mixColor[1] = imgColor2 * w + backgroundColor2 * (1.0 - w);
mixColor[2] = imgColor3 * w + backgroundColor3 * (1.0 - w);
//进行存放
destMat.put(i, j, mixColor);
}
}
}
Log.d(TAG, "替换完毕,展示各个累加值,blackSum : " + blackSum + ",whiteSum : " + whiteSum + ",otherSum : " + otherSum);
Log.d(TAG, "替换完毕,展示各个累加值,length1 : " + length1 + ",length2 : " + length2 + ",length3 : " + length3);
Log.d(TAG, "替换完毕,展示各个累加值,backgroundLength1 : " + backgroundLength1 + ",backgroundLength2 : " + backgroundLength2 + ",backgroundLength3 : " + backgroundLength3);
Log.d(TAG, "替换完毕,展示各个累加值,imgLength1 : " + imgLength1 + ",imgLength2 : " + imgLength2 + ",imgLength3 : " + imgLength3);
//将处理的foreground图像保存
Utils.matToBitmap(destMat, b);
mImage.setImageBitmap(b);
Mat img3 = new Mat();
img.copyTo(img3);
img3.setTo(new Scalar(0), blurMask);
Bitmap img3Three = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(img3, img3Three);
//图像保存
saveBitmap(b, FileConts.PATH_CUT_IMAGE + System.currentTimeMillis() + ".png");
saveBitmap(c, FileConts.PATH_CUT_IMAGE + "forMat" + System.currentTimeMillis() + ".png");
saveBitmap(backgroundBitmap, FileConts.PATH_CUT_IMAGE + "background" + System.currentTimeMillis() + ".png");
saveBitmap(img3Three, FileConts.PATH_CUT_IMAGE + "imgThree" + System.currentTimeMillis() + ".png");
} catch (Exception e) {
Log.e(TAG, "异常信息为 : " + e.toString());
e.printStackTrace();
}
}
3-1、完整 Activity 类(里面有我自己的一些工具类,不过是一些小功能,自己实现就好)
package com.yxm.cutoutpeople.activity;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.Toast;
import com.yxm.cutoutpeople.R;
import com.yxm.cutoutpeople.common.conts.FileConts;
import com.yxm.cutoutpeople.common.thread.MgThread;
import com.yxm.cutoutpeople.common.utils.FileUtils;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class TestActivity extends Activity implements CameraBridgeViewBase.CvCameraViewListener2 {
private static final String TAG = "yaoxuminTest";
private Handler mHandler;
ImageView mImage;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImage = findViewById(R.id.testImg);
mHandler = new Handler();
//清理上次的保存结果
MgThread.exceute(new Runnable() {
@Override
public void run() {
FileUtils.delAllFile(FileConts.PATH_CUT_IMAGE);
FileUtils.exitOrCreatePath(FileConts.PATH_CUT_IMAGE);
}
});
}
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.e(TAG, "OpenCV init error");
}
Resources r = getApplicationContext().getResources();
@SuppressLint("ResourceType") InputStream is = r.openRawResource(R.drawable.test2);
BitmapDrawable bmpDraw = new BitmapDrawable(is);
Bitmap bitmap = bmpDraw.getBitmap();
testCutImage(bitmap);
}
/**
* @Description 进行图片抠图
* @author 姚旭民
* @date 2019/12/30 15:07
*/
@SuppressLint("ResourceType")
public void testCutImage(Bitmap bitmap) {
try {
//这个是我写的测试用的背景图,你可以后面修改成参数传递进来
Resources r = getApplicationContext().getResources();
InputStream is = r.openRawResource(R.drawable.test4);
BitmapDrawable bmpDraw = new BitmapDrawable(is);
Bitmap backgroundBitmap = bmpDraw.getBitmap();
backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap, bitmap.getWidth(), bitmap.getHeight(), true);
Log.d(TAG, "testCutImage| 开始变换 bitmap : " + bitmap);
/*FileInputStream fis = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/CutOutPeople/test2.jpg");
Bitmap bitmap = BitmapFactory.decodeStream(fis);*/
Log.d(TAG, "testCutImage| bitmap : " + bitmap);
Mat img = new Mat();
//缩小图片尺寸
// Bitmap bm = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), true);
//bitmap->mat
Utils.bitmapToMat(bitmap, img);
//转成CV_8UC3格式
Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2RGB);
//这个是抠图范围左上角和右下角,如果有问题修改这里
Rect rect = new Rect(10, 10, bitmap.getWidth() - 50, bitmap.getHeight() - 10);
//生成遮板
Mat mask = new Mat();
Mat bgModel = new Mat();
Mat fgModel = new Mat(0, 0, CvType.CV_8U, new Scalar(155, 15, 10));
Mat source = new Mat(1, 1, CvType.CV_8U, new Scalar(Imgproc.GC_PR_FGD));
// firstMask.create(img.size(), CvType.CV_8UC1, new Scalar(0));
// bgModel.create(img.size(),CvType.CV_8UC1);
// fgModel.create(img.size(),CvType.CV_8UC1);
Imgproc.grabCut(img, mask, rect, bgModel, fgModel, 5, Imgproc.GC_INIT_WITH_RECT);
Core.compare(mask, source, mask, Core.CMP_EQ);
// 边缘模糊处理
Mat blurMask = new Mat();
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1));
//膨胀
Imgproc.dilate(mask, mask, element, new Point(-1, -1), 1);
//腐蚀
Mat kernelErode = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1));
Imgproc.erode(mask, mask, kernelErode);
// 双边模糊
// Imgproc.bilateralFilter(mask, blurMask, 3, 200, 200);
//高斯模糊
Imgproc.GaussianBlur(mask, blurMask, new Size(3, 3), 0, 0);
//保存模糊后的mask图像
Bitmap b = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(blurMask, b);
//图片保存
saveBitmap(b, FileConts.PATH_CUT_IMAGE + "mask" + System.currentTimeMillis() + ".png");
//抠图
// Mat foreground = new Mat(img.size(), CvType.CV_8U, new Scalar(196, 15, 24, 0));
Mat destMat = new Mat(img.size(), CvType.CV_8UC3, new Scalar(255, 255, 255));
Mat backgroundMat = new Mat();
Utils.bitmapToMat(backgroundBitmap, backgroundMat);
Imgproc.cvtColor(backgroundMat, backgroundMat, Imgproc.COLOR_RGBA2RGB);
Bitmap c = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(destMat, c);
//将img图像的mask选中的图像扣出来放在foreground中
img.copyTo(destMat, blurMask);
//进行像素替换
double[] m, backgroundColor, imgColor;
int blackSum = 0;
int whiteSum = 0;
int otherSum = 0;
//用来计算权重
double w;
double imgColor1, imgColor2, imgColor3;
double backgroundColor1, backgroundColor2, backgroundColor3;
double[] mixColor = new double[3];
int length1 = 0;
int length2 = 0;
int length3 = 0;
int imgLength1 = 0;
int imgLength2 = 0;
int imgLength3 = 0;
int backgroundLength1 = 0;
int backgroundLength2 = 0;
int backgroundLength3 = 0;
//这里是比较重要,进行图像的替换和原图边缘和背景图的元素融合处理
for (int i = 0; i < img.rows(); i++) {
for (int j = 0; j < img.cols(); j++) {
//当前元素数组
m = blurMask.get(i, j);
if (m[0] == 255) {//抠图部分
destMat.put(i, j, img.get(i, j));
whiteSum++;
} else if (m[0] == 0) {//背景黑色部分用别的图片代替
destMat.put(i, j, backgroundMat.get(i, j));
blackSum++;
} else {//mask 被模糊处理之后的人像边缘区域,要进行元素融合处理,让抠图效果更好一点
otherSum++;
//计算权重
w = m[0] / 255.0;
//获取前景元素
imgColor = img.get(i, j);
//获取背景元素
backgroundColor = backgroundMat.get(i, j);
//前景元素
imgColor1 = imgColor[0];
imgColor2 = imgColor[1];
imgColor3 = imgColor[2];
//背景元素
backgroundColor1 = backgroundColor[0];
backgroundColor2 = backgroundColor[1];
backgroundColor3 = backgroundColor[2];
//元素点混合
mixColor[0] = imgColor1 * w + backgroundColor1 * (1.0 - w);
mixColor[1] = imgColor2 * w + backgroundColor2 * (1.0 - w);
mixColor[2] = imgColor3 * w + backgroundColor3 * (1.0 - w);
//进行存放
destMat.put(i, j, mixColor);
}
}
}
Log.d(TAG, "替换完毕,展示各个累加值,blackSum : " + blackSum + ",whiteSum : " + whiteSum + ",otherSum : " + otherSum);
Log.d(TAG, "替换完毕,展示各个累加值,length1 : " + length1 + ",length2 : " + length2 + ",length3 : " + length3);
Log.d(TAG, "替换完毕,展示各个累加值,backgroundLength1 : " + backgroundLength1 + ",backgroundLength2 : " + backgroundLength2 + ",backgroundLength3 : " + backgroundLength3);
Log.d(TAG, "替换完毕,展示各个累加值,imgLength1 : " + imgLength1 + ",imgLength2 : " + imgLength2 + ",imgLength3 : " + imgLength3);
//将处理的foreground图像保存
Utils.matToBitmap(destMat, b);
mImage.setImageBitmap(b);
Mat img3 = new Mat();
img.copyTo(img3);
img3.setTo(new Scalar(0), blurMask);
Bitmap img3Three = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(img3, img3Three);
//图像保存
saveBitmap(b, FileConts.PATH_CUT_IMAGE + System.currentTimeMillis() + ".png");
saveBitmap(c, FileConts.PATH_CUT_IMAGE + "forMat" + System.currentTimeMillis() + ".png");
saveBitmap(backgroundBitmap, FileConts.PATH_CUT_IMAGE + "background" + System.currentTimeMillis() + ".png");
saveBitmap(img3Three, FileConts.PATH_CUT_IMAGE + "imgThree" + System.currentTimeMillis() + ".png");
} catch (Exception e) {
Log.e(TAG, "异常信息为 : " + e.toString());
e.printStackTrace();
}
}
public static void saveBitmap(Bitmap bitmap, String path) {
String savePath;
File filePic;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
savePath = path;
} else {
Log.d(TAG, "saveBitmap failure : sdcard not mounted");
return;
}
// Log.d(TAG, "saveBitmap savePath : " + savePath);
try {
filePic = new File(savePath);
if (!filePic.exists()) {
filePic.getParentFile().mkdirs();
filePic.createNewFile();
}
FileOutputStream fos = new FileOutputStream(filePic);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (IOException e) {
Log.d(TAG, "saveBitmap: " + e.getMessage());
return;
}
Log.d(TAG, "saveBitmap success: " + filePic.getAbsolutePath());
}
@Override
public void onCameraViewStarted(int width, int height) {
}
@Override
public void onCameraViewStopped() {
}
/**
* @Description 在这里实现人脸检测和性别年龄识别
* @author 姚旭民
* @date 2019/7/24 12:16
*/
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
return inputFrame.rgba();
}
}