原文博客:Doi技术团队
链接地址:https://blog.doiduoyi.com/authors/1584446358138
初心:记录优秀的Doi技术团队学习经历
本文链接:Android基于图像语义分割实现人物背景更换

本教程是通过PaddlePaddle的PaddleSeg实现的,该开源库的地址为:http://github.com/PaddlPaddle/PaddleSeg ,使用开源库提供的预训练模型实现人物的图像语义分割,最终部署到Android应用上。关于如何在Android应用上使用PaddlePaddle模型,可以参考笔者的这篇文章《基于Paddle Lite在Android手机上实现图像分类》。

本教程开源代码地址:https://github.com/yeyupiaoling/ChangeHumanBackground

图像语义分割工具

首先编写一个可以在Android应用使用PaddlePaddle的图像语义分割模型的工具类,通过是这个PaddleLiteSegmentation这个java工具类实现模型的加载和图像的预测。

首先是加载模型,获得一个预测器,其中inputShape为图像的输入大小,NUM_THREADS为使用线程数来预测图像,最高可以支持4个线程预测。

    private PaddlePredictor paddlePredictor;private Tensor inputTensor;public static long[] inputShape = new long[]{1, 3, 513, 513};private static final int NUM_THREADS = 4;/*** @param modelPath model path*/public PaddleLiteSegmentation(String modelPath) throws Exception {File file = new File(modelPath);if (!file.exists()) {throw new Exception("model file is not exists!");}try {MobileConfig config = new MobileConfig();config.setModelFromFile(modelPath);config.setThreads(NUM_THREADS);config.setPowerMode(PowerMode.LITE_POWER_HIGH);paddlePredictor = PaddlePredictor.createPaddlePredictor(config);inputTensor = paddlePredictor.getInput(0);inputTensor.resize(inputShape);} catch (Exception e) {e.printStackTrace();throw new Exception("load model fail!");}}

在预测开始之前,写两个重构方法,这个我们这个工具不管是图片路径还是图像的Bitmap都可以实现语义分割了。

    public long[] predictImage(String image_path) throws Exception {if (!new File(image_path).exists()) {throw new Exception("image file is not exists!");}FileInputStream fis = new FileInputStream(image_path);Bitmap bitmap = BitmapFactory.decodeStream(fis);long[] result = predictImage(bitmap);if (bitmap.isRecycled()) {bitmap.recycle();}return result;}public long[] predictImage(Bitmap bitmap) throws Exception {return predict(bitmap);}

现在还不能预测,还需要对图像进行预处理的方法,预测器输入的是一个浮点数组,而不是一个Bitmap对象,所以需要这样的一个工具方法,把图像Bitmap转换为浮点数组,同时对图像进行预处理,如通道顺序的变换,有的模型还需要数据的标准化,但这里没有使用到。

    private float[] getScaledMatrix(Bitmap bitmap) {int channels = (int) inputShape[1];int width = (int) inputShape[2];int height = (int) inputShape[3];float[] inputData = new float[channels * width * height];Bitmap rgbaImage = bitmap.copy(Bitmap.Config.ARGB_8888, true);Bitmap scaleImage = Bitmap.createScaledBitmap(rgbaImage, width, height, true);Log.d(TAG, scaleImage.getWidth() +  ", " + scaleImage.getHeight());if (channels == 3) {// RGB = {0, 1, 2}, BGR = {2, 1, 0}int[] channelIdx = new int[]{0, 1, 2};int[] channelStride = new int[]{width * height, width * height * 2};for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {int color = scaleImage.getPixel(x, y);float[] rgb = new float[]{(float) red(color), (float) green(color), (float) blue(color)};inputData[y * width + x] = rgb[channelIdx[0]];inputData[y * width + x + channelStride[0]] = rgb[channelIdx[1]];inputData[y * width + x + channelStride[1]] = rgb[channelIdx[2]];}}} else if (channels == 1) {for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {int color = scaleImage.getPixel(x, y);float gray = (float) (red(color) + green(color) + blue(color));inputData[y * width + x] = gray;}}} else {Log.e(TAG, "图片的通道数必须是1或者3");}return inputData;}

最后就可以执行预测了,预测的结果是一个数组,它代表了整个图像的语义分割的情况,0的为背景,1的为人物。

    private long[] predict(Bitmap bmp) throws Exception {float[] inputData = getScaledMatrix(bmp);inputTensor.setData(inputData);try {paddlePredictor.run();} catch (Exception e) {throw new Exception("predict image fail! log:" + e);}Tensor outputTensor = paddlePredictor.getOutput(0);long[] output = outputTensor.getLongData();long[] outputShape = outputTensor.shape();Log.d(TAG, "结果shape:"+ Arrays.toString(outputShape));return output;}

实现人物背景更换

MainActivity中,程序加载的时候就从assets中把模型复制到缓存目录中,然后加载图像语义分割模型。

String segmentationModelPath = getCacheDir().getAbsolutePath() + File.separator + "model.nb";
Utils.copyFileFromAsset(MainActivity.this, "model.nb", segmentationModelPath);
try {paddleLiteSegmentation = new PaddleLiteSegmentation(segmentationModelPath);Toast.makeText(MainActivity.this, "模型加载成功!", Toast.LENGTH_SHORT).show();Log.d(TAG, "模型加载成功!");
} catch (Exception e) {Toast.makeText(MainActivity.this, "模型加载失败!", Toast.LENGTH_SHORT).show();Log.d(TAG, "模型加载失败!");e.printStackTrace();finish();
}

创建几个按钮,来控制图片背景的更换。

// 获取控件
Button selectPicture = findViewById(R.id.select_picture);
Button selectBackground = findViewById(R.id.select_background);
Button savePicture = findViewById(R.id.save_picture);
imageView = findViewById(R.id.imageView);
selectPicture.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 打开相册Intent intent = new Intent(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(intent, 0);}
});
selectBackground.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (resultPicture != null){// 打开相册Intent intent = new Intent(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(intent, 1);}else {Toast.makeText(MainActivity.this, "先选择人物图片!", Toast.LENGTH_SHORT).show();}}
});
savePicture.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 保持图片String savePth = Utils.saveBitmap(mergeBitmap1);if (savePth != null) {Toast.makeText(MainActivity.this, "图片保存:" + savePth, Toast.LENGTH_SHORT).show();Log.d(TAG, "图片保存:" + savePth);} else {Toast.makeText(MainActivity.this, "图片保存失败", Toast.LENGTH_SHORT).show();Log.d(TAG, "图片保存失败");}}
});

首先需要选择包含人物的图片,这时就需要对图像进行预测,获取语义分割结果,然后将图像放大的跟原图像一样大小,并做这个临时的画布。

Uri image_uri = data.getData();
image_path = Utils.getPathFromURI(MainActivity.this, image_uri);
try {// 预测图像FileInputStream fis = new FileInputStream(image_path);Bitmap b = BitmapFactory.decodeStream(fis);long start = System.currentTimeMillis();long[] result = paddleLiteSegmentation.predictImage(image_path);long end = System.currentTimeMillis();// 创建一个任务为全黑色,背景完全透明的图片humanPicture = b.copy(Bitmap.Config.ARGB_8888, true);final int[] colors_map = {0x00000000, 0xFF000000};int[] objectColor = new int[result.length];for (int i = 0; i < result.length; i++) {objectColor[i] = colors_map[(int) result[i]];}Bitmap.Config config = humanPicture.getConfig();Bitmap outputImage = Bitmap.createBitmap(objectColor, (int) PaddleLiteSegmentation.inputShape[2], (int) PaddleLiteSegmentation.inputShape[3], config);resultPicture = Bitmap.createScaledBitmap(outputImage, humanPicture.getWidth(), humanPicture.getHeight(), true);imageView.setImageBitmap(b);Log.d(TAG, "预测时间:" + (end - start) + "ms");
} catch (Exception e) {e.printStackTrace();
}

最后在这里实现人物背景的更换,

Uri image_uri = data.getData();
image_path = Utils.getPathFromURI(MainActivity.this, image_uri);
try {FileInputStream fis = new FileInputStream(image_path);changeBackgroundPicture = BitmapFactory.decodeStream(fis);mergeBitmap1 = draw();imageView.setImageBitmap(mergeBitmap1);
} catch (Exception e) {e.printStackTrace();
}// 实现换背景
public Bitmap draw() {// 创建一个对应人物位置透明其他正常的背景图Bitmap bgBitmap = Bitmap.createScaledBitmap(changeBackgroundPicture, resultPicture.getWidth(), resultPicture.getHeight(), true);for (int y = 0; y < resultPicture.getHeight(); y++) {for (int x = 0; x < resultPicture.getWidth(); x++) {int color = resultPicture.getPixel(x, y);int a = Color.alpha(color);if (a == 255) {bgBitmap.setPixel(x, y, Color.TRANSPARENT);}}}// 添加画布,保证透明Bitmap bgBitmap2 = Bitmap.createBitmap(bgBitmap.getWidth(), bgBitmap.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas1 = new Canvas(bgBitmap2);canvas1.drawBitmap(bgBitmap, 0, 0, null);return mergeBitmap(humanPicture, bgBitmap2);
}// 合并两张图片
public static Bitmap mergeBitmap(Bitmap backBitmap, Bitmap frontBitmap) {Bitmap bitmap = backBitmap.copy(Bitmap.Config.ARGB_8888, true);Canvas canvas = new Canvas(bitmap);Rect baseRect = new Rect(0, 0, backBitmap.getWidth(), backBitmap.getHeight());Rect frontRect = new Rect(0, 0, frontBitmap.getWidth(), frontBitmap.getHeight());canvas.drawBitmap(frontBitmap, frontRect, baseRect, null);return bitmap;
}

实现的效果如下:

Android基于图像语义分割实现人物背景更换相关推荐

  1. 基于深度学习的图像语义分割技术概述之背景与深度网络架构

    本文为论文阅读笔记,不当之处,敬请指正.  A Review on Deep Learning Techniques Applied to Semantic Segmentation: 原文链接 摘要 ...

  2. 笔记:基于DCNN的图像语义分割综述

    写在前面:一篇魏云超博士的综述论文,完整题目为<基于DCNN的图像语义分割综述>,在这里选择性摘抄和理解,以加深自己印象,同时达到对近年来图像语义分割历史学习和了解的目的,博古才能通今!感 ...

  3. 毕业设计-基于卷积神经网络的遥感图像语义分割方法

    目录 前言 课题背景和意义 实现技术思路 一.相关技术理论 二.基于残差融合和多尺度上下文信息的遥感图像语义分割方法 三.基于注意力机制和边缘检测的遥感图像语义分割方法 实现效果图样例 最后 前言

  4. 【Keras】基于SegNet和U-Net的遥感图像语义分割

    from:[Keras]基于SegNet和U-Net的遥感图像语义分割 上两个月参加了个比赛,做的是对遥感高清图像做语义分割,美其名曰"天空之眼".这两周数据挖掘课期末projec ...

  5. 深度学习(二十一)基于FCN的图像语义分割-CVPR 2015-未完待续

    CNN应用之基于FCN的图像语义分割 原文地址:http://blog.csdn.net/hjimce/article/details/50268555 作者:hjimce 一.相关理论     本篇 ...

  6. Keras】基于SegNet和U-Net的遥感图像语义分割

    from:[Keras]基于SegNet和U-Net的遥感图像语义分割 上两个月参加了个比赛,做的是对遥感高清图像做语义分割,美其名曰"天空之眼".这两周数据挖掘课期末projec ...

  7. 基于深度学习的青菜病害区域图像语义分割与定位

    基于深度学习的青菜病害区域图像语义分割与定位 1.研究思路 提出了一种基于深度学习的青菜灾害区域图像语义分割的方法,通过 fine-tune FCN 以像素级精度分割出图像中作物灾害区进行识别,并借助 ...

  8. 学习笔记-基于全局和局部对比自监督学习的高分辨率遥感图像语义分割-day1

    基于全局和局部对比自监督学习的高分辨率遥感图像语义分割-day1 摘要 一. 引言 摘要 最近,监督深度学习在遥感图像(RSI)语义分割中取得了巨大成功. 然而,监督学习进行语义分割需要大量的标记样本 ...

  9. Pytorch:图像语义分割-基于VGG19的FCN8s实现

    Pytorch: 图像语义分割-基于VGG19的FCN8s语义分割网络实现 Copyright: Jingmin Wei, Pattern Recognition and Intelligent Sy ...

最新文章

  1. 《Unix网络编程卷1-套接字联网API》第一个例子编译 不通过问题解决
  2. VC调试篇:减少运行时错误,中断所有异常
  3. mongoDB - 日常操作四
  4. 有用的mysql语句
  5. 每日程序C语言8-打印“水仙花数”
  6. 少儿编程线下培训水到渠成了吗?2018
  7. 使用MySQL UDFs来调用gearman分布式任务分发系统
  8. 使用pip将Python软件包从本地文件系统文件夹安装到virtualenv
  9. 树莓派Linux内核编译选项如何开启TPM 2.0
  10. django 设置媒体url_django-文件上传Media url的配置
  11. 解决Pycharm挂代理后依旧插件下载慢
  12. 学习OpenCV2——Mat之通道的理解
  13. 不开方求两点距离的算法
  14. ie浏览器flash player不能用的解决方案
  15. 利用canvas开发一个绘图板
  16. 简单BroadcastRecevier
  17. 什么是Vue CLI(脚手架)?
  18. 一个Android下ping的简单工具类
  19. 第十三届蓝桥杯大赛软件类决赛Java大学B组C题——左移右移
  20. Windows下mklink使用

热门文章

  1. 《跟我学shiro》系列教程
  2. vmware虚拟机和windows PC机构建无线局域网
  3. python的rename报错_python rename报错怎么解决
  4. h5app开发移动端屏幕禁止滑动
  5. ctfshow菜狗 web 一言既出
  6. HTTP响应报文体和长连接详解
  7. 开机后,桌面上没图标,开始菜单,只有壁纸
  8. 康威定律——微服务的理论基础
  9. linux怎么使用ksh执行脚本,执行shell脚本遇到 ksh not found的问题
  10. 【GitHubDailyShare】主打小而美的功能,GitHub 上一款开源的 3D 建模