原文博客: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. Java 收集的代码 transient
  2. STM32 进阶教程 14 - 程序加密之FLASH读写保护
  3. 五问弄懂液冷数据中心
  4. python的tkinter编写计算器_Python+Tkinter 实现计算器功能
  5. 请你说明一下ConcurrentHashMap的原理?
  6. yum 卸载_不小心把Centos的yum给卸载了怎么办
  7. CSocket类的使用
  8. 老男孩爬虫实战密训课第一季,2018.6,初识爬虫训练-实战1-爬取汽车之家新闻数据...
  9. 前端学习(2237):react实现疫情数据
  10. LeetCode 1817. 查找用户活跃分钟数(哈希)
  11. C#写Windows系统日志(EventLog)
  12. IDEA 这个小技巧太实用了。。
  13. 根据二磁道数据识别是IC卡还是磁条卡
  14. Mac 下erlang及rabbitmq安装
  15. 切换窗口卡顿?禁用Lenovo System Interface Foundation
  16. vue-cli生成的模板各个文件详解(转)
  17. 【系统分析师之路】系统分析师必知必会(需求分析篇)
  18. 使用 hugegraph-studio 插入电影数据并查询
  19. c语言-基础知识点复习
  20. 1949-2020年各省全要素生产率(年度)

热门文章

  1. 好看响应式Emlog博客主题模板
  2. torch.arange()和torch.range()的区别
  3. 为什么说新一代流处理器Flink是第三代流处理器(论点:发展历史、区别、适用场景)
  4. SAP CRM的市场营销(Marketing)管理简介(VI)
  5. 计算机专业的口号运动会四字,常用运动会四字口号
  6. JAVA 中级面试题 (附答案)
  7. 最主要的需求还是 房间环境
  8. git 本地新建分支
  9. 基于SpringBoot的医院门诊管理系统,源码,数据库脚本,项目导入运行视频教程,论文撰写教程
  10. 左转还是右转?测试你用的是是左脑还是右脑