You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio中基于TFLite实现图像分类前的图片裁剪方案求助

带前置图片裁剪的TFLite图像分类Android应用实现方案

崩溃原因分析

从报错日志看,NullPointerException出现在classifyImage方法调用result.setText(classes[maxPos])时,说明result或confidence这两个TextView对象未被正确初始化。你修改后的代码混用了视图绑定(binding)和findViewById两种方式,但既没有用视图绑定初始化result和confidence,也没通过findViewById给这两个变量赋值,导致它们为null。

简单实现方案

下面提供两种轻量的图片裁剪集成方案,同时修复崩溃问题:

方案一:使用系统自带裁剪Intent

直接调用系统原生裁剪功能,无需引入第三方库,步骤如下:

  1. 统一视图初始化方式
    继续使用原代码的findViewById方式,确保所有控件都被正确赋值,避免混用视图绑定。

  2. 修改拍照逻辑,添加裁剪流程
    拍照完成后先启动系统裁剪Intent,裁剪完成后再执行图像分类。

  3. 完整修复代码

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();
            }
        }
    }
}
  1. 配置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库,集成步骤简单:

  1. 在build.gradle中添加依赖:
implementation 'com.github.yalantis:ucrop:2.2.8'
  1. 替换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);
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:40:26