Android Studio中基于TFLite实现图像分类前的图片裁剪方案求助
带前置图片裁剪的TFLite图像分类Android应用实现方案
崩溃原因分析
从报错日志看,NullPointerException出现在classifyImage方法调用result.setText(classes[maxPos])时,说明result或confidence这两个TextView对象未被正确初始化。你修改后的代码混用了视图绑定(binding)和findViewById两种方式,但既没有用视图绑定初始化result和confidence,也没通过findViewById给这两个变量赋值,导致它们为null。
简单实现方案
下面提供两种轻量的图片裁剪集成方案,同时修复崩溃问题:
方案一:使用系统自带裁剪Intent
直接调用系统原生裁剪功能,无需引入第三方库,步骤如下:
统一视图初始化方式
继续使用原代码的findViewById方式,确保所有控件都被正确赋值,避免混用视图绑定。修改拍照逻辑,添加裁剪流程
拍照完成后先启动系统裁剪Intent,裁剪完成后再执行图像分类。完整修复代码
package com.example.app; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.example.app.ml.Model; import org.tensorflow.lite.DataType; import org.tensorflow.lite.support.tensorbuffer.TensorBuffer; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends AppCompatActivity { TextView result, confidence; ImageView imageView; Button picture; int imageSize = 224; private Uri photoUri; private static final int REQUEST_IMAGE_CAPTURE = 1; private static final int REQUEST_IMAGE_CROP = 2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 统一用findViewById初始化所有控件,避免空指针 result = findViewById(R.id.result); confidence = findViewById(R.id.confidence); imageView = findViewById(R.id.imageView); picture = findViewById(R.id.button); picture.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { dispatchTakePictureIntent(); } else { requestPermissions(new String[]{Manifest.permission.CAMERA}, 100); } } else { dispatchTakePictureIntent(); } } }); } // 创建临时文件保存拍照原图 private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName, ".jpg", storageDir); } // 启动拍照Intent private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { ex.printStackTrace(); } if (photoFile != null) { photoUri = FileProvider.getUriForFile(this, "com.example.app.fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } } // 启动系统裁剪Intent private void startCropIntent(Uri imageUri) { Intent cropIntent = new Intent("com.android.camera.action.CROP"); cropIntent.setDataAndType(imageUri, "image/*"); cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 设置裁剪参数,这里用1:1比例,可按需修改 cropIntent.putExtra("crop", "true"); cropIntent.putExtra("aspectX", 1); cropIntent.putExtra("aspectY", 1); cropIntent.putExtra("outputX", imageSize); cropIntent.putExtra("outputY", imageSize); cropIntent.putExtra("return-data", true); startActivityForResult(cropIntent, REQUEST_IMAGE_CROP); } public void classifyImage(Bitmap image){ try { Model model = Model.newInstance(getApplicationContext()); TensorBuffer inputFeature0 = TensorBuffer.createFixedSize(new int[]{1, 224, 224, 3}, DataType.FLOAT32); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4 * imageSize * imageSize * 3); byteBuffer.order(ByteOrder.nativeOrder()); int [] intValues = new int[imageSize * imageSize]; image.getPixels(intValues, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight()); int pixel = 0; for(int i = 0; i < imageSize; i++){ for(int j = 0; j < imageSize; j++){ int val = intValues[pixel++]; byteBuffer.putFloat(((val >> 16 ) & 0xFF) * (1.f / 255.f)); byteBuffer.putFloat(((val >> 8 ) & 0xFF) * (1.f / 255.f)); byteBuffer.putFloat((val & 0xFF ) * (1.f / 255.f)); } } inputFeature0.loadBuffer(byteBuffer); Model.Outputs outputs = model.process(inputFeature0); TensorBuffer outputFeature0 = outputs.getOutputFeature0AsTensorBuffer(); float[] confidences = outputFeature0.getFloatArray(); int maxPos = 0; float maxConfidence = 0; for(int i = 0; i < confidences.length; i++){ if(confidences[i] > maxConfidence){ maxConfidence = confidences[i]; maxPos = i; } } String[] classes = {"Negative", "+/-100", "250", "500", "1000", ">=2000"}; result.setText(classes[maxPos]); String s = ""; for(int i = 0; i < classes.length; i++){ s += String.format("%s: %.1f%%\n", classes[i], confidences[i] * 100); } confidence.setText(s); model.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { // 拍照完成后启动裁剪 startCropIntent(photoUri); } else if (requestCode == REQUEST_IMAGE_CROP && resultCode == RESULT_OK) { // 裁剪完成后获取Bitmap并执行分类 Bundle extras = data.getExtras(); if (extras != null) { Bitmap croppedBitmap = extras.getParcelable("data"); imageView.setImageBitmap(croppedBitmap); classifyImage(croppedBitmap); } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { dispatchTakePictureIntent(); } } } }
- 配置FileProvider(Android 7.0+必填)
- 在
res/xml目录下创建file_paths.xml:
<?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" /> </paths>- 在
AndroidManifest.xml中添加Provider配置:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.app.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> - 在
方案二:使用轻量级第三方裁剪库(可选)
如果系统裁剪样式不符合需求,可以用uCrop库,集成步骤简单:
- 在
build.gradle中添加依赖:
implementation 'com.github.yalantis:ucrop:2.2.8'
- 替换
startCropIntent方法:
private void startCropIntent(Uri imageUri) { UCrop.of(imageUri, Uri.fromFile(new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "cropped_image.jpg"))) .withAspectRatio(1, 1) .withMaxResultSize(imageSize, imageSize) .start(this); }
- 修改
onActivityResult中裁剪结果的处理:
else if (requestCode == UCrop.REQUEST_CROP && resultCode == RESULT_OK) { Uri croppedUri = UCrop.getOutput(data); try { Bitmap croppedBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), croppedUri); imageView.setImageBitmap(croppedBitmap); classifyImage(croppedBitmap); } catch (IOException e) { e.printStackTrace(); } }
关键注意事项
- 禁止混用多种视图初始化方式,确保所有控件都被正确赋值,避免空指针异常。
- Android 7.0+必须使用FileProvider传递文件Uri,否则会触发权限异常。
- 裁剪后的图片尺寸建议直接设置为模型要求的224x224,减少后续缩放的性能损耗。
内容的提问来源于stack exchange,提问作者sgs2022
相关产品推荐
相关产品推荐

