用android开发一个识别人形的app,一键切换背景
前言:我是小松,今年大四,在android开发中持续耕耘,快来一起学习把
不知道大家有没有这种烦恼,手上有白底的证件照,但是学校偏偏要交红底的,万般无奈只能去照相馆再照,虽说可以进行PS,但是总归麻烦,现在可以用app一键解决啦
目录
- 效果如图
- 项目介绍
- 架构
- 布局
- activity_main
- activity_still_cut
- 功能
- MainActivity
- Constant
- BitmapUtils
- ImageUtils
- 核心类:StillCutActivity
- onCreate方法
- InitActivon方法
- selectLocalImage方法
- createImageTransactor方法
- changeBackground
- 写在最后
效果如图
项目介绍
架构
java代码有以下类
layout布局仅有两个
由于项目比较复杂,接下来的将不再介绍基础代码,只介绍核心逻辑
布局
布局代码将不再介绍与功能无关的代码
activity_main
<Buttonandroid:id="@+id/start_button"android:layout_width="310dp"android:layout_height="100dp"android:onClick="startImageSegmentation"android:layout_marginTop="30dp"android:text="@string/start"android:textSize="24sp"/>
这里的开始按钮设置了一个监听器startImageSegmentation
函数
昨天升级了android3.6,有很多新功能,现在已经可以在同一个界面进行拖拽和代码编辑了,美滋滋哈哈哈
activity_still_cut
功能
MainActivity
MainActivity
主要是进行一些权限的设定和跳转,权限比如存储,选择照片等等
public void startImageSegmentation(View v) {Intent intent = new Intent(MainActivity.this, StillCutPhotoActivity.class);startActivity(intent);}
Constant
这是一个定义修改颜色的模型,因为我们设置了四种换底颜色,如img_001是白色
public class Constant {public static int[] IMAGES = {R.mipmap.img_001, R.mipmap.img_002,R.mipmap.img_003,R.mipmap.img_004};public static final String VALUE_KEY = "index_value";
}
BitmapUtils
这是一个处理选出来的图片的工具类,在网上获取的代码,将选择的图片进行裁切等操作放在activity_still_cut
的imageView
中
public class BitmapUtils {private static final String TAG = "BitmapUtils";public static void recycleBitmap(Bitmap... bitmaps) {for (Bitmap bitmap : bitmaps) {if (bitmap != null && !bitmap.isRecycled()) {bitmap.recycle();bitmap = null;}}}private static String getImagePath(Activity activity, Uri uri) {String[] projection = {MediaStore.Images.Media.DATA};Cursor cursor = activity.managedQuery(uri, projection, null, null, null);int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);cursor.moveToFirst();return cursor.getString(columnIndex);}public static Bitmap loadFromPath(Activity activity, int id, int width, int height) {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;InputStream is = activity.getResources().openRawResource(id);int sampleSize = calculateInSampleSize(options, width, height);options.inSampleSize = sampleSize;options.inJustDecodeBounds = false;return zoomImage(BitmapFactory.decodeStream(is), width, height);}public static Bitmap loadFromPath(Activity activity, Uri uri, int width, int height) {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;String path = getImagePath(activity, uri);BitmapFactory.decodeFile(path, options);int sampleSize = calculateInSampleSize(options, width, height);options.inSampleSize = sampleSize;options.inJustDecodeBounds = false;Bitmap bitmap = zoomImage(BitmapFactory.decodeFile(path, options), width, height);return rotateBitmap(bitmap, getRotationAngle(path));}private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {final int width = options.outWidth;final int height = options.outHeight;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {// Calculate height and required height scalefinal int heightRatio = Math.round((float) height / (float) reqHeight);// Calculate width and required width scalefinal int widthRatio = Math.round((float) width / (float) reqWidth);// Take the larger of the valuesinSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;}return inSampleSize;}// Scale pictures to screen widthprivate static Bitmap zoomImage(Bitmap imageBitmap, int targetWidth, int maxHeight) {float scaleFactor =Math.max((float) imageBitmap.getWidth() / (float) targetWidth,(float) imageBitmap.getHeight() / (float) maxHeight);Bitmap resizedBitmap =Bitmap.createScaledBitmap(imageBitmap,(int) (imageBitmap.getWidth() / scaleFactor),(int) (imageBitmap.getHeight() / scaleFactor),true);return resizedBitmap;}/*** Get the rotation angle of the photo** @param path photo path* @return angle*/public static int getRotationAngle(String path) {int rotation = 0;try {ExifInterface exifInterface = new ExifInterface(path);int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:rotation = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:rotation = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:rotation = 270;break;}} catch (IOException e) {SmartLog.e(TAG, "Failed to get rotation: " + e.getMessage());}return rotation;}public static Bitmap rotateBitmap(Bitmap bitmap, int angle) {Matrix matrix = new Matrix();matrix.postRotate(angle);Bitmap result = null;try {result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);} catch (OutOfMemoryError e) {SmartLog.e(TAG, "Failed to rotate bitmap: " + e.getMessage());}if (result == null) {return bitmap;}return result;}}
ImageUtils
这是一个保存图片到本地的工具类,将换好底的图片保存到手机中,也是在网上获取的代码,
package com.example.changebackground.util;import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;import com.huawei.hms.mlsdk.common.internal.client.SmartLog;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class ImageUtils {private static final String TAG = "ImageUtils";private Context context;public ImageUtils(Context context){this.context = context;}// Save the picture to the system album and refresh it.public void saveToAlbum(Bitmap bitmap){File file = null;String fileName = System.currentTimeMillis() +".jpg";File root = new File(Environment.getExternalStorageDirectory().getAbsoluteFile(), this.context.getPackageName());File dir = new File(root, "image");if(dir.mkdirs() || dir.isDirectory()){file = new File(dir, fileName);}FileOutputStream os = null;try {os = new FileOutputStream(file);bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);os.flush();} catch (FileNotFoundException e) {SmartLog.e(TAG, e.getMessage());} catch (IOException e) {SmartLog.e(TAG, e.getMessage());}finally {try {if(os != null) {os.close();}}catch (IOException e){SmartLog.e(TAG, e.getMessage());}}if(file == null){return;}// Gallery refresh.if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {String path = null;try {path = file.getCanonicalPath();} catch (IOException e) {SmartLog.e(TAG, e.getMessage());}MediaScannerConnection.scanFile(this.context, new String[]{path}, null,new MediaScannerConnection.OnScanCompletedListener() {@Overridepublic void onScanCompleted(String path, Uri uri) {Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);mediaScanIntent.setData(uri);ImageUtils.this.context.sendBroadcast(mediaScanIntent);}});} else {String relationDir = file.getParent();File file1 = new File(relationDir);this.context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(file1.getAbsoluteFile())));}}
}
核心类:StillCutActivity
接下来到了我们今天的核心步骤,如何识别出图片中的人脸人体,并进行背景替换呢?
是使用了华为的SDK,你需要先注册华为的账号,然后
将project处的build.gradle
修改
allprojects {repositories {jcenter()google()maven {url 'http://developer.huawei.com/repo/'}}
}
在app处的build.gradle
添加依赖
implementation 'com.huawei.hms:ml-computer-vision-segmentation:1.0.3.300'implementation 'com.huawei.hms:ml-computer-vision-image-segmentation-body-model:1.0.3.300'
然后在图像分割进行相关SDK接口的调用学习
首先创建视图
onCreate方法
@Overridepublic void onCreate(Bundle savedInstance) {super.onCreate(savedInstance);activityStillCutBinding = ActivityStillCutBinding.inflate(LayoutInflater.from(this));setContentView(activityStillCutBinding.getRoot());preview = activityStillCutBinding.previewPane;activityStillCutBinding.back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {finish();}});isLandScape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);initAction(activityStillCutBinding);}
这里的activityStillCutBinding是在新的android 3.6中推出的视图绑定功能,只需要在app处的build.gradle
中添加
android{viewBinding {enabled = true}
}
即可开启视图绑定效果,activity_still_cut
和activity_main
将分别生成ActivityStillCutBing
和ActivityMainBinding
类,在Activity中可以直接调用,他们里面的控件,只要有id就可以以.id的形式调用,加载根布局的方法是activityStillCutBinding.getRoot()
InitActivon方法
private void initAction(ActivityStillCutBinding activityStillCutBinding) {activityStillCutBinding.relativeChooseImg.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {selectLocalImage( REQUEST_CHOOSE_ORIGINPIC);}});// Outline the edge.activityStillCutBinding.relativateCut.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (imageUri == null) {Toast.makeText( getApplicationContext(), R.string.please_select_picture, Toast.LENGTH_SHORT).show();} else {createImageTransactor();Toast.makeText( getApplicationContext(), R.string.cut_success, Toast.LENGTH_SHORT).show();}}});// Save the processed picture.activityStillCutBinding.relativateSave.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if ( processedImage == null) {Toast.makeText( getApplicationContext(), R.string.no_pic_neededSave, Toast.LENGTH_SHORT).show();try {throw new Exception("null processed image");} catch (Exception e) {SmartLog.e(StillCutPhotoActivity.TAG, e.getMessage());}} else {ImageUtils imageUtils = new ImageUtils( getApplicationContext());imageUtils.saveToAlbum( processedImage);Toast.makeText( getApplicationContext(), R.string.save_success, Toast.LENGTH_SHORT).show();}}});}
这里是为activity_still_cut
底部的三个按钮设置监听器,他们的id分别是relative_chooseImg
,relativate_cut
,relativate_save
,转成属性后,自动去掉下划线,下划线后第一个字母大写
selectLocalImage方法
加载图片
private void selectLocalImage(int requestCode) {Intent intent = new Intent(Intent.ACTION_PICK, null);intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");startActivityForResult(intent, requestCode);}
createImageTransactor方法
这个方法就是调用SDK中的API进行图像分割和底色替换,具体API含义可以在图像分割开发步骤中查询
private void createImageTransactor() {MLImageSegmentationSetting setting = new MLImageSegmentationSetting.Factory().setAnalyzerType(MLImageSegmentationSetting.BODY_SEG).setExact(true).create();analyzer = MLAnalyzerFactory.getInstance().getImageSegmentationAnalyzer(setting);if (isChosen(originBitmap)) {MLFrame mlFrame = new MLFrame.Creator().setBitmap(originBitmap).create();Task<MLImageSegmentation> task = analyzer.asyncAnalyseFrame(mlFrame);task.addOnSuccessListener(new OnSuccessListener<MLImageSegmentation>() {@Overridepublic void onSuccess(MLImageSegmentation mlImageSegmentationResults) {// 转换成功if (mlImageSegmentationResults != null) {foreground = mlImageSegmentationResults.getForeground();preview.setImageBitmap( foreground);processedImage = ((BitmapDrawable) ((ImageView) preview).getDrawable()).getBitmap();changeBackground();} else {displayFailure();}}}).addOnFailureListener(new OnFailureListener() {@Overridepublic void onFailure(Exception e) {// 转换失败displayFailure();return;}});} else {Toast.makeText(getApplicationContext(), R.string.please_select_picture, Toast.LENGTH_SHORT).show();return;}}
上面只是分割了图像,接下来进行换底操作,
changeBackground
private void changeBackground() {if (index< 0) {Toast.makeText(getApplicationContext(), R.string.please_select_picture, Toast.LENGTH_SHORT).show();} else {int id = Constant.IMAGES[index];loadOriginImage();Pair<Integer, Integer> targetedSize = getTargetSize();backgroundBitmap = BitmapUtils.loadFromPath(StillCutPhotoActivity.this, id, targetedSize.first, targetedSize.second);}if (isChosen(foreground) && isChosen(backgroundBitmap)) {BitmapDrawable drawable = new BitmapDrawable(backgroundBitmap);preview.setDrawingCacheEnabled(true);preview.setBackground(drawable);preview.setImageBitmap(foreground);processedImage = Bitmap.createBitmap( preview.getDrawingCache());preview.setDrawingCacheEnabled(false);} else {Toast.makeText(getApplicationContext(), R.string.please_select_picture, Toast.LENGTH_SHORT).show();return;}}
代码中的preview
就是显示图片的ImageView
,当判定好人物前景和图像背景后,只需要将图像背景修改为我们指定的颜色即可
最后是保存,保存使用的是ImageUtil
类,只需要在保存的监听器按钮中调用这个类的相关函数即可
写在最后
新的android 3.6其实还有一些小问题,但是总体来说,相对于3.5升级了很多有用的功能,快去体验一下吧
apk文件已经放在公众号【小松漫步】,回复【一键换底】,即可
用android开发一个识别人形的app,一键切换背景相关推荐
- 【发际线大作战】Android Studio使用Intent和ListView开发一个联系人列表的APP
要求 Intent和ListView的结合使用 目标:1. 掌握不同Activity之间跳转的方法,以及如何利用intent和bundle进行数据的传递:2. 掌握ListView控件使用的基本方法. ...
- 勇敢跨越,从0到1开发一个属于自己的App
1 前言 码字不易,且行且珍惜. 之前听一个老铁说,一个开发者的真正蜕变,要从真正做一个属于自己的App开始,你只有自己一个人摸索.研究,真正靠自己踏上这条路,才能尝尽这条路上的酸甜苦辣,才会成长!于 ...
- 开发一个属于自己的app
" 开发一个属于自己的app" 打开手机,可以看见手机上各种各样的应用,每一款软件都是别人开发的,想想就觉得不满足,怎么就没有我自己的app呢!想法一旦产生就止不住的往外流露.那怎 ...
- 一个html写的app首页,如何快速开发一个简单好看的APP控制页面
原标题:如何快速开发一个简单好看的APP控制页面 导读 机智云开源框架为了让开发者快速开发APP,已将用户登录,设备发现,设备配网等功能做成了各个标准模块,仅保留控制页面让开发者自行开发设计,节省了开 ...
- 基于Android开发的即时通讯聊天app
基于Android开发的即时通讯聊天app 前言 即时通讯(Instant Messaging,简称IM)在互联网中应用十分广泛,它可以和很多的领域结合,发挥十分重要的作用.比如金融行业的支付宝.各大 ...
- android开发 一个更优的listView的写法
android开发 一个更优的listView的写法 布局xml <?xml version="1.0" encoding="utf-8"?> &l ...
- Android开发高级进阶内涵段子APP项目实战视频教程
Android开发高级进阶内涵段子APP项目实战课程视频教程下载.本课程带你从框架入手,开启我们的Android进阶之旅,开始写一步一步完善整个项目. 项目目录: 01.Android进阶之旅与你同行 ...
- 开发一个微信小程序/APP一般需要多少时间,多少钱?
开发一个微信小程序/APP一般需要多少时间,多少钱? 微信小程序/APP开发的工期和费用估算需视功能需求的多少和难易程度而定,需求不明的情况下很难给出恰当评估. 在湃点网络定制平台,一对一的专业的顾问 ...
- App一键切换url环境、一键打包__Android拓展篇(Java)
文 | Promise Sun 一.背景: 1. 2022上班第一天,整理一下过去的工作,发现这方面的小知识点,去年忘记记录博客了,于是就有了这篇文章.分享给大家,希望对有需要的朋友有帮助. 2. 项 ...
最新文章
- 题目1207:质因数的个数
- SSH整合注解版(Spring+Struts2+Hibernate)
- Enterprise Services (COM+)服务组件开发异常分析
- linux 网络状态图,linux的网络连接状态
- 美联储FOMC会议前瞻:预计美联储将维持目前的政策不变
- Python面向对象之结构与成员
- 二叉树平衡因子应用举例
- 鸿蒙来了!能否与安卓、iOS三足鼎立?
- android命令行wifi开关,Android系统SVC命令(命令行WIFI开关、GPRS移动数据开关)
- python下视频的包_这套Python视频超详细,包你一小时就可开始入门,100天在编程界驰骋~...
- 成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘
- Linux 驱动 | SPI子系统
- Fragment Transactions Activity State Loss
- 有视频APP上线,一对一交友源码和抖音短视频源码稳步前行
- 专访北京航空航天大学黎健成:我和编程比赛
- Android 后台保活,这里有你需要的所有姿势。2019,flutterlistview滚动卡顿
- 挖掘高流量网站长尾词
- html scale属性,CSS中的zoom属性和scale属性的用法及区别
- spring 中定时器的 task:executor执行器和调度器task:scheduler
- 使用u盘PE安装原版xp系统
热门文章
- 抢到了!2021第一个爆款Moritek液体“热玛吉”喷雾,效果绝了~
- 超火的github项目you-get编写GUI界面并打包为exe文件
- Android音视频开发(二)SurfaceView
- 经典的soo单点登录实现
- MySQL表的查询进阶
- 【软工视频】——制定计划和可行性研究报告
- 功能更强的手机-Symbian OS手机(转)
- python爬去网页数据并对比excel中数据是否一致_python入门之对比两份excel表格数据...
- php闪字生成,在线闪字
- Ubuntu 安装PostgreSQL