Android NCNN识别文字(OCR)

效果

动态识别:

触发识别

资源

文章依赖开源库

GitHub - cmdbug/YOLOv5_NCNN: 🍅 Deploy ncnn on mobile phones. Support Android and iOS. 移动端ncnn部署,支持Android与iOS。🍅 Deploy ncnn on mobile phones. Support Android and iOS. 移动端ncnn部署,支持Android与iOS。 - GitHub - cmdbug/YOLOv5_NCNN: 🍅 Deploy ncnn on mobile phones. Support Android and iOS. 移动端ncnn部署,支持Android与iOS。https://github.com/cmdbug/YOLOv5_NCNN

训练的模型

 初始化模型

dbnet->load_param(mgr, "dbnet_op.param");
dbnet->load_model(mgr, "dbnet_op.bin");
crnn_net = new ncnn::Net();
crnn_net->opt.use_vulkan_compute = toUseGPU;  // gpu
crnn_net->opt.use_fp16_arithmetic = true;  // fp16运算加速
crnn_net->load_param(mgr, "crnn_lite_op.param");
crnn_net->load_model(mgr, "crnn_lite_op.bin");
angle_net = new ncnn::Net();
angle_net->opt.use_vulkan_compute = toUseGPU;  // gpu
angle_net->opt.use_fp16_arithmetic = true;  // fp16运算加速
angle_net->load_param(mgr, "angle_op.param");
angle_net->load_model(mgr, "angle_op.bin");
/*获取文件名并打开*/
jboolean iscopy;
jstring filename = env->NewStringUTF("keys.txt");
const char *mfile = env->GetStringUTFChars(filename, &iscopy);
AAsset *asset = AAssetManager_open(mgr, mfile, AASSET_MODE_BUFFER);
env->ReleaseStringUTFChars(filename, mfile);

文字识别和文字轮廓识别

std::vector<OCRResult> OCR::detect(JNIEnv *env, jobject image, int short_size) {
    AndroidBitmapInfo img_size;
    AndroidBitmap_getInfo(env, image, &img_size);

    ncnn::Mat src_img = ncnn::Mat::from_android_bitmap_resize(env, image, ncnn::Mat::PIXEL_RGBA2RGB,
                                                              img_size.width, img_size.height);
    cv::Mat im_bgr(src_img.h, src_img.w, CV_8UC3);
    src_img.to_pixels(im_bgr.data, ncnn::Mat::PIXEL_RGB2BGR);

    // 图像缩放
    auto im = resize_img(im_bgr, short_size);

    int wid = im.cols;
    int hi = im.rows;
    int srcwid = im_bgr.cols;
    int srchi = im_bgr.rows;

    float h_scale = im_bgr.rows * 1.0 / im.rows;
    float w_scale = im_bgr.cols * 1.0 / im.cols;

    ncnn::Mat in = ncnn::Mat::from_pixels(im.data, ncnn::Mat::PIXEL_BGR2RGB, im.cols, im.rows);
    in.substract_mean_normalize(mean_vals_dbnet, norm_vals_dbnet);

//    LOGD("jni ocr input size:%d x %d", in.w, in.h);

    ncnn::Extractor ex = dbnet->create_extractor();
    ex.set_light_mode(true);
    ex.set_num_threads(num_thread);
    if (toUseGPU) {  // 消除提示
        ex.set_vulkan_compute(toUseGPU);
    }
    ex.input("input0", in);
    ncnn::Mat dbnet_out;
    double time1 = static_cast<double>(cv::getTickCount());
    ex.extract("out1", dbnet_out);
    //LOGE("jni ocr dbnet forward time:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    //LOGE("jni ocr output size:%d x %d", dbnet_out.w, dbnet_out.h);

    time1 = static_cast<double>(cv::getTickCount());


    cv::Mat fmapmat(hi, wid, CV_32FC1);
    memcpy(fmapmat.data, (float *) dbnet_out.data, wid * hi * sizeof(float));

    cv::Mat norfmapmat;

    norfmapmat = fmapmat > thresh;


    time1 = static_cast<double>(cv::getTickCount());
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(norfmapmat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
    for (auto & contour : contours) {
        std::vector<cv::Point> minbox;
        float minedgesize, alledgesize;
        get_mini_boxes(contour, minbox, minedgesize, alledgesize);

        if (minedgesize < min_size)
            continue;
        float score = box_score_fast(fmapmat, contour);

        if (score < box_thresh)
            continue;


        std::vector<cv::Point> newbox;
        unclip(minbox, alledgesize, newbox, unclip_ratio);

        get_mini_boxes(newbox, minbox, minedgesize, alledgesize);

        if (minedgesize < min_size + 2)
            continue;

        for (auto & i : minbox) {
            i.x *= w_scale;
            i.y *= h_scale;
        }

        boxes.push_back(minbox);
        box_score.push_back(score);  // teng
//        LOGD("jni ocr box score:%f", score);
    }
    LOGE("jni ocr 轮廓识别耗时:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    //LOGE("jni ocr dbnet decode time:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    //LOGE("jni ocr box size:%u", boxes.size());

//    auto result = draw_bbox(im_bgr, boxes);
//    cv::imwrite("./imgs/result.jpg", result);

    time1 = static_cast<double>(cv::getTickCount());
    //开始行文本角度检测和文字识别
    for (int i = boxes.size() - 1; i >= 0; i--) {
        std::vector<cv::Point> temp_box = boxes[i];

        cv::Mat part_im;
        part_im = GetRotateCropImage(im_bgr, temp_box);
        int part_im_w = part_im.cols;
        int part_im_h = part_im.rows;

        // 开始文本识别
        int crnn_w_target;
        float scale = crnn_h * 1.0 / part_im_h;
        crnn_w_target = int(part_im.cols * scale);

        cv::Mat img2 = part_im.clone();

        ncnn::Mat crnn_in = ncnn::Mat::from_pixels_resize(img2.data,
                                                          ncnn::Mat::PIXEL_BGR2RGB, img2.cols, img2.rows, crnn_w_target,
                                                          crnn_h);

        //角度检测
        int crnn_w = crnn_in.w;
        int crnn_h = crnn_in.h;

        ncnn::Mat angle_in;
        if (crnn_w >= angle_target_w) copy_cut_border(crnn_in, angle_in, 0, 0, 0, crnn_w - angle_target_w);
        else copy_make_border(crnn_in, angle_in, 0, 0, 0, angle_target_w - crnn_w, 0, 255.f);

        angle_in.substract_mean_normalize(mean_vals_crnn_angle, norm_vals_crnn_angle);


        ncnn::Extractor angle_ex = angle_net->create_extractor();
        angle_ex.set_light_mode(true);
        angle_ex.set_num_threads(num_thread);
        if (toUseGPU) {  // 消除提示
            angle_ex.set_vulkan_compute(toUseGPU);
        }
        angle_ex.input("input", angle_in);
        ncnn::Mat angle_preds;

        angle_ex.extract("out", angle_preds);

        auto *srcdata = (float *) angle_preds.data;

        float angle_score = srcdata[0];
//        LOGD("jni ocr angle score:%f", angle_score);
        //判断方向
        if (angle_score < 0.5) {
            part_im = matRotateClockWise180(part_im);
        }

        //crnn识别
        crnn_in.substract_mean_normalize(mean_vals_crnn_angle, norm_vals_crnn_angle);

        ncnn::Mat crnn_preds;


        ncnn::Extractor crnn_ex = crnn_net->create_extractor();
        crnn_ex.set_light_mode(true);
        crnn_ex.set_num_threads(num_thread);
        if (toUseGPU) {  // 消除提示
            crnn_ex.set_vulkan_compute(toUseGPU);
        }
        crnn_ex.input("input", crnn_in);


        ncnn::Mat blob162;
        crnn_ex.extract("443", blob162);

        ncnn::Mat blob263(5532, blob162.h);
        //batch fc
        for (int i = 0; i < blob162.h; i++) {
            ncnn::Extractor crnn_ex_2 = crnn_net->create_extractor();
            crnn_ex_2.set_light_mode(true);
            crnn_ex_2.set_num_threads(num_thread);
            if (toUseGPU) {  // 消除提示
                crnn_ex_2.set_vulkan_compute(toUseGPU);
            }
            ncnn::Mat blob243_i = blob162.row_range(i, 1);
            crnn_ex_2.input("457", blob243_i);

            ncnn::Mat blob263_i;
            crnn_ex_2.extract("458", blob263_i);

            memcpy(blob263.row(i), blob263_i, 5532 * sizeof(float));
        }

        crnn_preds = blob263;

        auto res_pre = crnn_deocde(crnn_preds, alphabetChinese);
//        pre_res.push_back(res_pre);// teng
        pre_res.insert(pre_res.begin(), res_pre);// teng

//        for (int i = 0; i < res_pre.size(); i++) {
//            LOGD("jni ocr res_pre:%s", res_pre[i].c_str());
//        }
    }
    LOGE("jni ocr 文字识别耗时:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    //LOGE("jni ocr time:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    //LOGE("jni ocr boxes size:%u", boxes.size());
    //LOGE("jni ocr pre_res size:%u", pre_res.size());
    //LOGE("jni ocr box_score size:%u", box_score.size());

    std::vector<OCRResult> resutls;
    //resutls.reserve(boxes.size());
    //for (int i = 0; i < boxes.size(); i++) {
    //    OCRResult ocrInfo;
    //    ocrInfo.boxes = boxes[i];
    //    ocrInfo.pre_res = pre_res[i];
    //    ocrInfo.box_score = box_score[i];
    //    resutls.push_back(ocrInfo);
    //    LOGE("jni ocr ocrresult[%d]:%s",i, ocrInfo.pre_res[0].c_str());
    //}
    return resutls;
}

接口

JNI接口

初始化

extern "C" JNIEXPORT void JNICALL
Java_com_ocr_library_ChineseOCRLite_init(JNIEnv *env, jclass clazz, jobject assetManager, jboolean useGPU) {
    if (OCR::detector != nullptr) {
        delete OCR::detector;
        OCR::detector = nullptr;
    }
    if (OCR::detector == nullptr) {
        AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);
        OCR::detector = new OCR(env, clazz, mgr, useGPU);
    }
}

识别

extern "C" JNIEXPORT jobjectArray JNICALL
Java_com_ocr_library_ChineseOCRLite_detect(JNIEnv *env, jclass clazz, jobject bitmap, jint short_size) {
    double time1 = static_cast<double>(cv::getTickCount());
    OCR::detector->detect(env, bitmap, short_size);
    LOGE("jni ocr 识别耗时:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
    double time2 = static_cast<double>(cv::getTickCount());
//    LOGD("jni ocr result size:%ld", ocrResult.size());
//    LOGD("jni ocr ocrresult[0].pre_res size:%ld", ocrResult[0].pre_res.size());
//    LOGD("jni ocr ocrresult[0][0]:%s", ocrResult[0].pre_res[0].c_str());

    auto ocr_result = env->FindClass("com/ocr/library/OCRResult");
    auto cid = env->GetMethodID(ocr_result, "<init>", "([D[DLjava/lang/String;)V");
    jobjectArray ret = env->NewObjectArray(OCR::detector->boxes.size(), ocr_result, nullptr);
    for (int i = 0; i < OCR::detector->boxes.size(); i++) {
        // boxScore
        env->PushLocalFrame(1);
        jdoubleArray boxScoreData = env->NewDoubleArray(1);
        auto *bsnum = new jdouble[1];
        for (int j = 0; j < 1; ++j) {
            *(bsnum + j) = OCR::detector->box_score[i];
        }
        env->SetDoubleArrayRegion(boxScoreData, 0, 1, bsnum);
        delete[] bsnum;

        // text
        char *cp = new char;
        for (auto &pre_re : OCR::detector->pre_res[i]) {
            strcat(cp, pre_re.c_str());
        }
        jstring text = str2jstring(env, cp);
        delete cp;

        // boxs
        jdoubleArray boxsData = env->NewDoubleArray(OCR::detector->boxes[i].size() * 2);
        auto *bnum = new jdouble[OCR::detector->boxes[i].size() * 2];
        for (int j = 0; j < OCR::detector->boxes[i].size(); j++) {
            *(bnum + j * 2) = OCR::detector->boxes[i][j].x;
            *(bnum + j * 2 + 1) = OCR::detector->boxes[i][j].y;
        }
        env->SetDoubleArrayRegion(boxsData, 0, OCR::detector->boxes[i].size() * 2, bnum);
        delete[] bnum;

        // 合并一下
        jobject obj = env->NewObject(ocr_result, cid, boxScoreData, boxsData, text);
        obj = env->PopLocalFrame(obj);
        env->SetObjectArrayElement(ret, i, obj);
    }
    OCR::detector->boxes.clear();
    OCR::detector->pre_res.clear();
    OCR::detector->box_score.clear();
    LOGE("jni ocr 处理耗时:%lfs", (static_cast<double>(cv::getTickCount()) - time2) / cv::getTickFrequency());
    LOGE("jni ocr 总耗时耗时:%lfs", (static_cast<double>(cv::getTickCount()) - time1) / cv::getTickFrequency());
//    LOGD("jni ocr return");
    return ret;
}

java接口

public class ChineseOCRLite {
    static {
        System.loadLibrary("OCRLite");
    }

    public static native void init(AssetManager manager, boolean useGPU);
    public static native OCRResult[] detect(Bitmap bitmap, int short_size);
}

 CmakeList编译

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")
if(DEFINED ANDROID_NDK_MAJOR AND ${ANDROID_NDK_MAJOR} GREATER 20)
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-openmp")
endif()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

# 搜索当前目录下的所有.cpp文件
aux_source_directory(ocr SRC_LIST)

add_library(
        OCRLite
        SHARED
        ${SRC_LIST}
)

#add_library( # Sets the name of the library.
#        yolov5
#
#        # Sets the library as a shared library.
#        SHARED
#
#        # Provides a relative path to your source file(s).
#        jni_interface.cpp
#        YoloV5.cpp
#        YoloV4.cpp
#        SimplePose.cpp
#        Yolact.cpp
#        ocr/clipper.cpp
#        ocr/NCNNDBNet.cpp
#        ocr/ocr.cpp
#        ocr/RRLib.cpp
#        ocr/ZUtil.cpp
#        ENet.cpp
#        FaceLandmark.cpp
#        DBFace.cpp
#        MbnFCN.cpp
#        MobileNetV3Seg.cpp
#        YoloV5CustomLayer.cpp
#        NanoDet.cpp
#        )

include_directories(
        ncnnvulkan/include
        ocr
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library(log-lib log)
find_library(android-lib android)
find_library(vulkan-lib vulkan)
find_library(jnigraphics-lib jnigraphics)
add_library( ncnn STATIC IMPORTED )
#add_library( ncnnvulkan STATIC IMPORTED )
set_target_properties( # Specifies the target library.
        ncnn
        #ncnnvulkan
        # Specifies the parameter you want to define.
        PROPERTIES IMPORTED_LOCATION

        # Provides the path to the library you want to import.
        ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/libncnn.a )

add_compile_options(-g)
# ncnnvulkan
add_library(glslang STATIC IMPORTED)
add_library(OGLCompiler STATIC IMPORTED)
add_library(OSDependent STATIC IMPORTED)
add_library(SPIRV STATIC IMPORTED)
set_target_properties(glslang PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/libglslang.a)
set_target_properties(OGLCompiler PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/libOGLCompiler.a)
set_target_properties(OSDependent PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/libOSDependent.a)
set_target_properties(SPIRV PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/libSPIRV.a)

# disable rtti and exceptions
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
# enable rtti and exceptions
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti")

include_directories(
        ${CMAKE_SOURCE_DIR}/opencv/include/
)
add_library(libopencv_java4 STATIC IMPORTED)
set_target_properties(
        libopencv_java4
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/opencv/${ANDROID_ABI}/libopencv_java4.so
)
target_link_libraries( # Specifies the target library.
        OCRLite

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        ${vulkan-lib}
        ${android-lib}
        ${jnigraphics-lib}
        libopencv_java4)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        OCRLite

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        ${vulkan-lib}
        ${android-lib}
        ${jnigraphics-lib}
        ncnn
        glslang SPIRV OGLCompiler OSDependent )

#target_link_libraries( # Specifies the target library.
#        OCRLite
#
#        # Links the target library to the log library
#        # included in the NDK.
#        ${log-lib}
#        ${vulkan-lib}
#        ${android-lib}
#        ${jnigraphics-lib}
#        ncnnvulkan
#        glslang SPIRV OGLCompiler OSDependent )

项目结构

使用 

文字识别用的ncnn库,轮廓识别用的opencv

使用CameraX来采集文字图片信息

private PreviewView previewView;
private ImageCapture imageCapture;
private ProcessCameraProvider cameraProvider;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private Preview preview;

cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity())
cameraProviderFuture.addListener(() -> {
    try {
        cameraProvider = cameraProviderFuture.get();
        //相机ID CameraSelector.LENS_FACING_FRONT前置;CameraSelector.LENS_F
        bindPreview(getActivity(), cameraProvider);
    } catch (Exception e) {
        e.printStackTrace();
    }
}, ContextCompat.getMainExecutor(getActivity()));

preview = new Preview.Builder()
        .setTargetAspectRatio(AspectRatio.RATIO_16_9)  //设置宽高比
        .setTargetRotation(Surface.ROTATION_0)         // 设置旋转角度
        .build();
CameraSelector cameraSelector = new CameraSelector.Builder()
        .requireLensFacing(CameraSelector.LENS_FACING_BACK)
        .build();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
        .setTargetAspectRatio(AspectRatio.RATIO_4_3)  //设置宽高比
        .setTargetRotation(Surface.ROTATION_0)         // 设置旋转角度
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(activity), imageProxy -> {
    if (!isLocked)
        initOCR(previewView.getBitmap());
    imageProxy.close();
});
//拍摄图像的配置
imageCapture = new ImageCapture.Builder()
        //CAPTURE_MODE_MAXIMIZE_QUALITY 拍摄高质量图片,图像质量优先于延迟,可能需要更长的时间
        //CAPTURE_MODE_MINIMIZE_LATENCY
        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
        .setTargetAspectRatio(AspectRatio.RATIO_4_3)  //设置宽高比
        .setTargetRotation(Surface.ROTATION_0)         // 设置旋转角度
        .build();
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle((LifecycleOwner) activity, cameraSelector, imageCapture, preview/*, imageAnalysis*/);
//这里previewView是预览图层,需要在布局中实现,然后在这里使用
preview.setSurfaceProvider(previewView.getSurfaceProvider());


//识别动作触发后
initOCR(previewView.getBitmap());

OCR接口初始化

调用接口ChineseOCRLite.init();

这里getAssets()是训练模型的保存路径,可以自定义路径。

public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(() -> ChineseOCRLite.init(getAssets(), false)).start();
    }
}

识别接口调用

调用接口ChineseOCRLite.detect();

所需要的数据是图片数据转换成的bitmap,文中是把相机获取到的数据直接转换成了bitmap.

识别出来的结果是OCRResult类型的数组。

private void initOCR(Bitmap bitmap) {
    isLocked = true;
    new Thread(() -> {
        try {
            outBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
            bitmap.recycle();
            int maxSize = Math.max(outBitmap.getWidth(), outBitmap.getHeight());
            final OCRResult[] ocrResult = ChineseOCRLite.detect(outBitmap, maxSize);
            final StringBuilder allText = new StringBuilder();
            if (ocrResult != null && ocrResult.length > 0) {
                outBitmap = drawResult(outBitmap, ocrResult);
                for (OCRResult result : ocrResult) {
                    //Log.e("NCNN", "text=" + result.text);
                    //Log.e("NCNN", "boxes=" + Arrays.toString(result.boxes));
                    //Log.e("NCNN", "boxScore=" + Arrays.toString(result.boxScore));
                    allText.append(result.text);
                }
            }
            handler.sendEmptyMessage(98);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

//识别结果绘制
protected Bitmap drawResult(Bitmap mutableBitmap, OCRResult[] results) {
    if (results == null || results.length <= 0) {
        return mutableBitmap;
    }
    Canvas canvas = new Canvas(mutableBitmap);
    final Paint boxPaint = new Paint();
    boxPaint.setAlpha(200);
    boxPaint.setStyle(Paint.Style.STROKE);
    boxPaint.setStrokeWidth(2 * Math.min(mutableBitmap.getWidth(), mutableBitmap.getHeight()) / 800.0f);
    boxPaint.setTextSize(15 * Math.min(mutableBitmap.getWidth(), mutableBitmap.getHeight()) / 800.0f);
    boxPaint.setColor(Color.BLUE);
    boxPaint.setAntiAlias(true);
    for (OCRResult result : results) {
        boxPaint.setColor(Color.RED);
        boxPaint.setStyle(Paint.Style.FILL);
        // 框
        canvas.drawLine((float) result.boxes[0], (float) result.boxes[1], (float) result.boxes[2], (float) result.boxes[3], boxPaint);
        canvas.drawLine((float) result.boxes[2], (float) result.boxes[3], (float) result.boxes[4], (float) result.boxes[5], boxPaint);
        canvas.drawLine((float) result.boxes[4], (float) result.boxes[5], (float) result.boxes[6], (float) result.boxes[7], boxPaint);
        canvas.drawLine((float) result.boxes[6], (float) result.boxes[7], (float) result.boxes[0], (float) result.boxes[1], boxPaint);
        // 文字
        if (showText) {  // 防止太乱
            double angle = getBoxAngle(result, true);
            canvas.save();
            canvas.rotate((float) angle, (float) result.boxes[0], (float) result.boxes[1] - 5);
            boxPaint.setColor(Color.BLUE);  // 防止有角度的框与之重叠
            if (angle > 70) {
                canvas.drawText(String.format(Locale.CHINESE, "%s  (%.3f)", result.text, result.boxScore[0]),
                        (float) result.boxes[0] + 5, (float) result.boxes[1] + 15, boxPaint);
            } else {
                canvas.drawText(String.format(Locale.CHINESE, "%s  (%.3f)", result.text, result.boxScore[0]),
                        (float) result.boxes[0], (float) result.boxes[1] - 5, boxPaint);
            }
            canvas.restore();
        }
        // 提示
        boxPaint.setColor(Color.YELLOW);  // 左上角画个红点
        canvas.drawPoint((float) result.boxes[0], (float) result.boxes[1], boxPaint);
        boxPaint.setColor(Color.GREEN);  // 右下角画个绿点
        canvas.drawPoint((float) result.boxes[4], (float) result.boxes[5], boxPaint);
    }
    return mutableBitmap;
}
protected double getBoxAngle(OCRResult ocrResult, boolean toDegrees) {
    double angle = 0.0f;
    if (ocrResult == null) {
        return angle;
    }
    // 0 1  2 3  4 5  6 7
    // x0y0 x1y1 x2y2 x3y3
    double dx1 = ocrResult.boxes[2] - ocrResult.boxes[0];
    double dy1 = ocrResult.boxes[3] - ocrResult.boxes[1];
    double dis1 = dy1 * dy1 + dx1 * dx1;
    double dx2 = ocrResult.boxes[4] - ocrResult.boxes[2];
    double dy2 = ocrResult.boxes[5] - ocrResult.boxes[3];
    double dis2 = dy2 * dy2 + dx2 * dx2;
    if (dis1 > dis2) {
        if (dx1 != 0) {
            angle = Math.asin(dy1 / dx1);
        }
    } else {
        if (dx2 != 0) {
            angle = Math.asin(dx2 / dy2);
        }
    }
    if (toDegrees) {
        angle = Math.toDegrees(angle);
        if (dis2 > dis1) {
            angle = angle + 90;
        }
        return angle;
    }
    return angle;
}

private final Handler.Callback callback = new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        if (msg.what == 98) {
            imageView.setImageBitmap(outBitmap);
            isLocked = false;
        }
        return false;
    }
};
private final Handler handler = new Handler(Looper.myLooper(), callback);
@Override
public void onDestroyView() {
    super.onDestroyView();
    handler.removeCallbacksAndMessages(callback);
}

这里public double[] boxScore; 取boxScore[0]即可。识别结果准确度

public String text;存放识别到的文字
public double[] boxes; 存放文字位置,可根据x,y的坐标位置,绘制出文字框

public class OCRResult {

    public double[] boxScore;  // 直接 boxScore[0] 就行
    public String text;
    public double[] boxes;  // xy xy xy xy


    public OCRResult (double[] boxScore, double[] boxes, String text) {
        this.boxScore = boxScore;
        this.boxes = boxes;
        this.text = text;
    }

    public double[] getBoxScore() {
        return boxScore;
    }

    public void setBoxScore(double[] boxScore) {
        this.boxScore = boxScore;
    }

    public double[] getBoxes() {
        return boxes;
    }

    public void setBoxes(double[] boxes) {
        this.boxes = boxes;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会写代码的猴子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值