前言:我是小松,今年大四,在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_cutimageView


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_cutactivity_main将分别生成ActivityStillCutBingActivityMainBinding类,在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,一键切换背景相关推荐

  1. 【发际线大作战】Android Studio使用Intent和ListView开发一个联系人列表的APP

    要求 Intent和ListView的结合使用 目标:1. 掌握不同Activity之间跳转的方法,以及如何利用intent和bundle进行数据的传递:2. 掌握ListView控件使用的基本方法. ...

  2. 勇敢跨越,从0到1开发一个属于自己的App

    1 前言 码字不易,且行且珍惜. 之前听一个老铁说,一个开发者的真正蜕变,要从真正做一个属于自己的App开始,你只有自己一个人摸索.研究,真正靠自己踏上这条路,才能尝尽这条路上的酸甜苦辣,才会成长!于 ...

  3. 开发一个属于自己的app

    " 开发一个属于自己的app" 打开手机,可以看见手机上各种各样的应用,每一款软件都是别人开发的,想想就觉得不满足,怎么就没有我自己的app呢!想法一旦产生就止不住的往外流露.那怎 ...

  4. 一个html写的app首页,如何快速开发一个简单好看的APP控制页面

    原标题:如何快速开发一个简单好看的APP控制页面 导读 机智云开源框架为了让开发者快速开发APP,已将用户登录,设备发现,设备配网等功能做成了各个标准模块,仅保留控制页面让开发者自行开发设计,节省了开 ...

  5. 基于Android开发的即时通讯聊天app

    基于Android开发的即时通讯聊天app 前言 即时通讯(Instant Messaging,简称IM)在互联网中应用十分广泛,它可以和很多的领域结合,发挥十分重要的作用.比如金融行业的支付宝.各大 ...

  6. android开发 一个更优的listView的写法

    android开发 一个更优的listView的写法 布局xml <?xml version="1.0" encoding="utf-8"?> &l ...

  7. Android开发高级进阶内涵段子APP项目实战视频教程

    Android开发高级进阶内涵段子APP项目实战课程视频教程下载.本课程带你从框架入手,开启我们的Android进阶之旅,开始写一步一步完善整个项目. 项目目录: 01.Android进阶之旅与你同行 ...

  8. 开发一个微信小程序/APP一般需要多少时间,多少钱?

    开发一个微信小程序/APP一般需要多少时间,多少钱? 微信小程序/APP开发的工期和费用估算需视功能需求的多少和难易程度而定,需求不明的情况下很难给出恰当评估. 在湃点网络定制平台,一对一的专业的顾问 ...

  9. App一键切换url环境、一键打包__Android拓展篇(Java)

    文 | Promise Sun 一.背景: 1. 2022上班第一天,整理一下过去的工作,发现这方面的小知识点,去年忘记记录博客了,于是就有了这篇文章.分享给大家,希望对有需要的朋友有帮助. 2. 项 ...

最新文章

  1. 题目1207:质因数的个数
  2. SSH整合注解版(Spring+Struts2+Hibernate)
  3. Enterprise Services (COM+)服务组件开发异常分析
  4. linux 网络状态图,linux的网络连接状态
  5. 美联储FOMC会议前瞻:预计美联储将维持目前的政策不变
  6. Python面向对象之结构与成员
  7. 二叉树平衡因子应用举例
  8. 鸿蒙来了!能否与安卓、iOS三足鼎立?
  9. android命令行wifi开关,Android系统SVC命令(命令行WIFI开关、GPRS移动数据开关)
  10. python下视频的包_这套Python视频超详细,包你一小时就可开始入门,100天在编程界驰骋~...
  11. 成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘
  12. Linux 驱动 | SPI子系统
  13. Fragment Transactions Activity State Loss
  14. 有视频APP上线,一对一交友源码和抖音短视频源码稳步前行
  15. 专访北京航空航天大学黎健成:我和编程比赛
  16. Android 后台保活,这里有你需要的所有姿势。2019,flutterlistview滚动卡顿
  17. 挖掘高流量网站长尾词
  18. html scale属性,CSS中的zoom属性和scale属性的用法及区别
  19. spring 中定时器的 task:executor执行器和调度器task:scheduler
  20. 使用u盘PE安装原版xp系统

热门文章

  1. 抢到了!2021第一个爆款Moritek液体“热玛吉”喷雾,效果绝了~
  2. 超火的github项目you-get编写GUI界面并打包为exe文件
  3. Android音视频开发(二)SurfaceView
  4. 经典的soo单点登录实现
  5. MySQL表的查询进阶
  6. 【软工视频】——制定计划和可行性研究报告
  7. 功能更强的手机-Symbian OS手机(转)
  8. python爬去网页数据并对比excel中数据是否一致_python入门之对比两份excel表格数据...
  9. php闪字生成,在线闪字
  10. Ubuntu 安装PostgreSQL