You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Android 4.2无Play Services环境下摄像头OCR实现求助

Hey Neeraj, I totally get where you’re coming from—custom Android 4.2 devices with proprietary controllers can feel like navigating a maze, especially when Google Play is off the table and you’re more comfortable with C/C++/Python than Android’s Java ecosystem. Let’s build this from the ground up, focusing on two core pieces: capturing photos with your custom controller and getting Tesseract OCR working reliably (since you hit snags with examples before).


Part 1: Basic Camera Capture (Custom Controller Friendly)

First, since you’re using a custom controller, we’ll handle its input directly in your Activity. Android lets you override key events, so we’ll map your controller’s "shutter" button to trigger the camera.

Here’s a stripped-down Activity setup. Note: We’re using the legacy Camera class instead of newer frameworks like CameraX because Android 4.2 (API 17) doesn’t support them:

import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.KeyEvent;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class CameraOcrActivity extends Activity implements SurfaceHolder.Callback {
    private Camera mCamera;
    private SurfaceView mPreview;
    private SurfaceHolder mHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        mPreview = findViewById(R.id.camera_preview);
        mHolder = mPreview.getHolder();
        mHolder.addCallback(this);
        // Deprecated but required for Android 4.2 compatibility
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    // Handle custom controller key press (replace KEYCODE_CAMERA with your controller's actual key code)
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_CAMERA) {
            takePicture();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    private void takePicture() {
        if (mCamera != null) {
            mCamera.takePicture(null, null, mPictureCallback);
        }
    }

    private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            // Save photo to internal storage (avoids external storage permission headaches)
            File photoFile = new File(getFilesDir(), "captured_photo.jpg");
            try (FileOutputStream fos = new FileOutputStream(photoFile)) {
                fos.write(data);
                Toast.makeText(CameraOcrActivity.this, "Photo saved", Toast.LENGTH_SHORT).show();
                // Pass the file path to OCR function
                runOcrOnImage(photoFile.getAbsolutePath());
                // Restart preview for next shot
                mCamera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(CameraOcrActivity.this, "Failed to save photo", Toast.LENGTH_SHORT).show();
            }
        }
    };

    // SurfaceHolder callbacks to manage camera lifecycle
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera = Camera.open();
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mHolder.getSurface() == null) return;
        try {
            mCamera.stopPreview();
            // Optional: Adjust camera parameters (rotation, resolution) here
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    // We'll implement this in Part 2
    private void runOcrOnImage(String imagePath) {
        // OCR logic here
    }
}

Corresponding layout (res/layout/activity_camera.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Pro Tip: To find your custom controller’s key code, temporarily add a log statement in onKeyDown to print the keyCode value when you press the shutter button.


Part 2: Tesseract OCR (No Google Play, Android 4.2 Compatible)

The most common issues with Tesseract on older Android versions are using a too-new library or failing to properly copy tessdata files. We’ll use tess-two—a maintained fork of the original Tesseract Android port that works with API 17.

Step 1: Add tess-two to your project

Since you can’t use Google Play, download the pre-built AAR for API 17 from the tess-two releases (or build it from source, which might be more reliable given your C/C++ background). Add the AAR to your project’s libs folder, then update your module-level build.gradle:

dependencies {
    implementation files('libs/tess-two-release.aar')
}

Step 2: Add tessdata files

Download your target language’s .traineddata file (e.g., eng.traineddata for English) from the Tesseract tessdata repo. Create an assets/tessdata folder in your project and paste the file there.

Step 3: Implement OCR Logic

Update the runOcrOnImage method in your Activity:

import com.googlecode.tesseract.android.TessBaseAPI;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import java.io.InputStream;
import java.io.OutputStream;

private void runOcrOnImage(String imagePath) {
    // Copy tessdata from assets to local storage (only need to do this once)
    File tessDataDir = new File(getFilesDir(), "tessdata");
    if (!tessDataDir.exists()) {
        tessDataDir.mkdirs();
        try {
            copyAsset("tessdata/eng.traineddata", tessDataDir.getAbsolutePath() + "/eng.traineddata");
        } catch (IOException e) {
            Log.e("OCR", "Failed to copy tessdata", e);
            Toast.makeText(this, "OCR setup failed", Toast.LENGTH_SHORT).show();
            return;
        }
    }

    // Initialize Tesseract
    TessBaseAPI tessBaseAPI = new TessBaseAPI();
    boolean initSuccess = tessBaseAPI.init(getFilesDir().getAbsolutePath(), "eng");
    if (!initSuccess) {
        Log.e("OCR", "Tesseract initialization failed");
        Toast.makeText(this, "OCR init failed", Toast.LENGTH_SHORT).show();
        tessBaseAPI.end();
        return;
    }

    // Load and process the image
    Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
    tessBaseAPI.setImage(bitmap);
    String recognizedText = tessBaseAPI.getUTF8Text();

    // Output result
    Log.d("OCR Result", recognizedText);
    Toast.makeText(this, "OCR Result:\n" + recognizedText, Toast.LENGTH_LONG).show();

    // Cleanup to avoid memory leaks
    tessBaseAPI.end();
    bitmap.recycle();
}

// Helper to copy assets to local storage
private void copyAsset(String assetPath, String destPath) throws IOException {
    InputStream in = getAssets().open(assetPath);
    OutputStream out = new FileOutputStream(destPath);
    byte[] buffer = new byte[1024];
    int read;
    while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
    }
    in.close();
    out.close();
}

Bonus: C/C++ OCR via NDK (For Your Comfort Zone)

If you’d rather implement OCR in C/C++, use Tesseract’s C API via Android NDK. Here’s a quick JNI example:

JNI C++ Code (native-lib.cpp):

#include <jni.h>
#include <string>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_cameraocr_CameraOcrActivity_runOcrNative(JNIEnv* env, jobject thiz, jstring image_path) {
    const char* path = env->GetStringUTFChars(image_path, nullptr);

    tesseract::TessBaseAPI api;
    // Get app's files directory path from Java
    jobject fileObj = env->CallObjectMethod(thiz, env->GetMethodID(thiz, "getFilesDir", "()Ljava/io/File;"));
    jstring dirPath = (jstring)env->CallObjectMethod(fileObj, env->GetMethodID(env->GetObjectClass(fileObj), "getAbsolutePath", "()Ljava/lang/String;"));
    
    if (api.Init(env->GetStringUTFChars(dirPath, nullptr), "eng")) {
        env->ReleaseStringUTFChars(image_path, path);
        env->ReleaseStringUTFChars(dirPath, env->GetStringUTFChars(dirPath, nullptr));
        return env->NewStringUTF("Tesseract init failed");
    }

    Pix* pix = pixRead(path);
    api.SetImage(pix);
    char* outText = api.GetUTF8Text();

    jstring result = env->NewStringUTF(outText);

    // Cleanup
    api.End();
    pixDestroy(&pix);
    delete[] outText;
    env->ReleaseStringUTFChars(image_path, path);
    env->ReleaseStringUTFChars(dirPath, env->GetStringUTFChars(dirPath, nullptr));

    return result;
}

Call from Java:

Add these to your Activity:

// Load the native library
static {
    System.loadLibrary("native-lib");
}

// Native method declaration
private native String runOcrNative(String imagePath);

// Update runOcrOnImage to use the native method
private void runOcrOnImage(String imagePath) {
    // Copy tessdata first (same as Part 2 Step 3)
    // ...
    String result = runOcrNative(imagePath);
    Log.d("Native OCR Result", result);
    Toast.makeText(this, "Native OCR:\n" + result, Toast.LENGTH_LONG).show();
}

Critical Android 4.2 Checks

  • Permissions: Add these to your AndroidManifest.xml:
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-feature android:name="android.hardware.camera" />
    
    Android 4.2 grants permissions at install time, so no runtime checks are needed.
  • Camera Rotation: Use Camera.setDisplayOrientation() to fix preview/capture rotation if your device’s orientation is mismatched.
  • Memory Limits: Android 4.2 has strict memory constraints—always release Bitmaps and Tesseract instances promptly to avoid crashes.

内容的提问来源于stack exchange,提问作者Neeraj Aggarwal

火山引擎 最新活动