效果
动态识别:
触发识别
资源
文章依赖开源库
训练的模型
初始化模型
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;
}
}