package com.example.skin;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import org.tensorflow.lite.DataType;
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.support.common.ops.NormalizeOp;
import org.tensorflow.lite.support.image.ImageProcessor;
import org.tensorflow.lite.support.image.TensorImage;
import org.tensorflow.lite.support.image.ops.ResizeOp;
import org.tensorflow.lite.support.image.ops.ResizeWithCropOrPadOp;
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
public class SkinClassifier {
private static final String TAG = "SkinClassifier";
private static final int INPUT_SIZE = 224; // 模型输入尺寸
private static final int NUM_CHANNELS = 3; // RGB通道
private static final int NUM_CLASSES = 3; // 分类类别数
private final Interpreter tflite;
private final ImageProcessor imageProcessor;
public SkinClassifier(Context context) throws IOException {
// 1. 从ml文件夹加载模型文件
ByteBuffer modelBuffer = loadModelFile(context, "skin_classifier.tflite");
// 2. 初始化Interpreter
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(4); // 使用4个线程加速推理
tflite = new Interpreter(modelBuffer, options);
// 3. 初始化图像处理器
imageProcessor = new ImageProcessor.Builder()
.add(new ResizeWithCropOrPadOp(INPUT_SIZE, INPUT_SIZE))
.add(new ResizeOp(INPUT_SIZE, INPUT_SIZE, ResizeOp.ResizeMethod.BILINEAR))
.add(new NormalizeOp(0f, 255f)) // 归一化处理
.build();
// 打印输入输出张量信息
Log.i(TAG, "输入张量: " + tflite.getInputTensor(0).shape()[1] + "x"
+ tflite.getInputTensor(0).shape()[2] + "x"
+ tflite.getInputTensor(0).shape()[3]);
Log.i(TAG, "输出张量: " + tflite.getOutputTensor(0).shape()[1]);
}
// 从ml文件夹加载模型
private ByteBuffer loadModelFile(Context context, String modelName) throws IOException {
String resourceName = modelName.replace(".tflite", "").toLowerCase();
int resId = context.getResources().getIdentifier(
resourceName, "raw", context.getPackageName());
if (resId == 0) {
throw new IOException("模型资源未找到: " + resourceName);
}
try (InputStream inputStream = context.getResources().openRawResource(resId)) {
byte[] modelBytes = new byte[inputStream.available()];
inputStream.read(modelBytes);
// 修复方案:直接创建只读缓冲区
return (ByteBuffer) ByteBuffer.allocateDirect(modelBytes.length)
.order(ByteOrder.nativeOrder())
.put(modelBytes)
.asReadOnlyBuffer() // 在ByteBuffer上调用
.rewind(); // 重置位置指针
}
}
// 执行分类
public List<Category> classify(Bitmap bitmap) {
if (bitmap == null) {
Log.w(TAG, "输入Bitmap为空");
return new ArrayList<>();
}
try {
// 使用TensorBuffer直接处理
TensorImage tensorImage = new TensorImage(DataType.FLOAT32);
tensorImage.load(bitmap);
TensorImage processedImage = imageProcessor.process(tensorImage);
// 自动处理输入输出
TensorBuffer inputBuffer = processedImage.getTensorBuffer();
TensorBuffer outputBuffer = TensorBuffer.createFixedSize(
new int[]{1, NUM_CLASSES}, DataType.FLOAT32);
// 执行推理
tflite.run(inputBuffer.getBuffer(), outputBuffer.getBuffer());
// 解析结果
return parseOutput(outputBuffer.getFloatArray());
} catch (Exception e) {
Log.e(TAG, "分类错误", e);
return new ArrayList<>();
}
}
public void close() {
if (tflite != null) {
tflite.close();
Log.i(TAG, "模型资源已释放");
}
}
// 解析模型输出
private List<Category> parseOutput(float[] probabilities) {
List<Category> results = new ArrayList<>();
// 假设类别顺序: [0]油性, [1]干性, [2]中性
String[] labels = {"Dry", "Normal", "Oily"};
for (int i = 0; i < probabilities.length; i++) {
results.add(new Category(labels[i], probabilities[i]));
}
// 按置信度排序
results.sort((c1, c2) -> Float.compare(c2.confidence, c1.confidence));
return results;
}
// 自定义分类结果类
public static class Category {
public final String label;
public final float confidence;
public Category(String label, float confidence) {
this.label = label;
this.confidence = confidence;
}
}
}上述为SkinClassifier的代码package com.example.skin;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private static final int CAMERA_PERMISSION_CODE = 100;
private static final int CAMERA_REQUEST_CODE = 101;
private static final int PICK_IMAGE_REQUEST = 102;
private String currentPhotoPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 设置点击监听器
findViewById(R.id.takePhotoBtn).setOnClickListener(v -> checkCameraPermission());
findViewById(R.id.selectPhotoBtn).setOnClickListener(v -> {
// 这里可以添加从相册选择照片的功能
Toast.makeText(this, "相册功能待实现", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, PICK_IMAGE_REQUEST);
});
}
private void checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
CAMERA_PERMISSION_CODE);
} else {
openCamera();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
Toast.makeText(this, "需要摄像头权限才能拍照", Toast.LENGTH_SHORT).show();
}
}
}
private void openCamera() {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (cameraIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = createImageFile();
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.skin.fileprovider",
photoFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE);
}
}
}
private File createImageFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "SKIN_ANALYSIS_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = null;
try {
image = File.createTempFile(
imageFileName,
".jpg",
storageDir
);
currentPhotoPath = image.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
if (requestCode == CAMERA_REQUEST_CODE) {
// 相机拍照处理
Intent analysisIntent = new Intent(this, AnalysisActivity.class);
analysisIntent.putExtra("image_path", currentPhotoPath);
startActivity(analysisIntent);
} else if (requestCode == PICK_IMAGE_REQUEST && data != null) {
// 相册选择处理
Uri selectedImage = data.getData();
String[] filePathColumn = { MediaStore.Images.Media.DATA };
Cursor cursor = getContentResolver().query(
selectedImage, filePathColumn, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String imagePath = cursor.getString(columnIndex);
cursor.close();
Intent analysisIntent = new Intent(this, AnalysisActivity.class);
analysisIntent.putExtra("image_path", imagePath);
startActivity(analysisIntent);
//跳转到皮肤分析界面后可点击分享按钮,实现分享功能
}
}
}
}上述为mainactivity的代码package com.example.skin;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.example.skin.SkinClassifier;
import java.io.File;
import java.util.List;
public class AnalysisActivity extends AppCompatActivity {
private ImageView previewImage;
private TextView resultText;
private Button retryButton;
private SkinClassifier classifier;
private String imagePath;
private Button homeButton;
private Button shareButton;
private String skinTypeResult = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_analysis);
// 初始化UI
previewImage = findViewById(R.id.previewImage);
resultText = findViewById(R.id.resultText);
retryButton = findViewById(R.id.retryButton);
// 获取传递的图片路径
imagePath = getIntent().getStringExtra("image_path");
// 显示图片
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
previewImage.setImageBitmap(bitmap);
// 初始化分类器
initClassifier();
// 重拍按钮事件
retryButton.setOnClickListener(v -> finish());
// 绑定新按钮
homeButton = findViewById(R.id.homeButton);
shareButton = findViewById(R.id.shareButton);
// 设置监听器
homeButton.setOnClickListener(v -> returnToMain());
shareButton.setOnClickListener(v -> shareResults());
}
private void initClassifier() {
new Thread(() -> {
try {
classifier = new SkinClassifier(this);
runOnUiThread(this::performAnalysis);
} catch (Exception e) {
runOnUiThread(() -> {
resultText.setText("模型加载失败");
resultText.setTextColor(Color.RED);
});
}
}).start();
}
// 返回主界面功能
private void returnToMain() {
Intent mainIntent = new Intent(this, MainActivity.class);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清除Activity栈
startActivity(mainIntent);
finish(); // 结束当前Activity
}
// 分享功能实现
private void shareResults() {
// 1. 获取分享内容
TextView resultView = findViewById(R.id.resultText);
TextView adviceView = findViewById(R.id.adviceText);
String resultText = skinTypeResult + "\n\n" +
resultView.getText().toString() + "\n\n" +
adviceView.getText().toString();
// 2. 创建分享图片的URI
Uri imageUri = FileProvider.getUriForFile(
this,
"com.example.skin.fileprovider",
new File(imagePath)
);
// 3. 创建分享Intent
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
shareIntent.putExtra(Intent.EXTRA_TEXT, resultText);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 4. 启动分享选择器
startActivity(Intent.createChooser(
shareIntent,
"分享皮肤分析结果"
));
}
@SuppressLint("SetTextI18n")
private void performAnalysis() {
new Thread(() -> {
try {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
if (bitmap == null) return;
// 执行预测
long start = System.currentTimeMillis();
List<SkinClassifier.Category> results = classifier.classify(bitmap);
long duration = System.currentTimeMillis() - start;
runOnUiThread(() -> {
if (results != null && !results.isEmpty()) {
SkinClassifier.Category top = results.get(0);
String skinType = getSkinTypeDisplayName(top.label);
@SuppressLint("DefaultLocale") String resultMsg = String.format(
"✅ 预测结果: %s\n\n" +
"🔬 置信度: %.1f%%\n" +
"⏱️ 耗时: %dms",
skinType, top.confidence * 100, duration
);
resultText.setText(resultMsg);
resultText.setTextColor(getConfidenceColor(top.confidence));
// 添加个性化建议
addSkinCareAdvice(skinType);
// 记录分享用的文本
skinTypeResult = "我的皮肤类型分析结果:" + skinType;
// 启用分享按钮
shareButton.setEnabled(true);
} else {
resultText.setText("⚠️ 无法识别皮肤类型");
resultText.setTextColor(Color.YELLOW);
// 禁用分享按钮
shareButton.setEnabled(false);
}
});
} catch (Exception e) {
runOnUiThread(() -> {
resultText.setText("❌ 分析失败: " + e.getMessage());
resultText.setTextColor(Color.RED);
});
}
}).start();
}
private String getSkinTypeDisplayName(String type) {
switch (type.toLowerCase()) {
case "oily": return "油性肌肤";
case "dry": return "干性肌肤";
case "normal": return "中性肌肤";
default: return type;
}
}
private int getConfidenceColor(float confidence) {
if (confidence > 0.8f) return Color.parseColor("#4CAF50"); // 绿色
if (confidence > 0.6f) return Color.parseColor("#FFC107"); // 黄色
return Color.parseColor("#F44336"); // 红色
}
private void addSkinCareAdvice(String skinType) {
TextView adviceText = findViewById(R.id.adviceText);
switch (skinType) {
case "油性肌肤":
adviceText.setText("✨ 护理建议:\n- 使用控油洁面产品\n- 每周1-2次深层清洁\n- 选择轻薄无油配方护肤品");
break;
case "干性肌肤":
adviceText.setText("✨ 护理建议:\n- 使用滋润型洁面乳\n- 早晚使用高效保湿霜\n- 每周2-3次保湿面膜");
break;
default:
adviceText.setText("✨ 护理建议:\n- 保持日常清洁保湿\n- 注意防晒\n- 定期皮肤检查");
}
adviceText.setTextColor(Color.parseColor("#2196F3")); // 蓝色
}
}上述为analysisactivity的代码<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures/" />
<external-path name="external_files" path="."/>
<external-files-path name="images" path="Pictures" />
</paths>上述为file_paths的代码仔细分析所给代码,用Android Studio运行该APP时拍照后出现跳转皮肤分析界面,显示tflite模型加载失败,分析问题,并给出解决办法,tflite模型是在Python环境下tflite-support为0.1.0a1版本下训练的,但在Android中打开模型时没有元数据显示