This commit is contained in:
80
deploy/lite/Makefile
Normal file
80
deploy/lite/Makefile
Normal file
@@ -0,0 +1,80 @@
|
||||
ARM_ABI = arm8
|
||||
export ARM_ABI
|
||||
|
||||
include ../Makefile.def
|
||||
|
||||
LITE_ROOT=../../../
|
||||
|
||||
THIRD_PARTY_DIR=${LITE_ROOT}/third_party
|
||||
|
||||
OPENCV_VERSION=opencv4.1.0
|
||||
|
||||
OPENCV_LIBS = ../../../third_party/${OPENCV_VERSION}/arm64-v8a/libs/libopencv_imgcodecs.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/libs/libopencv_imgproc.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/libs/libopencv_core.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/libtegra_hal.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/liblibjpeg-turbo.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/liblibwebp.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/liblibpng.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/liblibjasper.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/liblibtiff.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/libIlmImf.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/libtbb.a \
|
||||
../../../third_party/${OPENCV_VERSION}/arm64-v8a/3rdparty/libs/libcpufeatures.a
|
||||
|
||||
OPENCV_INCLUDE = -I../../../third_party/${OPENCV_VERSION}/arm64-v8a/include
|
||||
|
||||
CXX_INCLUDES = $(INCLUDES) ${OPENCV_INCLUDE} -I$(LITE_ROOT)/cxx/include
|
||||
|
||||
CXX_LIBS = ${OPENCV_LIBS} -L$(LITE_ROOT)/cxx/lib/ -lpaddle_light_api_shared $(SYSTEM_LIBS)
|
||||
|
||||
###############################################################
|
||||
# How to use one of static libaray: #
|
||||
# `libpaddle_api_full_bundled.a` #
|
||||
# `libpaddle_api_light_bundled.a` #
|
||||
###############################################################
|
||||
# Note: default use lite's shared library. #
|
||||
###############################################################
|
||||
# 1. Comment above line using `libpaddle_light_api_shared.so`
|
||||
# 2. Undo comment below line using `libpaddle_api_light_bundled.a`
|
||||
|
||||
#CXX_LIBS = $(LITE_ROOT)/cxx/lib/libpaddle_api_light_bundled.a $(SYSTEM_LIBS)
|
||||
|
||||
ocr_db_crnn: fetch_clipper fetch_opencv ocr_db_crnn.o crnn_process.o db_post_process.o clipper.o cls_process.o
|
||||
$(CC) $(SYSROOT_LINK) $(CXXFLAGS_LINK) ocr_db_crnn.o crnn_process.o db_post_process.o clipper.o cls_process.o -o ocr_db_crnn $(CXX_LIBS) $(LDFLAGS)
|
||||
|
||||
ocr_db_crnn.o: ocr_db_crnn.cc
|
||||
$(CC) $(SYSROOT_COMPLILE) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o ocr_db_crnn.o -c ocr_db_crnn.cc
|
||||
|
||||
crnn_process.o: fetch_opencv crnn_process.cc
|
||||
$(CC) $(SYSROOT_COMPLILE) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o crnn_process.o -c crnn_process.cc
|
||||
|
||||
cls_process.o: fetch_opencv cls_process.cc
|
||||
$(CC) $(SYSROOT_COMPLILE) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o cls_process.o -c cls_process.cc
|
||||
|
||||
db_post_process.o: fetch_clipper fetch_opencv db_post_process.cc
|
||||
$(CC) $(SYSROOT_COMPLILE) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o db_post_process.o -c db_post_process.cc
|
||||
|
||||
clipper.o: fetch_clipper
|
||||
$(CC) $(SYSROOT_COMPLILE) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o clipper.o -c clipper.cpp
|
||||
|
||||
fetch_clipper:
|
||||
@test -e clipper.hpp || \
|
||||
( echo "Fetch clipper " && \
|
||||
wget -c https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/Clipper/clipper.hpp)
|
||||
@ test -e clipper.cpp || \
|
||||
wget -c https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/Clipper/clipper.cpp
|
||||
|
||||
fetch_opencv:
|
||||
@ test -d ${THIRD_PARTY_DIR} || mkdir ${THIRD_PARTY_DIR}
|
||||
@ test -e ${THIRD_PARTY_DIR}/${OPENCV_VERSION}.tar.gz || \
|
||||
(echo "fetch opencv libs" && \
|
||||
wget -P ${THIRD_PARTY_DIR} https://paddle-inference-dist.bj.bcebos.com/${OPENCV_VERSION}.tar.gz)
|
||||
@ test -d ${THIRD_PARTY_DIR}/${OPENCV_VERSION} || \
|
||||
tar -zxvf ${THIRD_PARTY_DIR}/${OPENCV_VERSION}.tar.gz -C ${THIRD_PARTY_DIR}
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ocr_db_crnn.o clipper.o db_post_process.o crnn_process.o cls_process.o
|
||||
rm -f ocr_db_crnn
|
||||
43
deploy/lite/cls_process.cc
Normal file
43
deploy/lite/cls_process.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cls_process.h" //NOLINT
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
const std::vector<int> rec_image_shape{3, 48, 192};
|
||||
|
||||
cv::Mat ClsResizeImg(cv::Mat img) {
|
||||
int imgC, imgH, imgW;
|
||||
imgC = rec_image_shape[0];
|
||||
imgH = rec_image_shape[1];
|
||||
imgW = rec_image_shape[2];
|
||||
|
||||
float ratio = static_cast<float>(img.cols) / static_cast<float>(img.rows);
|
||||
|
||||
int resize_w, resize_h;
|
||||
if (ceilf(imgH * ratio) > imgW)
|
||||
resize_w = imgW;
|
||||
else
|
||||
resize_w = int(ceilf(imgH * ratio));
|
||||
cv::Mat resize_img;
|
||||
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
|
||||
cv::INTER_LINEAR);
|
||||
if (resize_w < imgW) {
|
||||
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, imgW - resize_w,
|
||||
cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
|
||||
}
|
||||
return resize_img;
|
||||
}
|
||||
29
deploy/lite/cls_process.h
Normal file
29
deploy/lite/cls_process.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "math.h" //NOLINT
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
cv::Mat ClsResizeImg(cv::Mat img);
|
||||
8
deploy/lite/config.txt
Normal file
8
deploy/lite/config.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
max_side_len 960
|
||||
det_db_thresh 0.3
|
||||
det_db_box_thresh 0.5
|
||||
det_db_unclip_ratio 1.6
|
||||
det_db_use_dilate 0
|
||||
det_use_polygon_score 1
|
||||
use_direction_classify 1
|
||||
rec_image_height 48
|
||||
118
deploy/lite/crnn_process.cc
Normal file
118
deploy/lite/crnn_process.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "crnn_process.h" //NOLINT
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
const std::vector<int> rec_image_shape{3, 32, 320};
|
||||
|
||||
cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio, int rec_image_height) {
|
||||
int imgC, imgH, imgW;
|
||||
imgC = rec_image_shape[0];
|
||||
imgH = rec_image_height;
|
||||
imgW = rec_image_shape[2];
|
||||
|
||||
imgW = int(imgH * wh_ratio);
|
||||
|
||||
float ratio = float(img.cols) / float(img.rows);
|
||||
int resize_w, resize_h;
|
||||
|
||||
if (ceilf(imgH * ratio) > imgW)
|
||||
resize_w = imgW;
|
||||
else
|
||||
resize_w = int(ceilf(imgH * ratio));
|
||||
cv::Mat resize_img;
|
||||
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
|
||||
cv::INTER_LINEAR);
|
||||
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0,
|
||||
int(imgW - resize_img.cols), cv::BORDER_CONSTANT,
|
||||
{127, 127, 127});
|
||||
return resize_img;
|
||||
}
|
||||
|
||||
std::vector<std::string> ReadDict(std::string path) {
|
||||
std::ifstream in(path);
|
||||
std::string filename;
|
||||
std::string line;
|
||||
std::vector<std::string> m_vec;
|
||||
if (in) {
|
||||
while (getline(in, line)) {
|
||||
m_vec.push_back(line);
|
||||
}
|
||||
} else {
|
||||
std::cout << "no such file" << std::endl;
|
||||
}
|
||||
return m_vec;
|
||||
}
|
||||
|
||||
cv::Mat GetRotateCropImage(cv::Mat srcimage,
|
||||
std::vector<std::vector<int>> box) {
|
||||
cv::Mat image;
|
||||
srcimage.copyTo(image);
|
||||
std::vector<std::vector<int>> points = box;
|
||||
|
||||
int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]};
|
||||
int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]};
|
||||
int left = int(*std::min_element(x_collect, x_collect + 4));
|
||||
int right = int(*std::max_element(x_collect, x_collect + 4));
|
||||
int top = int(*std::min_element(y_collect, y_collect + 4));
|
||||
int bottom = int(*std::max_element(y_collect, y_collect + 4));
|
||||
|
||||
cv::Mat img_crop;
|
||||
image(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop);
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
points[i][0] -= left;
|
||||
points[i][1] -= top;
|
||||
}
|
||||
|
||||
int img_crop_width =
|
||||
static_cast<int>(sqrt(pow(points[0][0] - points[1][0], 2) +
|
||||
pow(points[0][1] - points[1][1], 2)));
|
||||
int img_crop_height =
|
||||
static_cast<int>(sqrt(pow(points[0][0] - points[3][0], 2) +
|
||||
pow(points[0][1] - points[3][1], 2)));
|
||||
|
||||
cv::Point2f pts_std[4];
|
||||
pts_std[0] = cv::Point2f(0., 0.);
|
||||
pts_std[1] = cv::Point2f(img_crop_width, 0.);
|
||||
pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
|
||||
pts_std[3] = cv::Point2f(0.f, img_crop_height);
|
||||
|
||||
cv::Point2f pointsf[4];
|
||||
pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
|
||||
pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
|
||||
pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
|
||||
pointsf[3] = cv::Point2f(points[3][0], points[3][1]);
|
||||
|
||||
cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);
|
||||
|
||||
cv::Mat dst_img;
|
||||
cv::warpPerspective(img_crop, dst_img, M,
|
||||
cv::Size(img_crop_width, img_crop_height),
|
||||
cv::BORDER_REPLICATE);
|
||||
|
||||
const float ratio = 1.5;
|
||||
if (static_cast<float>(dst_img.rows) >=
|
||||
static_cast<float>(dst_img.cols) * ratio) {
|
||||
cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
|
||||
cv::transpose(dst_img, srcCopy);
|
||||
cv::flip(srcCopy, srcCopy, 0);
|
||||
return srcCopy;
|
||||
} else {
|
||||
return dst_img;
|
||||
}
|
||||
}
|
||||
38
deploy/lite/crnn_process.h
Normal file
38
deploy/lite/crnn_process.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "math.h" //NOLINT
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio, int rec_image_height);
|
||||
|
||||
std::vector<std::string> ReadDict(std::string path);
|
||||
|
||||
cv::Mat GetRotateCropImage(cv::Mat srcimage, std::vector<std::vector<int>> box);
|
||||
|
||||
template <class ForwardIterator>
|
||||
inline size_t Argmax(ForwardIterator first, ForwardIterator last) {
|
||||
return std::distance(first, std::max_element(first, last));
|
||||
}
|
||||
351
deploy/lite/db_post_process.cc
Normal file
351
deploy/lite/db_post_process.cc
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "db_post_process.h" // NOLINT
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
void GetContourArea(std::vector<std::vector<float>> box, float unclip_ratio,
|
||||
float &distance) {
|
||||
int pts_num = 4;
|
||||
float area = 0.0f;
|
||||
float dist = 0.0f;
|
||||
for (int i = 0; i < pts_num; i++) {
|
||||
area += box[i][0] * box[(i + 1) % pts_num][1] -
|
||||
box[i][1] * box[(i + 1) % pts_num][0];
|
||||
dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) *
|
||||
(box[i][0] - box[(i + 1) % pts_num][0]) +
|
||||
(box[i][1] - box[(i + 1) % pts_num][1]) *
|
||||
(box[i][1] - box[(i + 1) % pts_num][1]));
|
||||
}
|
||||
area = fabs(float(area / 2.0));
|
||||
|
||||
distance = area * unclip_ratio / dist;
|
||||
}
|
||||
|
||||
cv::RotatedRect Unclip(std::vector<std::vector<float>> box,
|
||||
float unclip_ratio) {
|
||||
float distance = 1.0;
|
||||
|
||||
GetContourArea(box, unclip_ratio, distance);
|
||||
|
||||
ClipperLib::ClipperOffset offset;
|
||||
ClipperLib::Path p;
|
||||
p << ClipperLib::IntPoint(static_cast<int>(box[0][0]),
|
||||
static_cast<int>(box[0][1]))
|
||||
<< ClipperLib::IntPoint(static_cast<int>(box[1][0]),
|
||||
static_cast<int>(box[1][1]))
|
||||
<< ClipperLib::IntPoint(static_cast<int>(box[2][0]),
|
||||
static_cast<int>(box[2][1]))
|
||||
<< ClipperLib::IntPoint(static_cast<int>(box[3][0]),
|
||||
static_cast<int>(box[3][1]));
|
||||
offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
|
||||
|
||||
ClipperLib::Paths soln;
|
||||
offset.Execute(soln, distance);
|
||||
std::vector<cv::Point2f> points;
|
||||
|
||||
for (int j = 0; j < soln.size(); j++) {
|
||||
for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
|
||||
points.emplace_back(soln[j][i].X, soln[j][i].Y);
|
||||
}
|
||||
}
|
||||
cv::RotatedRect res = cv::minAreaRect(points);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> Mat2Vector(cv::Mat mat) {
|
||||
std::vector<std::vector<float>> img_vec;
|
||||
std::vector<float> tmp;
|
||||
|
||||
for (int i = 0; i < mat.rows; ++i) {
|
||||
tmp.clear();
|
||||
for (int j = 0; j < mat.cols; ++j) {
|
||||
tmp.push_back(mat.at<float>(i, j));
|
||||
}
|
||||
img_vec.push_back(tmp);
|
||||
}
|
||||
return img_vec;
|
||||
}
|
||||
|
||||
bool XsortFp32(std::vector<float> a, std::vector<float> b) {
|
||||
if (a[0] != b[0])
|
||||
return a[0] < b[0];
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XsortInt(std::vector<int> a, std::vector<int> b) {
|
||||
if (a[0] != b[0])
|
||||
return a[0] < b[0];
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::vector<int>>
|
||||
OrderPointsClockwise(std::vector<std::vector<int>> pts) {
|
||||
std::vector<std::vector<int>> box = pts;
|
||||
std::sort(box.begin(), box.end(), XsortInt);
|
||||
|
||||
std::vector<std::vector<int>> leftmost = {box[0], box[1]};
|
||||
std::vector<std::vector<int>> rightmost = {box[2], box[3]};
|
||||
|
||||
if (leftmost[0][1] > leftmost[1][1])
|
||||
std::swap(leftmost[0], leftmost[1]);
|
||||
|
||||
if (rightmost[0][1] > rightmost[1][1])
|
||||
std::swap(rightmost[0], rightmost[1]);
|
||||
|
||||
std::vector<std::vector<int>> rect = {leftmost[0], rightmost[0], rightmost[1],
|
||||
leftmost[1]};
|
||||
return rect;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> GetMiniBoxes(cv::RotatedRect box, float &ssid) {
|
||||
ssid = std::min(box.size.width, box.size.height);
|
||||
|
||||
cv::Mat points;
|
||||
cv::boxPoints(box, points);
|
||||
|
||||
auto array = Mat2Vector(points);
|
||||
std::sort(array.begin(), array.end(), XsortFp32);
|
||||
|
||||
std::vector<float> idx1 = array[0], idx2 = array[1], idx3 = array[2],
|
||||
idx4 = array[3];
|
||||
if (array[3][1] <= array[2][1]) {
|
||||
idx2 = array[3];
|
||||
idx3 = array[2];
|
||||
} else {
|
||||
idx2 = array[2];
|
||||
idx3 = array[3];
|
||||
}
|
||||
if (array[1][1] <= array[0][1]) {
|
||||
idx1 = array[1];
|
||||
idx4 = array[0];
|
||||
} else {
|
||||
idx1 = array[0];
|
||||
idx4 = array[1];
|
||||
}
|
||||
|
||||
array[0] = idx1;
|
||||
array[1] = idx2;
|
||||
array[2] = idx3;
|
||||
array[3] = idx4;
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
float BoxScoreFast(std::vector<std::vector<float>> box_array, cv::Mat pred) {
|
||||
auto array = box_array;
|
||||
int width = pred.cols;
|
||||
int height = pred.rows;
|
||||
|
||||
float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]};
|
||||
float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]};
|
||||
|
||||
int xmin = clamp(
|
||||
static_cast<int>(std::floorf(*(std::min_element(box_x, box_x + 4)))), 0,
|
||||
width - 1);
|
||||
int xmax =
|
||||
clamp(static_cast<int>(std::ceilf(*(std::max_element(box_x, box_x + 4)))),
|
||||
0, width - 1);
|
||||
int ymin = clamp(
|
||||
static_cast<int>(std::floorf(*(std::min_element(box_y, box_y + 4)))), 0,
|
||||
height - 1);
|
||||
int ymax =
|
||||
clamp(static_cast<int>(std::ceilf(*(std::max_element(box_y, box_y + 4)))),
|
||||
0, height - 1);
|
||||
|
||||
cv::Mat mask;
|
||||
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
|
||||
|
||||
cv::Point root_point[4];
|
||||
root_point[0] = cv::Point(static_cast<int>(array[0][0]) - xmin,
|
||||
static_cast<int>(array[0][1]) - ymin);
|
||||
root_point[1] = cv::Point(static_cast<int>(array[1][0]) - xmin,
|
||||
static_cast<int>(array[1][1]) - ymin);
|
||||
root_point[2] = cv::Point(static_cast<int>(array[2][0]) - xmin,
|
||||
static_cast<int>(array[2][1]) - ymin);
|
||||
root_point[3] = cv::Point(static_cast<int>(array[3][0]) - xmin,
|
||||
static_cast<int>(array[3][1]) - ymin);
|
||||
const cv::Point *ppt[1] = {root_point};
|
||||
int npt[] = {4};
|
||||
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
|
||||
|
||||
cv::Mat croppedImg;
|
||||
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1))
|
||||
.copyTo(croppedImg);
|
||||
|
||||
auto score = cv::mean(croppedImg, mask)[0];
|
||||
return score;
|
||||
}
|
||||
|
||||
float PolygonScoreAcc(std::vector<cv::Point> contour, cv::Mat pred) {
|
||||
int width = pred.cols;
|
||||
int height = pred.rows;
|
||||
std::vector<float> box_x;
|
||||
std::vector<float> box_y;
|
||||
for (int i = 0; i < contour.size(); ++i) {
|
||||
box_x.push_back(contour[i].x);
|
||||
box_y.push_back(contour[i].y);
|
||||
}
|
||||
|
||||
int xmin =
|
||||
clamp(int(std::floor(*(std::min_element(box_x.begin(), box_x.end())))), 0,
|
||||
width - 1);
|
||||
int xmax =
|
||||
clamp(int(std::ceil(*(std::max_element(box_x.begin(), box_x.end())))), 0,
|
||||
width - 1);
|
||||
int ymin =
|
||||
clamp(int(std::floor(*(std::min_element(box_y.begin(), box_y.end())))), 0,
|
||||
height - 1);
|
||||
int ymax =
|
||||
clamp(int(std::ceil(*(std::max_element(box_y.begin(), box_y.end())))), 0,
|
||||
height - 1);
|
||||
|
||||
cv::Mat mask;
|
||||
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
|
||||
|
||||
cv::Point *rook_point = new cv::Point[contour.size()];
|
||||
|
||||
for (int i = 0; i < contour.size(); ++i) {
|
||||
rook_point[i] = cv::Point(int(box_x[i]) - xmin, int(box_y[i]) - ymin);
|
||||
}
|
||||
const cv::Point *ppt[1] = {rook_point};
|
||||
int npt[] = {int(contour.size())};
|
||||
|
||||
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
|
||||
|
||||
cv::Mat croppedImg;
|
||||
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1))
|
||||
.copyTo(croppedImg);
|
||||
float score = cv::mean(croppedImg, mask)[0];
|
||||
|
||||
delete[] rook_point;
|
||||
return score;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::vector<int>>>
|
||||
BoxesFromBitmap(const cv::Mat pred, const cv::Mat bitmap,
|
||||
std::map<std::string, double> Config) {
|
||||
const int min_size = 3;
|
||||
const int max_candidates = 1000;
|
||||
const float box_thresh = static_cast<float>(Config["det_db_box_thresh"]);
|
||||
const float unclip_ratio = static_cast<float>(Config["det_db_unclip_ratio"]);
|
||||
const int det_use_polygon_score = int(Config["det_use_polygon_score"]);
|
||||
|
||||
int width = bitmap.cols;
|
||||
int height = bitmap.rows;
|
||||
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
std::vector<cv::Vec4i> hierarchy;
|
||||
|
||||
cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST,
|
||||
cv::CHAIN_APPROX_SIMPLE);
|
||||
|
||||
int num_contours =
|
||||
contours.size() >= max_candidates ? max_candidates : contours.size();
|
||||
|
||||
std::vector<std::vector<std::vector<int>>> boxes;
|
||||
|
||||
for (int i = 0; i < num_contours; i++) {
|
||||
float ssid;
|
||||
if (contours[i].size() <= 2)
|
||||
continue;
|
||||
|
||||
cv::RotatedRect box = cv::minAreaRect(contours[i]);
|
||||
auto array = GetMiniBoxes(box, ssid);
|
||||
|
||||
auto box_for_unclip = array;
|
||||
// end get_mini_box
|
||||
|
||||
if (ssid < min_size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float score;
|
||||
if (det_use_polygon_score) {
|
||||
score = PolygonScoreAcc(contours[i], pred);
|
||||
} else {
|
||||
score = BoxScoreFast(array, pred);
|
||||
}
|
||||
// end box_score_fast
|
||||
if (score < box_thresh)
|
||||
continue;
|
||||
|
||||
// start for unclip
|
||||
cv::RotatedRect points = Unclip(box_for_unclip, unclip_ratio);
|
||||
if (points.size.height < 1.001 && points.size.width < 1.001)
|
||||
continue;
|
||||
// end for unclip
|
||||
|
||||
cv::RotatedRect clipbox = points;
|
||||
auto cliparray = GetMiniBoxes(clipbox, ssid);
|
||||
|
||||
if (ssid < min_size + 2)
|
||||
continue;
|
||||
|
||||
int dest_width = pred.cols;
|
||||
int dest_height = pred.rows;
|
||||
std::vector<std::vector<int>> intcliparray;
|
||||
|
||||
for (int num_pt = 0; num_pt < 4; num_pt++) {
|
||||
std::vector<int> a{
|
||||
static_cast<int>(clamp(
|
||||
roundf(cliparray[num_pt][0] / float(width) * float(dest_width)),
|
||||
float(0), float(dest_width))),
|
||||
static_cast<int>(clamp(
|
||||
roundf(cliparray[num_pt][1] / float(height) * float(dest_height)),
|
||||
float(0), float(dest_height)))};
|
||||
intcliparray.push_back(a);
|
||||
}
|
||||
boxes.push_back(intcliparray);
|
||||
|
||||
} // end for
|
||||
return boxes;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::vector<int>>>
|
||||
FilterTagDetRes(std::vector<std::vector<std::vector<int>>> boxes, float ratio_h,
|
||||
float ratio_w, cv::Mat srcimg) {
|
||||
int oriimg_h = srcimg.rows;
|
||||
int oriimg_w = srcimg.cols;
|
||||
|
||||
std::vector<std::vector<std::vector<int>>> root_points;
|
||||
for (int n = 0; n < static_cast<int>(boxes.size()); n++) {
|
||||
boxes[n] = OrderPointsClockwise(boxes[n]);
|
||||
for (int m = 0; m < static_cast<int>(boxes[0].size()); m++) {
|
||||
boxes[n][m][0] /= ratio_w;
|
||||
boxes[n][m][1] /= ratio_h;
|
||||
|
||||
boxes[n][m][0] =
|
||||
static_cast<int>(std::min(std::max(boxes[n][m][0], 0), oriimg_w - 1));
|
||||
boxes[n][m][1] =
|
||||
static_cast<int>(std::min(std::max(boxes[n][m][1], 0), oriimg_h - 1));
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < boxes.size(); n++) {
|
||||
int rect_width, rect_height;
|
||||
rect_width =
|
||||
static_cast<int>(sqrt(pow(boxes[n][0][0] - boxes[n][1][0], 2) +
|
||||
pow(boxes[n][0][1] - boxes[n][1][1], 2)));
|
||||
rect_height =
|
||||
static_cast<int>(sqrt(pow(boxes[n][0][0] - boxes[n][3][0], 2) +
|
||||
pow(boxes[n][0][1] - boxes[n][3][1], 2)));
|
||||
if (rect_width <= 4 || rect_height <= 4)
|
||||
continue;
|
||||
root_points.push_back(boxes[n]);
|
||||
}
|
||||
return root_points;
|
||||
}
|
||||
62
deploy/lite/db_post_process.h
Normal file
62
deploy/lite/db_post_process.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
template <class T> T clamp(T x, T min, T max) {
|
||||
if (x > max)
|
||||
return max;
|
||||
if (x < min)
|
||||
return min;
|
||||
return x;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> Mat2Vector(cv::Mat mat);
|
||||
|
||||
void GetContourArea(std::vector<std::vector<float>> box, float unclip_ratio,
|
||||
float &distance);
|
||||
|
||||
cv::RotatedRect Unclip(std::vector<std::vector<float>> box, float unclip_ratio);
|
||||
|
||||
std::vector<std::vector<float>> Mat2Vector(cv::Mat mat);
|
||||
|
||||
bool XsortFp32(std::vector<float> a, std::vector<float> b);
|
||||
|
||||
bool XsortInt(std::vector<int> a, std::vector<int> b);
|
||||
|
||||
std::vector<std::vector<int>>
|
||||
OrderPointsClockwise(std::vector<std::vector<int>> pts);
|
||||
|
||||
std::vector<std::vector<float>> GetMiniBoxes(cv::RotatedRect box, float &ssid);
|
||||
|
||||
float BoxScoreFast(std::vector<std::vector<float>> box_array, cv::Mat pred);
|
||||
|
||||
std::vector<std::vector<std::vector<int>>>
|
||||
BoxesFromBitmap(const cv::Mat pred, const cv::Mat bitmap,
|
||||
std::map<std::string, double> Config);
|
||||
|
||||
std::vector<std::vector<std::vector<int>>>
|
||||
FilterTagDetRes(std::vector<std::vector<std::vector<int>>> boxes, float ratio_h,
|
||||
float ratio_w, cv::Mat srcimg);
|
||||
BIN
deploy/lite/imgs/lite_demo.png
Normal file
BIN
deploy/lite/imgs/lite_demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
679
deploy/lite/ocr_db_crnn.cc
Normal file
679
deploy/lite/ocr_db_crnn.cc
Normal file
@@ -0,0 +1,679 @@
|
||||
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "paddle_api.h" // NOLINT
|
||||
#include "paddle_place.h"
|
||||
#include <chrono>
|
||||
|
||||
#include "AutoLog/auto_log/lite_autolog.h"
|
||||
#include "cls_process.h"
|
||||
#include "crnn_process.h"
|
||||
#include "db_post_process.h"
|
||||
|
||||
using namespace paddle::lite_api; // NOLINT
|
||||
using namespace std;
|
||||
|
||||
// fill tensor with mean and scale and trans layout: nhwc -> nchw, neon speed up
|
||||
void NeonMeanScale(const float *din, float *dout, int size,
|
||||
const std::vector<float> mean,
|
||||
const std::vector<float> scale) {
|
||||
if (mean.size() != 3 || scale.size() != 3) {
|
||||
std::cerr << "[ERROR] mean or scale size must equal to 3" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
float32x4_t vmean0 = vdupq_n_f32(mean[0]);
|
||||
float32x4_t vmean1 = vdupq_n_f32(mean[1]);
|
||||
float32x4_t vmean2 = vdupq_n_f32(mean[2]);
|
||||
float32x4_t vscale0 = vdupq_n_f32(scale[0]);
|
||||
float32x4_t vscale1 = vdupq_n_f32(scale[1]);
|
||||
float32x4_t vscale2 = vdupq_n_f32(scale[2]);
|
||||
|
||||
float *dout_c0 = dout;
|
||||
float *dout_c1 = dout + size;
|
||||
float *dout_c2 = dout + size * 2;
|
||||
|
||||
int i = 0;
|
||||
for (; i < size - 3; i += 4) {
|
||||
float32x4x3_t vin3 = vld3q_f32(din);
|
||||
float32x4_t vsub0 = vsubq_f32(vin3.val[0], vmean0);
|
||||
float32x4_t vsub1 = vsubq_f32(vin3.val[1], vmean1);
|
||||
float32x4_t vsub2 = vsubq_f32(vin3.val[2], vmean2);
|
||||
float32x4_t vs0 = vmulq_f32(vsub0, vscale0);
|
||||
float32x4_t vs1 = vmulq_f32(vsub1, vscale1);
|
||||
float32x4_t vs2 = vmulq_f32(vsub2, vscale2);
|
||||
vst1q_f32(dout_c0, vs0);
|
||||
vst1q_f32(dout_c1, vs1);
|
||||
vst1q_f32(dout_c2, vs2);
|
||||
|
||||
din += 12;
|
||||
dout_c0 += 4;
|
||||
dout_c1 += 4;
|
||||
dout_c2 += 4;
|
||||
}
|
||||
for (; i < size; i++) {
|
||||
*(dout_c0++) = (*(din++) - mean[0]) * scale[0];
|
||||
*(dout_c1++) = (*(din++) - mean[1]) * scale[1];
|
||||
*(dout_c2++) = (*(din++) - mean[2]) * scale[2];
|
||||
}
|
||||
}
|
||||
|
||||
// resize image to a size multiple of 32 which is required by the network
|
||||
cv::Mat DetResizeImg(const cv::Mat img, int max_size_len,
|
||||
std::vector<float> &ratio_hw) {
|
||||
int w = img.cols;
|
||||
int h = img.rows;
|
||||
|
||||
float ratio = 1.f;
|
||||
int max_wh = w >= h ? w : h;
|
||||
if (max_wh > max_size_len) {
|
||||
if (h > w) {
|
||||
ratio = static_cast<float>(max_size_len) / static_cast<float>(h);
|
||||
} else {
|
||||
ratio = static_cast<float>(max_size_len) / static_cast<float>(w);
|
||||
}
|
||||
}
|
||||
|
||||
int resize_h = static_cast<int>(float(h) * ratio);
|
||||
int resize_w = static_cast<int>(float(w) * ratio);
|
||||
if (resize_h % 32 == 0)
|
||||
resize_h = resize_h;
|
||||
else if (resize_h / 32 < 1 + 1e-5)
|
||||
resize_h = 32;
|
||||
else
|
||||
resize_h = (resize_h / 32 - 1) * 32;
|
||||
|
||||
if (resize_w % 32 == 0)
|
||||
resize_w = resize_w;
|
||||
else if (resize_w / 32 < 1 + 1e-5)
|
||||
resize_w = 32;
|
||||
else
|
||||
resize_w = (resize_w / 32 - 1) * 32;
|
||||
|
||||
cv::Mat resize_img;
|
||||
cv::resize(img, resize_img, cv::Size(resize_w, resize_h));
|
||||
|
||||
ratio_hw.push_back(static_cast<float>(resize_h) / static_cast<float>(h));
|
||||
ratio_hw.push_back(static_cast<float>(resize_w) / static_cast<float>(w));
|
||||
return resize_img;
|
||||
}
|
||||
|
||||
cv::Mat RunClsModel(cv::Mat img, std::shared_ptr<PaddlePredictor> predictor_cls,
|
||||
const float thresh = 0.9) {
|
||||
std::vector<float> mean = {0.5f, 0.5f, 0.5f};
|
||||
std::vector<float> scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f};
|
||||
|
||||
cv::Mat srcimg;
|
||||
img.copyTo(srcimg);
|
||||
cv::Mat crop_img;
|
||||
img.copyTo(crop_img);
|
||||
cv::Mat resize_img;
|
||||
|
||||
int index = 0;
|
||||
float wh_ratio =
|
||||
static_cast<float>(crop_img.cols) / static_cast<float>(crop_img.rows);
|
||||
|
||||
resize_img = ClsResizeImg(crop_img);
|
||||
resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f);
|
||||
|
||||
const float *dimg = reinterpret_cast<const float *>(resize_img.data);
|
||||
|
||||
std::unique_ptr<Tensor> input_tensor0(std::move(predictor_cls->GetInput(0)));
|
||||
input_tensor0->Resize({1, 3, resize_img.rows, resize_img.cols});
|
||||
auto *data0 = input_tensor0->mutable_data<float>();
|
||||
|
||||
NeonMeanScale(dimg, data0, resize_img.rows * resize_img.cols, mean, scale);
|
||||
// Run CLS predictor
|
||||
predictor_cls->Run();
|
||||
|
||||
// Get output and run postprocess
|
||||
std::unique_ptr<const Tensor> softmax_out(
|
||||
std::move(predictor_cls->GetOutput(0)));
|
||||
auto *softmax_scores = softmax_out->mutable_data<float>();
|
||||
auto softmax_out_shape = softmax_out->shape();
|
||||
float score = 0;
|
||||
int label = 0;
|
||||
for (int i = 0; i < softmax_out_shape[1]; i++) {
|
||||
if (softmax_scores[i] > score) {
|
||||
score = softmax_scores[i];
|
||||
label = i;
|
||||
}
|
||||
}
|
||||
if (label % 2 == 1 && score > thresh) {
|
||||
cv::rotate(srcimg, srcimg, 1);
|
||||
}
|
||||
return srcimg;
|
||||
}
|
||||
|
||||
void RunRecModel(std::vector<std::vector<std::vector<int>>> boxes, cv::Mat img,
|
||||
std::shared_ptr<PaddlePredictor> predictor_crnn,
|
||||
std::vector<std::string> &rec_text,
|
||||
std::vector<float> &rec_text_score,
|
||||
std::vector<std::string> charactor_dict,
|
||||
std::shared_ptr<PaddlePredictor> predictor_cls,
|
||||
int use_direction_classify, std::vector<double> *times,
|
||||
int rec_image_height) {
|
||||
std::vector<float> mean = {0.5f, 0.5f, 0.5f};
|
||||
std::vector<float> scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f};
|
||||
|
||||
cv::Mat srcimg;
|
||||
img.copyTo(srcimg);
|
||||
cv::Mat crop_img;
|
||||
cv::Mat resize_img;
|
||||
|
||||
int index = 0;
|
||||
|
||||
std::vector<double> time_info = {0, 0, 0};
|
||||
for (int i = boxes.size() - 1; i >= 0; i--) {
|
||||
auto preprocess_start = std::chrono::steady_clock::now();
|
||||
crop_img = GetRotateCropImage(srcimg, boxes[i]);
|
||||
if (use_direction_classify >= 1) {
|
||||
crop_img = RunClsModel(crop_img, predictor_cls);
|
||||
}
|
||||
float wh_ratio =
|
||||
static_cast<float>(crop_img.cols) / static_cast<float>(crop_img.rows);
|
||||
|
||||
resize_img = CrnnResizeImg(crop_img, wh_ratio, rec_image_height);
|
||||
resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f);
|
||||
|
||||
const float *dimg = reinterpret_cast<const float *>(resize_img.data);
|
||||
|
||||
std::unique_ptr<Tensor> input_tensor0(
|
||||
std::move(predictor_crnn->GetInput(0)));
|
||||
input_tensor0->Resize({1, 3, resize_img.rows, resize_img.cols});
|
||||
auto *data0 = input_tensor0->mutable_data<float>();
|
||||
|
||||
NeonMeanScale(dimg, data0, resize_img.rows * resize_img.cols, mean, scale);
|
||||
auto preprocess_end = std::chrono::steady_clock::now();
|
||||
//// Run CRNN predictor
|
||||
auto inference_start = std::chrono::steady_clock::now();
|
||||
predictor_crnn->Run();
|
||||
|
||||
// Get output and run postprocess
|
||||
std::unique_ptr<const Tensor> output_tensor0(
|
||||
std::move(predictor_crnn->GetOutput(0)));
|
||||
auto *predict_batch = output_tensor0->data<float>();
|
||||
auto predict_shape = output_tensor0->shape();
|
||||
auto inference_end = std::chrono::steady_clock::now();
|
||||
|
||||
// ctc decode
|
||||
auto postprocess_start = std::chrono::steady_clock::now();
|
||||
std::string str_res;
|
||||
int argmax_idx;
|
||||
int last_index = 0;
|
||||
float score = 0.f;
|
||||
int count = 0;
|
||||
float max_value = 0.0f;
|
||||
|
||||
for (int n = 0; n < predict_shape[1]; n++) {
|
||||
argmax_idx = int(Argmax(&predict_batch[n * predict_shape[2]],
|
||||
&predict_batch[(n + 1) * predict_shape[2]]));
|
||||
max_value =
|
||||
float(*std::max_element(&predict_batch[n * predict_shape[2]],
|
||||
&predict_batch[(n + 1) * predict_shape[2]]));
|
||||
if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) {
|
||||
score += max_value;
|
||||
count += 1;
|
||||
str_res += charactor_dict[argmax_idx];
|
||||
}
|
||||
last_index = argmax_idx;
|
||||
}
|
||||
score /= count;
|
||||
rec_text.push_back(str_res);
|
||||
rec_text_score.push_back(score);
|
||||
auto postprocess_end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<float> preprocess_diff =
|
||||
preprocess_end - preprocess_start;
|
||||
time_info[0] += double(preprocess_diff.count() * 1000);
|
||||
std::chrono::duration<float> inference_diff =
|
||||
inference_end - inference_start;
|
||||
time_info[1] += double(inference_diff.count() * 1000);
|
||||
std::chrono::duration<float> postprocess_diff =
|
||||
postprocess_end - postprocess_start;
|
||||
time_info[2] += double(postprocess_diff.count() * 1000);
|
||||
}
|
||||
|
||||
times->push_back(time_info[0]);
|
||||
times->push_back(time_info[1]);
|
||||
times->push_back(time_info[2]);
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::vector<int>>>
|
||||
RunDetModel(std::shared_ptr<PaddlePredictor> predictor, cv::Mat img,
|
||||
std::map<std::string, double> Config, std::vector<double> *times) {
|
||||
// Read img
|
||||
int max_side_len = int(Config["max_side_len"]);
|
||||
int det_db_use_dilate = int(Config["det_db_use_dilate"]);
|
||||
|
||||
cv::Mat srcimg;
|
||||
img.copyTo(srcimg);
|
||||
|
||||
auto preprocess_start = std::chrono::steady_clock::now();
|
||||
std::vector<float> ratio_hw;
|
||||
img = DetResizeImg(img, max_side_len, ratio_hw);
|
||||
cv::Mat img_fp;
|
||||
img.convertTo(img_fp, CV_32FC3, 1.0 / 255.f);
|
||||
|
||||
// Prepare input data from image
|
||||
std::unique_ptr<Tensor> input_tensor0(std::move(predictor->GetInput(0)));
|
||||
input_tensor0->Resize({1, 3, img_fp.rows, img_fp.cols});
|
||||
auto *data0 = input_tensor0->mutable_data<float>();
|
||||
|
||||
std::vector<float> mean = {0.485f, 0.456f, 0.406f};
|
||||
std::vector<float> scale = {1 / 0.229f, 1 / 0.224f, 1 / 0.225f};
|
||||
const float *dimg = reinterpret_cast<const float *>(img_fp.data);
|
||||
NeonMeanScale(dimg, data0, img_fp.rows * img_fp.cols, mean, scale);
|
||||
auto preprocess_end = std::chrono::steady_clock::now();
|
||||
|
||||
// Run predictor
|
||||
auto inference_start = std::chrono::steady_clock::now();
|
||||
predictor->Run();
|
||||
|
||||
// Get output and post process
|
||||
std::unique_ptr<const Tensor> output_tensor(
|
||||
std::move(predictor->GetOutput(0)));
|
||||
auto *outptr = output_tensor->data<float>();
|
||||
auto shape_out = output_tensor->shape();
|
||||
auto inference_end = std::chrono::steady_clock::now();
|
||||
|
||||
// Save output
|
||||
auto postprocess_start = std::chrono::steady_clock::now();
|
||||
float pred[shape_out[2] * shape_out[3]];
|
||||
unsigned char cbuf[shape_out[2] * shape_out[3]];
|
||||
|
||||
for (int i = 0; i < int(shape_out[2] * shape_out[3]); i++) {
|
||||
pred[i] = static_cast<float>(outptr[i]);
|
||||
cbuf[i] = static_cast<unsigned char>((outptr[i]) * 255);
|
||||
}
|
||||
|
||||
cv::Mat cbuf_map(shape_out[2], shape_out[3], CV_8UC1,
|
||||
reinterpret_cast<unsigned char *>(cbuf));
|
||||
cv::Mat pred_map(shape_out[2], shape_out[3], CV_32F,
|
||||
reinterpret_cast<float *>(pred));
|
||||
|
||||
const double threshold = double(Config["det_db_thresh"]) * 255;
|
||||
const double max_value = 255;
|
||||
cv::Mat bit_map;
|
||||
cv::threshold(cbuf_map, bit_map, threshold, max_value, cv::THRESH_BINARY);
|
||||
if (det_db_use_dilate == 1) {
|
||||
cv::Mat dilation_map;
|
||||
cv::Mat dila_ele =
|
||||
cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
|
||||
cv::dilate(bit_map, dilation_map, dila_ele);
|
||||
bit_map = dilation_map;
|
||||
}
|
||||
auto boxes = BoxesFromBitmap(pred_map, bit_map, Config);
|
||||
|
||||
std::vector<std::vector<std::vector<int>>> filter_boxes =
|
||||
FilterTagDetRes(boxes, ratio_hw[0], ratio_hw[1], srcimg);
|
||||
auto postprocess_end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<float> preprocess_diff =
|
||||
preprocess_end - preprocess_start;
|
||||
times->push_back(double(preprocess_diff.count() * 1000));
|
||||
std::chrono::duration<float> inference_diff = inference_end - inference_start;
|
||||
times->push_back(double(inference_diff.count() * 1000));
|
||||
std::chrono::duration<float> postprocess_diff =
|
||||
postprocess_end - postprocess_start;
|
||||
times->push_back(double(postprocess_diff.count() * 1000));
|
||||
|
||||
return filter_boxes;
|
||||
}
|
||||
|
||||
std::shared_ptr<PaddlePredictor> loadModel(std::string model_file,
|
||||
int num_threads) {
|
||||
MobileConfig config;
|
||||
config.set_model_from_file(model_file);
|
||||
|
||||
config.set_threads(num_threads);
|
||||
std::shared_ptr<PaddlePredictor> predictor =
|
||||
CreatePaddlePredictor<MobileConfig>(config);
|
||||
return predictor;
|
||||
}
|
||||
|
||||
cv::Mat Visualization(cv::Mat srcimg,
|
||||
std::vector<std::vector<std::vector<int>>> boxes) {
|
||||
cv::Point rook_points[boxes.size()][4];
|
||||
for (int n = 0; n < boxes.size(); n++) {
|
||||
for (int m = 0; m < boxes[0].size(); m++) {
|
||||
rook_points[n][m] = cv::Point(static_cast<int>(boxes[n][m][0]),
|
||||
static_cast<int>(boxes[n][m][1]));
|
||||
}
|
||||
}
|
||||
cv::Mat img_vis;
|
||||
srcimg.copyTo(img_vis);
|
||||
for (int n = 0; n < boxes.size(); n++) {
|
||||
const cv::Point *ppt[1] = {rook_points[n]};
|
||||
int npt[] = {4};
|
||||
cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0);
|
||||
}
|
||||
|
||||
cv::imwrite("./vis.jpg", img_vis);
|
||||
std::cout << "The detection visualized image saved in ./vis.jpg" << std::endl;
|
||||
return img_vis;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &str,
|
||||
const std::string &delim) {
|
||||
std::vector<std::string> res;
|
||||
if ("" == str)
|
||||
return res;
|
||||
char *strs = new char[str.length() + 1];
|
||||
std::strcpy(strs, str.c_str());
|
||||
|
||||
char *d = new char[delim.length() + 1];
|
||||
std::strcpy(d, delim.c_str());
|
||||
|
||||
char *p = std::strtok(strs, d);
|
||||
while (p) {
|
||||
string s = p;
|
||||
res.push_back(s);
|
||||
p = std::strtok(NULL, d);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::map<std::string, double> LoadConfigTxt(std::string config_path) {
|
||||
auto config = ReadDict(config_path);
|
||||
|
||||
std::map<std::string, double> dict;
|
||||
for (int i = 0; i < config.size(); i++) {
|
||||
std::vector<std::string> res = split(config[i], " ");
|
||||
dict[res[0]] = stod(res[1]);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
void check_params(int argc, char **argv) {
|
||||
if (argc <= 1 ||
|
||||
(strcmp(argv[1], "det") != 0 && strcmp(argv[1], "rec") != 0 &&
|
||||
strcmp(argv[1], "system") != 0)) {
|
||||
std::cerr << "Please choose one mode of [det, rec, system] !" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (strcmp(argv[1], "det") == 0) {
|
||||
if (argc < 9) {
|
||||
std::cerr << "[ERROR] usage:" << argv[0]
|
||||
<< " det det_model runtime_device num_threads batchsize "
|
||||
"img_dir det_config lite_benchmark_value"
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "rec") == 0) {
|
||||
if (argc < 9) {
|
||||
std::cerr << "[ERROR] usage:" << argv[0]
|
||||
<< " rec rec_model runtime_device num_threads batchsize "
|
||||
"img_dir key_txt lite_benchmark_value"
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "system") == 0) {
|
||||
if (argc < 12) {
|
||||
std::cerr << "[ERROR] usage:" << argv[0]
|
||||
<< " system det_model rec_model clas_model runtime_device "
|
||||
"num_threads batchsize img_dir det_config key_txt "
|
||||
"lite_benchmark_value"
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void system(char **argv) {
|
||||
std::string det_model_file = argv[2];
|
||||
std::string rec_model_file = argv[3];
|
||||
std::string cls_model_file = argv[4];
|
||||
std::string runtime_device = argv[5];
|
||||
std::string precision = argv[6];
|
||||
std::string num_threads = argv[7];
|
||||
std::string batchsize = argv[8];
|
||||
std::string img_dir = argv[9];
|
||||
std::string det_config_path = argv[10];
|
||||
std::string dict_path = argv[11];
|
||||
|
||||
if (strcmp(argv[6], "FP32") != 0 && strcmp(argv[6], "INT8") != 0) {
|
||||
std::cerr << "Only support FP32 or INT8." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::vector<cv::String> cv_all_img_names;
|
||||
cv::glob(img_dir, cv_all_img_names);
|
||||
|
||||
//// load config from txt file
|
||||
auto Config = LoadConfigTxt(det_config_path);
|
||||
int use_direction_classify = int(Config["use_direction_classify"]);
|
||||
int rec_image_height = int(Config["rec_image_height"]);
|
||||
auto charactor_dict = ReadDict(dict_path);
|
||||
charactor_dict.insert(charactor_dict.begin(), "#"); // blank char for ctc
|
||||
charactor_dict.push_back(" ");
|
||||
|
||||
auto det_predictor = loadModel(det_model_file, std::stoi(num_threads));
|
||||
auto rec_predictor = loadModel(rec_model_file, std::stoi(num_threads));
|
||||
auto cls_predictor = loadModel(cls_model_file, std::stoi(num_threads));
|
||||
|
||||
std::vector<double> det_time_info = {0, 0, 0};
|
||||
std::vector<double> rec_time_info = {0, 0, 0};
|
||||
|
||||
for (int i = 0; i < cv_all_img_names.size(); ++i) {
|
||||
std::cout << "The predict img: " << cv_all_img_names[i] << std::endl;
|
||||
cv::Mat srcimg = cv::imread(cv_all_img_names[i], cv::IMREAD_COLOR);
|
||||
|
||||
if (!srcimg.data) {
|
||||
std::cerr << "[ERROR] image read failed! image path: "
|
||||
<< cv_all_img_names[i] << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::vector<double> det_times;
|
||||
auto boxes = RunDetModel(det_predictor, srcimg, Config, &det_times);
|
||||
|
||||
std::vector<std::string> rec_text;
|
||||
std::vector<float> rec_text_score;
|
||||
|
||||
std::vector<double> rec_times;
|
||||
RunRecModel(boxes, srcimg, rec_predictor, rec_text, rec_text_score,
|
||||
charactor_dict, cls_predictor, use_direction_classify,
|
||||
&rec_times, rec_image_height);
|
||||
|
||||
//// visualization
|
||||
auto img_vis = Visualization(srcimg, boxes);
|
||||
|
||||
//// print recognized text
|
||||
for (int i = 0; i < rec_text.size(); i++) {
|
||||
std::cout << i << "\t" << rec_text[i] << "\t" << rec_text_score[i]
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
det_time_info[0] += det_times[0];
|
||||
det_time_info[1] += det_times[1];
|
||||
det_time_info[2] += det_times[2];
|
||||
rec_time_info[0] += rec_times[0];
|
||||
rec_time_info[1] += rec_times[1];
|
||||
rec_time_info[2] += rec_times[2];
|
||||
}
|
||||
if (strcmp(argv[12], "True") == 0) {
|
||||
AutoLogger autolog_det(det_model_file, runtime_device,
|
||||
std::stoi(num_threads), std::stoi(batchsize),
|
||||
"dynamic", precision, det_time_info,
|
||||
cv_all_img_names.size());
|
||||
AutoLogger autolog_rec(rec_model_file, runtime_device,
|
||||
std::stoi(num_threads), std::stoi(batchsize),
|
||||
"dynamic", precision, rec_time_info,
|
||||
cv_all_img_names.size());
|
||||
|
||||
autolog_det.report();
|
||||
std::cout << std::endl;
|
||||
autolog_rec.report();
|
||||
}
|
||||
}
|
||||
|
||||
void det(int argc, char **argv) {
|
||||
std::string det_model_file = argv[2];
|
||||
std::string runtime_device = argv[3];
|
||||
std::string precision = argv[4];
|
||||
std::string num_threads = argv[5];
|
||||
std::string batchsize = argv[6];
|
||||
std::string img_dir = argv[7];
|
||||
std::string det_config_path = argv[8];
|
||||
|
||||
if (strcmp(argv[4], "FP32") != 0 && strcmp(argv[4], "INT8") != 0) {
|
||||
std::cerr << "Only support FP32 or INT8." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::vector<cv::String> cv_all_img_names;
|
||||
cv::glob(img_dir, cv_all_img_names);
|
||||
|
||||
//// load config from txt file
|
||||
auto Config = LoadConfigTxt(det_config_path);
|
||||
|
||||
auto det_predictor = loadModel(det_model_file, std::stoi(num_threads));
|
||||
|
||||
std::vector<double> time_info = {0, 0, 0};
|
||||
for (int i = 0; i < cv_all_img_names.size(); ++i) {
|
||||
std::cout << "The predict img: " << cv_all_img_names[i] << std::endl;
|
||||
cv::Mat srcimg = cv::imread(cv_all_img_names[i], cv::IMREAD_COLOR);
|
||||
|
||||
if (!srcimg.data) {
|
||||
std::cerr << "[ERROR] image read failed! image path: "
|
||||
<< cv_all_img_names[i] << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::vector<double> times;
|
||||
auto boxes = RunDetModel(det_predictor, srcimg, Config, ×);
|
||||
|
||||
//// visualization
|
||||
auto img_vis = Visualization(srcimg, boxes);
|
||||
std::cout << boxes.size() << " bboxes have detected:" << std::endl;
|
||||
|
||||
for (int i = 0; i < boxes.size(); i++) {
|
||||
std::cout << "The " << i << " box:" << std::endl;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
for (int k = 0; k < 2; k++) {
|
||||
std::cout << boxes[i][j][k] << "\t";
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
time_info[0] += times[0];
|
||||
time_info[1] += times[1];
|
||||
time_info[2] += times[2];
|
||||
}
|
||||
|
||||
if (strcmp(argv[9], "True") == 0) {
|
||||
AutoLogger autolog(det_model_file, runtime_device, std::stoi(num_threads),
|
||||
std::stoi(batchsize), "dynamic", precision, time_info,
|
||||
cv_all_img_names.size());
|
||||
autolog.report();
|
||||
}
|
||||
}
|
||||
|
||||
void rec(int argc, char **argv) {
|
||||
std::string rec_model_file = argv[2];
|
||||
std::string runtime_device = argv[3];
|
||||
std::string precision = argv[4];
|
||||
std::string num_threads = argv[5];
|
||||
std::string batchsize = argv[6];
|
||||
std::string img_dir = argv[7];
|
||||
std::string dict_path = argv[8];
|
||||
std::string config_path = argv[9];
|
||||
|
||||
if (strcmp(argv[4], "FP32") != 0 && strcmp(argv[4], "INT8") != 0) {
|
||||
std::cerr << "Only support FP32 or INT8." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto Config = LoadConfigTxt(config_path);
|
||||
int rec_image_height = int(Config["rec_image_height"]);
|
||||
|
||||
std::vector<cv::String> cv_all_img_names;
|
||||
cv::glob(img_dir, cv_all_img_names);
|
||||
|
||||
auto charactor_dict = ReadDict(dict_path);
|
||||
charactor_dict.insert(charactor_dict.begin(), "#"); // blank char for ctc
|
||||
charactor_dict.push_back(" ");
|
||||
|
||||
auto rec_predictor = loadModel(rec_model_file, std::stoi(num_threads));
|
||||
|
||||
std::shared_ptr<PaddlePredictor> cls_predictor;
|
||||
|
||||
std::vector<double> time_info = {0, 0, 0};
|
||||
for (int i = 0; i < cv_all_img_names.size(); ++i) {
|
||||
std::cout << "The predict img: " << cv_all_img_names[i] << std::endl;
|
||||
cv::Mat srcimg = cv::imread(cv_all_img_names[i], cv::IMREAD_COLOR);
|
||||
|
||||
if (!srcimg.data) {
|
||||
std::cerr << "[ERROR] image read failed! image path: "
|
||||
<< cv_all_img_names[i] << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int width = srcimg.cols;
|
||||
int height = srcimg.rows;
|
||||
std::vector<int> upper_left = {0, 0};
|
||||
std::vector<int> upper_right = {width, 0};
|
||||
std::vector<int> lower_right = {width, height};
|
||||
std::vector<int> lower_left = {0, height};
|
||||
std::vector<std::vector<int>> box = {upper_left, upper_right, lower_right,
|
||||
lower_left};
|
||||
std::vector<std::vector<std::vector<int>>> boxes = {box};
|
||||
|
||||
std::vector<std::string> rec_text;
|
||||
std::vector<float> rec_text_score;
|
||||
std::vector<double> times;
|
||||
RunRecModel(boxes, srcimg, rec_predictor, rec_text, rec_text_score,
|
||||
charactor_dict, cls_predictor, 0, ×, rec_image_height);
|
||||
|
||||
//// print recognized text
|
||||
for (int i = 0; i < rec_text.size(); i++) {
|
||||
std::cout << i << "\t" << rec_text[i] << "\t" << rec_text_score[i]
|
||||
<< std::endl;
|
||||
}
|
||||
time_info[0] += times[0];
|
||||
time_info[1] += times[1];
|
||||
time_info[2] += times[2];
|
||||
}
|
||||
// TODO: support autolog
|
||||
if (strcmp(argv[9], "True") == 0) {
|
||||
AutoLogger autolog(rec_model_file, runtime_device, std::stoi(num_threads),
|
||||
std::stoi(batchsize), "dynamic", precision, time_info,
|
||||
cv_all_img_names.size());
|
||||
autolog.report();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
check_params(argc, argv);
|
||||
std::cout << "mode: " << argv[1] << endl;
|
||||
|
||||
if (strcmp(argv[1], "system") == 0) {
|
||||
system(argv);
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "det") == 0) {
|
||||
det(argc, argv);
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "rec") == 0) {
|
||||
rec(argc, argv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
9
deploy/lite/prepare.sh
Normal file
9
deploy/lite/prepare.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p $1/demo/cxx/ocr/debug/
|
||||
cp ../../ppocr/utils/ppocr_keys_v1.txt $1/demo/cxx/ocr/debug/
|
||||
cp -r ./* $1/demo/cxx/ocr/
|
||||
cp ./config.txt $1/demo/cxx/ocr/debug/
|
||||
cp ../../doc/imgs/11.jpg $1/demo/cxx/ocr/debug/
|
||||
|
||||
echo "Prepare Done"
|
||||
298
deploy/lite/readme.md
Normal file
298
deploy/lite/readme.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Mobile deployment based on Paddle-Lite
|
||||
|
||||
- [1. Preparation](#1-preparation)
|
||||
- [Preparation environment](#preparation-environment)
|
||||
- [1.1 Prepare the cross-compilation environment](#11-prepare-the-cross-compilation-environment)
|
||||
- [1.2 Prepare Paddle-Lite library](#12-prepare-paddle-lite-library)
|
||||
- [2. Run](#2-run)
|
||||
- [2.1 Inference Model Optimization](#21-inference-model-optimization)
|
||||
- [2.2 Run optimized model on Phone](#22-run-optimized-model-on-phone)
|
||||
- [FAQ](#faq)
|
||||
|
||||
|
||||
This tutorial will introduce how to use [Paddle-Lite](https://github.com/PaddlePaddle/Paddle-Lite) to deploy PaddleOCR ultra-lightweight Chinese and English detection models on mobile phones.
|
||||
|
||||
Paddle-Lite is a lightweight inference engine for PaddlePaddle. It provides efficient inference capabilities for mobile phones and IoT, and extensively integrates cross-platform hardware to provide lightweight deployment solutions for end-side deployment issues.
|
||||
|
||||
## 1. Preparation
|
||||
|
||||
### Preparation environment
|
||||
|
||||
- Computer (for Compiling Paddle Lite)
|
||||
- Mobile phone (arm7 or arm8)
|
||||
|
||||
### 1.1 Prepare the cross-compilation environment
|
||||
The cross-compilation environment is used to compile C++ demos of Paddle Lite and PaddleOCR.
|
||||
Supports multiple development environments.
|
||||
|
||||
For the compilation process of different development environments, please refer to the corresponding documents.
|
||||
|
||||
1. [Docker](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#docker)
|
||||
2. [Linux](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#linux)
|
||||
3. [MAC OS](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#mac-os)
|
||||
|
||||
### 1.2 Prepare Paddle-Lite library
|
||||
|
||||
There are two ways to obtain the Paddle-Lite library:
|
||||
- 1. [Recommended] Download directly, the download link of the Paddle-Lite library is as follows:
|
||||
|
||||
| Platform | Paddle-Lite library download link |
|
||||
|---|---|
|
||||
|Android|[arm7](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.android.armv7.gcc.c++_shared.with_extra.with_cv.tar.gz) / [arm8](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.android.armv8.gcc.c++_shared.with_extra.with_cv.tar.gz)|
|
||||
|IOS|[arm7](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.ios.armv7.with_cv.with_extra.with_log.tiny_publish.tar.gz) / [arm8](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.ios.armv8.with_cv.with_extra.with_log.tiny_publish.tar.gz)|
|
||||
|
||||
Note: 1. The above Paddle-Lite library is compiled from the Paddle-Lite 2.10 branch. For more information about Paddle-Lite 2.10, please refer to [link](https://github.com/PaddlePaddle/Paddle-Lite/releases/tag/v2.10).
|
||||
|
||||
**Note: It is recommended to use paddlelite>=2.10 version of the prediction library, other prediction library versions [download link](https://github.com/PaddlePaddle/Paddle-Lite/tags)**
|
||||
|
||||
- 2. Compile Paddle-Lite to get the prediction library. The compilation method of Paddle-Lite is as follows:
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
|
||||
cd Paddle-Lite
|
||||
# Switch to Paddle-Lite release/v2.10 stable branch
|
||||
git checkout release/v2.10
|
||||
./lite/tools/build_android.sh --arch=armv8 --with_cv=ON --with_extra=ON
|
||||
```
|
||||
|
||||
Note: When compiling Paddle-Lite to obtain the Paddle-Lite library, you need to turn on the two options `--with_cv=ON --with_extra=ON`, `--arch` means the `arm` version, here is designated as armv8,
|
||||
|
||||
More compilation commands refer to the introduction [link](https://paddle-lite.readthedocs.io/zh/release-v2.10_a/source_compile/linux_x86_compile_android.html) 。
|
||||
|
||||
After directly downloading the Paddle-Lite library and decompressing it, you can get the `inference_lite_lib.android.armv8/` folder, and the Paddle-Lite library obtained by compiling Paddle-Lite is located
|
||||
`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/` folder.
|
||||
|
||||
The structure of the prediction library is as follows:
|
||||
```
|
||||
inference_lite_lib.android.armv8/
|
||||
|-- cxx C++ prebuild library
|
||||
| |-- include C++
|
||||
| | |-- paddle_api.h
|
||||
| | |-- paddle_image_preprocess.h
|
||||
| | |-- paddle_lite_factory_helper.h
|
||||
| | |-- paddle_place.h
|
||||
| | |-- paddle_use_kernels.h
|
||||
| | |-- paddle_use_ops.h
|
||||
| | `-- paddle_use_passes.h
|
||||
| `-- lib C++ library
|
||||
| |-- libpaddle_api_light_bundled.a C++ static library
|
||||
| `-- libpaddle_light_api_shared.so C++ dynamic library
|
||||
|-- java Java library
|
||||
| |-- jar
|
||||
| | `-- PaddlePredictor.jar
|
||||
| |-- so
|
||||
| | `-- libpaddle_lite_jni.so
|
||||
| `-- src
|
||||
|-- demo C++ and Java demo
|
||||
| |-- cxx C++ demo
|
||||
| `-- java Java demo
|
||||
```
|
||||
|
||||
## 2 Run
|
||||
|
||||
### 2.1 Inference Model Optimization
|
||||
|
||||
Paddle Lite provides a variety of strategies to automatically optimize the original training model, including quantization, sub-graph fusion, hybrid scheduling, Kernel optimization and so on. In order to make the optimization process more convenient and easy to use, Paddle Lite provide opt tools to automatically complete the optimization steps and output a lightweight, optimal executable model.
|
||||
|
||||
If you have prepared the model file ending in .nb, you can skip this step.
|
||||
|
||||
The following table also provides a series of models that can be deployed on mobile phones to recognize Chinese. You can directly download the optimized model.
|
||||
|
||||
|Version|Introduction|Model size|Detection model|Text Direction model|Recognition model|Paddle-Lite branch|
|
||||
|---|---|---|---|---|---|---|
|
||||
|PP-OCRv3|extra-lightweight chinese OCR optimized model|16.2M|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/PP-OCRv3_mobile_det_infer.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_infer_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.nb)|v2.10|
|
||||
|PP-OCRv3(slim)|extra-lightweight chinese OCR optimized model|5.9M|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_slim_infer.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_slim_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_slim_infer.nb)|v2.10|
|
||||
|PP-OCRv2|extra-lightweight chinese OCR optimized model|11M|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_det_infer_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_infer_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_rec_infer_opt.nb)|v2.10|
|
||||
|PP-OCRv2(slim)|extra-lightweight chinese OCR optimized model|4.6M|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_det_slim_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_slim_opt.nb)|[download link](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_rec_slim_opt.nb)|v2.10|
|
||||
|
||||
If you directly use the model in the above table for deployment, you can skip the following steps and directly read [Section 2.2](#2.2-Run-optimized-model-on-Phone).
|
||||
|
||||
If the model to be deployed is not in the above table, you need to follow the steps below to obtain the optimized model.
|
||||
|
||||
- Step 1: Refer to [document](https://www.paddlepaddle.org.cn/lite/v2.10/user_guides/opt/opt_python.html) to install paddlelite, which is used to convert paddle inference model to paddlelite required for running nb model
|
||||
```
|
||||
pip install paddlelite==2.10 # The paddlelite version should be the same as the prediction library version
|
||||
```
|
||||
After installation, the following commands can view the help information
|
||||
```
|
||||
paddle_lite_opt
|
||||
```
|
||||
|
||||
Introduction to paddle_lite_opt parameters:
|
||||
|
||||
|Options|Description|
|
||||
|---|---|
|
||||
|--model_dir|The path of the PaddlePaddle model to be optimized (non-combined form)|
|
||||
|--model_file|The network structure file path of the PaddlePaddle model (combined form) to be optimized|
|
||||
|--param_file|The weight file path of the PaddlePaddle model (combined form) to be optimized|
|
||||
|--optimize_out_type|Output model type, currently supports two types: protobuf and naive_buffer, among which naive_buffer is a more lightweight serialization/deserialization implementation. If you need to perform model prediction on the mobile side, please set this option to naive_buffer. The default is protobuf|
|
||||
|--optimize_out|The output path of the optimized model|
|
||||
|--valid_targets|The executable backend of the model, the default is arm. Currently it supports x86, arm, opencl, npu, xpu, multiple backends can be specified at the same time (separated by spaces), and Model Optimize Tool will automatically select the best method. If you need to support Huawei NPU (DaVinci architecture NPU equipped with Kirin 810/990 Soc), it should be set to npu, arm|
|
||||
|--record_tailoring_info|When using the function of cutting library files according to the model, set this option to true to record the kernel and OP information contained in the optimized model. The default is false|
|
||||
|
||||
`--model_dir` is suitable for the non-combined mode of the model to be optimized, and the inference model of PaddleOCR is the combined mode, that is, the model structure and model parameters are stored in a single file.
|
||||
|
||||
- Step 2: Use paddle_lite_opt to convert the inference model to the mobile model format.
|
||||
|
||||
The following takes the ultra-lightweight Chinese model of PaddleOCR as an example to introduce the use of the compiled opt file to complete the conversion of the inference model to the Paddle-Lite optimized model
|
||||
|
||||
```
|
||||
# 【[Recommendation] Download the Chinese and English inference model of PP-OCRv3
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_slim_infer.tar && tar xf ch_PP-OCRv3_det_slim_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_slim_infer.tar && tar xf ch_PP-OCRv2_rec_slim_quant_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/slim/ch_ppocr_mobile_v2.0_cls_slim_infer.tar && tar xf ch_ppocr_mobile_v2.0_cls_slim_infer.tar
|
||||
# Convert detection model
|
||||
paddle_lite_opt --model_file=./ch_PP-OCRv3_det_slim_infer/inference.pdmodel --param_file=./ch_PP-OCRv3_det_slim_infer/inference.pdiparams --optimize_out=./ch_PP-OCRv3_det_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
# Convert recognition model
|
||||
paddle_lite_opt --model_file=./ch_PP-OCRv3_rec_slim_infer/inference.pdmodel --param_file=./ch_PP-OCRv3_rec_slim_infer/inference.pdiparams --optimize_out=./ch_PP-OCRv3_rec_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
# Convert angle classifier model
|
||||
paddle_lite_opt --model_file=./ch_ppocr_mobile_v2.0_cls_slim_infer/inference.pdmodel --param_file=./ch_ppocr_mobile_v2.0_cls_slim_infer/inference.pdiparams --optimize_out=./ch_ppocr_mobile_v2.0_cls_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
```
|
||||
|
||||
After the conversion is successful, there will be more files ending with `.nb` in the inference model directory, which is the successfully converted model file.
|
||||
|
||||
<a name="2.2-Run-optimized-model-on-Phone"></a>
|
||||
### 2.2 Run optimized model on Phone
|
||||
|
||||
Some preparatory work is required first.
|
||||
1. Prepare an Android phone with arm8. If the compiled prediction library and opt file are armv7, you need an arm7 phone and modify ARM_ABI = arm7 in the Makefile.
|
||||
2. Make sure the phone is connected to the computer, open the USB debugging option of the phone, and select the file transfer mode.
|
||||
3. Install the adb tool on the computer.
|
||||
|
||||
3.1. Install ADB for MAC:
|
||||
```
|
||||
brew cask install android-platform-tools
|
||||
```
|
||||
3.2. Install ADB for Linux
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install -y wget adb
|
||||
```
|
||||
3.3. Install ADB for windows
|
||||
|
||||
To install on win, you need to go to Google's Android platform to download the adb package for installation:[link](https://developer.android.com/studio)
|
||||
|
||||
Verify whether adb is installed successfully
|
||||
```
|
||||
adb devices
|
||||
```
|
||||
If there is device output, it means the installation is successful。
|
||||
```
|
||||
List of devices attached
|
||||
744be294 device
|
||||
```
|
||||
|
||||
4. Prepare optimized models, prediction library files, test images and dictionary files used.
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
cd PaddleOCR/deploy/lite/
|
||||
# run prepare.sh
|
||||
sh prepare.sh /{lite prediction library path}/inference_lite_lib.android.armv8
|
||||
|
||||
#
|
||||
cd /{lite prediction library path}/inference_lite_lib.android.armv8/
|
||||
cd demo/cxx/ocr/
|
||||
# copy paddle-lite C++ .so file to debug/ directory
|
||||
cp ../../../cxx/lib/libpaddle_light_api_shared.so ./debug/
|
||||
|
||||
cd inference_lite_lib.android.armv8/demo/cxx/ocr/
|
||||
cp ../../../cxx/lib/libpaddle_light_api_shared.so ./debug/
|
||||
```
|
||||
|
||||
Prepare the test image, taking PaddleOCR/doc/imgs/11.jpg as an example, copy the image file to the demo/cxx/ocr/debug/ folder. Prepare the model files optimized by the lite opt tool, ch_PP-OCRv3_det_slim_opt.nb , ch_PP-OCRv3_rec_slim_opt.nb , and place them under the demo/cxx/ocr/debug/ folder.
|
||||
|
||||
The structure of the OCR demo is as follows after the above command is executed:
|
||||
|
||||
```
|
||||
demo/cxx/ocr/
|
||||
|-- debug/
|
||||
| |--ch_PP-OCRv3_det_slim_opt.nb Detection model
|
||||
| |--ch_PP-OCRv3_rec_slim_opt.nb Recognition model
|
||||
| |--ch_ppocr_mobile_v2.0_cls_slim_opt.nb Text direction classification model
|
||||
| |--11.jpg Image for OCR
|
||||
| |--ppocr_keys_v1.txt Dictionary file
|
||||
| |--libpaddle_light_api_shared.so C++ .so file
|
||||
| |--config.txt Config file
|
||||
|-- config.txt Config file
|
||||
|-- cls_process.cc Pre-processing and post-processing files for the angle classifier
|
||||
|-- cls_process.h
|
||||
|-- crnn_process.cc Pre-processing and post-processing files for the CRNN model
|
||||
|-- crnn_process.h
|
||||
|-- db_post_process.cc Pre-processing and post-processing files for the DB model
|
||||
|-- db_post_process.h
|
||||
|-- Makefile
|
||||
|-- ocr_db_crnn.cc C++ main code
|
||||
```
|
||||
|
||||
**Note**:
|
||||
1. `ppocr_keys_v1.txt` is a Chinese dictionary file. If the nb model is used for English recognition or other language recognition, dictionary file should be replaced with a dictionary of the corresponding language. PaddleOCR provides a variety of dictionaries under ppocr/utils/, including:
|
||||
```
|
||||
dict/french_dict.txt # french
|
||||
dict/german_dict.txt # german
|
||||
ic15_dict.txt # english
|
||||
dict/japan_dict.txt # japan
|
||||
dict/korean_dict.txt # korean
|
||||
ppocr_keys_v1.txt # chinese
|
||||
```
|
||||
|
||||
2. `config.txt` of the detector and classifier, as shown below:
|
||||
```
|
||||
max_side_len 960 # Limit the maximum image height and width to 960
|
||||
det_db_thresh 0.3 # Used to filter the binarized image of DB prediction, setting 0.-0.3 has no obvious effect on the result
|
||||
det_db_box_thresh 0.5 # DDB post-processing filter box threshold, if there is a missing box detected, it can be reduced as appropriate
|
||||
det_db_unclip_ratio 1.6 # Indicates the compactness of the text box, the smaller the value, the closer the text box to the text
|
||||
use_direction_classify 0 # Whether to use the direction classifier, 0 means not to use, 1 means to use
|
||||
rec_image_height 48 # The height of the input image of the recognition model, the PP-OCRv3 model needs to be set to 48, and the PP-OCRv2 model needs to be set to 32
|
||||
```
|
||||
|
||||
5. Run Model on phone
|
||||
|
||||
After the above steps are completed, you can use adb to push the file to the phone to run, the steps are as follows:
|
||||
|
||||
```
|
||||
# Execute the compilation and get the executable file ocr_db_crnn
|
||||
# The first execution of this command will download dependent libraries such as opencv. After the download is complete, you need to execute it again
|
||||
make -j
|
||||
# Move the compiled executable file to the debug folder
|
||||
mv ocr_db_crnn ./debug/
|
||||
# Push the debug folder to the phone
|
||||
adb push debug /data/local/tmp/
|
||||
adb shell
|
||||
cd /data/local/tmp/debug
|
||||
export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH
|
||||
# The use of ocr_db_crnn is:
|
||||
# ./ocr_db_crnn Mode Detection model file Orientation classifier model file Recognition model file Hardware Precision Threads Batchsize Test image path Dictionary file path
|
||||
./ocr_db_crnn system ch_PP-OCRv3_det_slim_opt.nb ch_PP-OCRv3_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True
|
||||
# precision can be INT8 for quantitative model or FP32 for normal model.
|
||||
|
||||
# Only using detection model
|
||||
./ocr_db_crnn det ch_PP-OCRv3_det_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt
|
||||
|
||||
# Only using recognition model
|
||||
./ocr_db_crnn rec ch_PP-OCRv3_rec_slim_opt.nb arm8 INT8 10 1 word_1.jpg ppocr_keys_v1.txt config.txt
|
||||
```
|
||||
|
||||
If you modify the code, you need to recompile and push to the phone.
|
||||
|
||||
The outputs are as follows:
|
||||
|
||||
<div align="center">
|
||||
<img src="imgs/lite_demo.png" width="600">
|
||||
</div>
|
||||
|
||||
## FAQ
|
||||
|
||||
Q1: What if I want to change the model, do I need to run it again according to the process?
|
||||
|
||||
A1: If you have performed the above steps, you only need to replace the .nb model file to complete the model replacement.
|
||||
|
||||
Q2: How to test with another picture?
|
||||
|
||||
A2: Replace the .jpg test image under ./debug with the image you want to test, and run adb push to push new image to the phone.
|
||||
|
||||
Q3: How to package it into the mobile APP?
|
||||
|
||||
A3: This demo aims to provide the core algorithm part that can run OCR on mobile phones. Further, PaddleOCR/deploy/android_demo is an example of encapsulating this demo into a mobile app for reference.
|
||||
|
||||
Q4: When running the demo, an error is reported `Error: This model is not supported, because kernel for 'io_copy' is not supported by Paddle-Lite.`
|
||||
|
||||
A4: The problem is that the installed paddlelite version does not match the downloaded prediction library version. Make sure that the paddleliteopt tool matches your prediction library version, and try to switch to the nb model again.
|
||||
297
deploy/lite/readme_ch.md
Normal file
297
deploy/lite/readme_ch.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# 端侧部署
|
||||
|
||||
- [1. 准备环境](#1-准备环境)
|
||||
- [运行准备](#运行准备)
|
||||
- [1.1 准备交叉编译环境](#11-准备交叉编译环境)
|
||||
- [1.2 准备预测库](#12-准备预测库)
|
||||
- [2 开始运行](#2-开始运行)
|
||||
- [2.1 模型优化](#21-模型优化)
|
||||
- [2.2 与手机联调](#22-与手机联调)
|
||||
- [FAQ](#faq)
|
||||
|
||||
|
||||
本教程将介绍基于[Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 在移动端部署PaddleOCR超轻量中文检测、识别模型的详细步骤。
|
||||
|
||||
Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理能力,并广泛整合跨平台硬件,为端侧部署及应用落地问题提供轻量化的部署方案。
|
||||
|
||||
|
||||
## 1. 准备环境
|
||||
|
||||
### 运行准备
|
||||
- 电脑(编译Paddle Lite)
|
||||
- 安卓手机(armv7或armv8)
|
||||
|
||||
### 1.1 准备交叉编译环境
|
||||
交叉编译环境用于编译 Paddle Lite 和 PaddleOCR 的C++ demo。
|
||||
支持多种开发环境,不同开发环境的编译流程请参考对应文档。
|
||||
|
||||
1. [Docker](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#docker)
|
||||
2. [Linux](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#linux)
|
||||
3. [MAC OS](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#mac-os)
|
||||
|
||||
### 1.2 准备预测库
|
||||
|
||||
预测库有两种获取方式:
|
||||
- 1. [推荐]直接下载,预测库下载链接如下:
|
||||
|
||||
| 平台 | 预测库下载链接 |
|
||||
|---|---|
|
||||
|Android|[arm7](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.android.armv7.gcc.c++_shared.with_extra.with_cv.tar.gz) / [arm8](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.android.armv8.gcc.c++_shared.with_extra.with_cv.tar.gz)|
|
||||
|IOS|[arm7](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.ios.armv7.with_cv.with_extra.with_log.tiny_publish.tar.gz) / [arm8](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10/inference_lite_lib.ios.armv8.with_cv.with_extra.with_log.tiny_publish.tar.gz)|
|
||||
|
||||
注:1. 上述预测库为PaddleLite 2.10分支编译得到,有关PaddleLite 2.10 详细信息可参考 [链接](https://github.com/PaddlePaddle/Paddle-Lite/releases/tag/v2.10) 。
|
||||
|
||||
**注:建议使用paddlelite>=2.10版本的预测库,其他预测库版本[下载链接](https://github.com/PaddlePaddle/Paddle-Lite/tags)**
|
||||
|
||||
- 2. 编译Paddle-Lite得到预测库,Paddle-Lite的编译方式如下:
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
|
||||
cd Paddle-Lite
|
||||
# 切换到Paddle-Lite release/v2.10 稳定分支
|
||||
git checkout release/v2.10
|
||||
./lite/tools/build_android.sh --arch=armv8 --with_cv=ON --with_extra=ON
|
||||
```
|
||||
|
||||
注意:编译Paddle-Lite获得预测库时,需要打开`--with_cv=ON --with_extra=ON`两个选项,`--arch`表示`arm`版本,这里指定为armv8,
|
||||
更多编译命令
|
||||
介绍请参考 [链接](https://paddle-lite.readthedocs.io/zh/release-v2.10_a/source_compile/linux_x86_compile_android.html) 。
|
||||
|
||||
直接下载预测库并解压后,可以得到`inference_lite_lib.android.armv8/`文件夹,通过编译Paddle-Lite得到的预测库位于
|
||||
`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/`文件夹下。
|
||||
预测库的文件目录如下:
|
||||
```
|
||||
inference_lite_lib.android.armv8/
|
||||
|-- cxx C++ 预测库和头文件
|
||||
| |-- include C++ 头文件
|
||||
| | |-- paddle_api.h
|
||||
| | |-- paddle_image_preprocess.h
|
||||
| | |-- paddle_lite_factory_helper.h
|
||||
| | |-- paddle_place.h
|
||||
| | |-- paddle_use_kernels.h
|
||||
| | |-- paddle_use_ops.h
|
||||
| | `-- paddle_use_passes.h
|
||||
| `-- lib C++预测库
|
||||
| |-- libpaddle_api_light_bundled.a C++静态库
|
||||
| `-- libpaddle_light_api_shared.so C++动态库
|
||||
|-- java Java预测库
|
||||
| |-- jar
|
||||
| | `-- PaddlePredictor.jar
|
||||
| |-- so
|
||||
| | `-- libpaddle_lite_jni.so
|
||||
| `-- src
|
||||
|-- demo C++和Java示例代码
|
||||
| |-- cxx C++ 预测库demo
|
||||
| `-- java Java 预测库demo
|
||||
```
|
||||
|
||||
## 2 开始运行
|
||||
|
||||
### 2.1 模型优化
|
||||
|
||||
Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括量化、子图融合、混合调度、Kernel优选等方法,使用Paddle-lite的opt工具可以自动
|
||||
对inference模型进行优化,优化后的模型更轻量,模型运行速度更快。
|
||||
|
||||
如果已经准备好了 `.nb` 结尾的模型文件,可以跳过此步骤。
|
||||
|
||||
下述表格中也提供了一系列中文移动端模型:
|
||||
|
||||
|模型版本|模型简介|模型大小|检测模型|文本方向分类模型|识别模型|Paddle-Lite版本|
|
||||
|---|---|---|---|---|---|---|
|
||||
|PP-OCRv3|蒸馏版超轻量中文OCR移动端模型|16.2M|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/PP-OCRv3_mobile_det_infer.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_infer_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.nb)|v2.10|
|
||||
|PP-OCRv3(slim)|蒸馏版超轻量中文OCR移动端模型|5.9M|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_slim_infer.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_slim_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_slim_infer.nb)|v2.10|
|
||||
|PP-OCRv2|蒸馏版超轻量中文OCR移动端模型|11M|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_det_infer_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_infer_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_rec_infer_opt.nb)|v2.10|
|
||||
|PP-OCRv2(slim)|蒸馏版超轻量中文OCR移动端模型|4.6M|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_det_slim_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_ppocr_mobile_v2.0_cls_slim_opt.nb)|[下载地址](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2_rec_slim_opt.nb)|v2.10|
|
||||
|
||||
如果直接使用上述表格中的模型进行部署,可略过下述步骤,直接阅读 [2.2节](#2.2与手机联调)。
|
||||
|
||||
如果要部署的模型不在上述表格中,则需要按照如下步骤获得优化后的模型。
|
||||
|
||||
- 步骤1:参考[文档](https://www.paddlepaddle.org.cn/lite/v2.10/user_guides/opt/opt_python.html)安装paddlelite,用于转换paddle inference model为paddlelite运行所需的nb模型
|
||||
```
|
||||
pip install paddlelite==2.10 # paddlelite版本要与预测库版本一致
|
||||
```
|
||||
安装完后,如下指令可以查看帮助信息
|
||||
```
|
||||
paddle_lite_opt
|
||||
```
|
||||
|
||||
paddle_lite_opt 参数介绍:
|
||||
|选项|说明|
|
||||
|---|---|
|
||||
|--model_dir|待优化的PaddlePaddle模型(非combined形式)的路径|
|
||||
|--model_file|待优化的PaddlePaddle模型(combined形式)的网络结构文件路径|
|
||||
|--param_file|待优化的PaddlePaddle模型(combined形式)的权重文件路径|
|
||||
|--optimize_out_type|输出模型类型,目前支持两种类型:protobuf和naive_buffer,其中naive_buffer是一种更轻量级的序列化/反序列化实现。若您需要在mobile端执行模型预测,请将此选项设置为naive_buffer。默认为protobuf|
|
||||
|--optimize_out|优化模型的输出路径|
|
||||
|--valid_targets|指定模型可执行的backend,默认为arm。目前可支持x86、arm、opencl、npu、xpu,可以同时指定多个backend(以空格分隔),Model Optimize Tool将会自动选择最佳方式。如果需要支持华为NPU(Kirin 810/990 Soc搭载的达芬奇架构NPU),应当设置为npu, arm|
|
||||
|--record_tailoring_info|当使用 根据模型裁剪库文件 功能时,则设置该选项为true,以记录优化后模型含有的kernel和OP信息,默认为false|
|
||||
|
||||
`--model_dir`适用于待优化的模型是非combined方式,PaddleOCR的inference模型是combined方式,即模型结构和模型参数使用单独一个文件存储。
|
||||
|
||||
- 步骤2:使用paddle_lite_opt将inference模型转换成移动端模型格式。
|
||||
|
||||
下面以PaddleOCR的超轻量中文模型为例,介绍使用编译好的opt文件完成inference模型到Paddle-Lite优化模型的转换。
|
||||
|
||||
```
|
||||
# 【推荐】 下载 PP-OCRv3版本的中英文 inference模型
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_slim_infer.tar && tar xf ch_PP-OCRv3_det_slim_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_slim_infer.tar && tar xf ch_PP-OCRv2_rec_slim_quant_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/slim/ch_ppocr_mobile_v2.0_cls_slim_infer.tar && tar xf ch_ppocr_mobile_v2.0_cls_slim_infer.tar
|
||||
# 转换检测模型
|
||||
paddle_lite_opt --model_file=./ch_PP-OCRv3_det_slim_infer/inference.pdmodel --param_file=./ch_PP-OCRv3_det_slim_infer/inference.pdiparams --optimize_out=./ch_PP-OCRv3_det_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
# 转换识别模型
|
||||
paddle_lite_opt --model_file=./ch_PP-OCRv3_rec_slim_infer/inference.pdmodel --param_file=./ch_PP-OCRv3_rec_slim_infer/inference.pdiparams --optimize_out=./ch_PP-OCRv3_rec_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
# 转换方向分类器模型
|
||||
paddle_lite_opt --model_file=./ch_ppocr_mobile_v2.0_cls_slim_infer/inference.pdmodel --param_file=./ch_ppocr_mobile_v2.0_cls_slim_infer/inference.pdiparams --optimize_out=./ch_ppocr_mobile_v2.0_cls_slim_opt --valid_targets=arm --optimize_out_type=naive_buffer
|
||||
|
||||
```
|
||||
|
||||
转换成功后,inference模型目录下会多出`.nb`结尾的文件,即是转换成功的模型文件。
|
||||
|
||||
注意:使用paddle-lite部署时,需要使用opt工具优化后的模型。 opt工具的输入模型是paddle保存的inference模型
|
||||
|
||||
<a name="2.2与手机联调"></a>
|
||||
### 2.2 与手机联调
|
||||
|
||||
首先需要进行一些准备工作。
|
||||
1. 准备一台arm8的安卓手机,如果编译的预测库和opt文件是armv7,则需要arm7的手机,并修改Makefile中`ARM_ABI = arm7`。
|
||||
2. 打开手机的USB调试选项,选择文件传输模式,连接电脑。
|
||||
3. 电脑上安装adb工具,用于调试。 adb安装方式如下:
|
||||
|
||||
3.1. MAC电脑安装ADB:
|
||||
```
|
||||
brew cask install android-platform-tools
|
||||
```
|
||||
3.2. Linux安装ADB
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install -y wget adb
|
||||
```
|
||||
3.3. Window安装ADB
|
||||
|
||||
win上安装需要去谷歌的安卓平台下载adb软件包进行安装:[链接](https://developer.android.com/studio)
|
||||
|
||||
打开终端,手机连接电脑,在终端中输入
|
||||
```
|
||||
adb devices
|
||||
```
|
||||
如果有device输出,则表示安装成功。
|
||||
```
|
||||
List of devices attached
|
||||
744be294 device
|
||||
```
|
||||
|
||||
4. 准备优化后的模型、预测库文件、测试图像和使用的字典文件。
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
cd PaddleOCR/deploy/lite/
|
||||
# 运行prepare.sh,准备预测库文件、测试图像和使用的字典文件,并放置在预测库中的demo/cxx/ocr文件夹下
|
||||
sh prepare.sh /{lite prediction library path}/inference_lite_lib.android.armv8
|
||||
|
||||
# 进入OCR demo的工作目录
|
||||
cd /{lite prediction library path}/inference_lite_lib.android.armv8/
|
||||
cd demo/cxx/ocr/
|
||||
# 将C++预测动态库so文件复制到debug文件夹中
|
||||
cp ../../../cxx/lib/libpaddle_light_api_shared.so ./debug/
|
||||
```
|
||||
|
||||
准备测试图像,以`PaddleOCR/doc/imgs/11.jpg`为例,将测试的图像复制到`demo/cxx/ocr/debug/`文件夹下。
|
||||
准备lite opt工具优化后的模型文件,比如使用`ch_PP-OCRv3_det_slim_opt.ch_PP-OCRv3_rec_slim_rec.nb, ch_ppocr_mobile_v2.0_cls_slim_opt.nb`,模型文件放置在`demo/cxx/ocr/debug/`文件夹下。
|
||||
|
||||
执行完成后,ocr文件夹下将有如下文件格式:
|
||||
|
||||
```
|
||||
demo/cxx/ocr/
|
||||
|-- debug/
|
||||
| |--ch_PP-OCRv3_det_slim_opt.nb 优化后的检测模型文件
|
||||
| |--ch_PP-OCRv3_rec_slim_opt.nb 优化后的识别模型文件
|
||||
| |--ch_ppocr_mobile_v2.0_cls_slim_opt.nb 优化后的文字方向分类器模型文件
|
||||
| |--11.jpg 待测试图像
|
||||
| |--ppocr_keys_v1.txt 中文字典文件
|
||||
| |--libpaddle_light_api_shared.so C++预测库文件
|
||||
| |--config.txt 超参数配置
|
||||
|-- config.txt 超参数配置
|
||||
|-- cls_process.cc 方向分类器的预处理和后处理文件
|
||||
|-- cls_process.h
|
||||
|-- crnn_process.cc 识别模型CRNN的预处理和后处理文件
|
||||
|-- crnn_process.h
|
||||
|-- db_post_process.cc 检测模型DB的后处理文件
|
||||
|-- db_post_process.h
|
||||
|-- Makefile 编译文件
|
||||
|-- ocr_db_crnn.cc C++预测源文件
|
||||
```
|
||||
|
||||
#### 注意:
|
||||
1. ppocr_keys_v1.txt是中文字典文件,如果使用的 nb 模型是英文数字或其他语言的模型,需要更换为对应语言的字典。
|
||||
PaddleOCR 在ppocr/utils/下存放了多种字典,包括:
|
||||
```
|
||||
dict/french_dict.txt # 法语字典
|
||||
dict/german_dict.txt # 德语字典
|
||||
ic15_dict.txt # 英文字典
|
||||
dict/japan_dict.txt # 日语字典
|
||||
dict/korean_dict.txt # 韩语字典
|
||||
ppocr_keys_v1.txt # 中文字典
|
||||
...
|
||||
```
|
||||
|
||||
2. `config.txt` 包含了检测器、分类器、识别器的超参数,如下:
|
||||
```
|
||||
max_side_len 960 # 输入图像长宽大于960时,等比例缩放图像,使得图像最长边为960
|
||||
det_db_thresh 0.3 # 用于过滤DB预测的二值化图像,设置为0.-0.3对结果影响不明显
|
||||
det_db_box_thresh 0.5 # 检测器后处理过滤box的阈值,如果检测存在漏框情况,可酌情减小
|
||||
det_db_unclip_ratio 1.6 # 表示文本框的紧致程度,越小则文本框更靠近文本
|
||||
use_direction_classify 0 # 是否使用方向分类器,0表示不使用,1表示使用
|
||||
rec_image_height 48 # 识别模型输入图像的高度,PP-OCRv3模型设置为48,PP-OCRv2模型需要设置为32
|
||||
```
|
||||
|
||||
5. 启动调试
|
||||
|
||||
上述步骤完成后就可以使用adb将文件push到手机上运行,步骤如下:
|
||||
|
||||
```
|
||||
# 执行编译,得到可执行文件ocr_db_crnn, 第一次执行此命令会下载opencv等依赖库,下载完成后,需要再执行一次
|
||||
make -j
|
||||
|
||||
# 将编译的可执行文件移动到debug文件夹中
|
||||
mv ocr_db_crnn ./debug/
|
||||
# 将debug文件夹push到手机上
|
||||
adb push debug /data/local/tmp/
|
||||
adb shell
|
||||
cd /data/local/tmp/debug
|
||||
export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH
|
||||
# 开始使用,ocr_db_crnn可执行文件的使用方式为:
|
||||
# ./ocr_db_crnn 预测模式 检测模型文件 方向分类器模型文件 识别模型文件 运行硬件 运行精度 线程数 batchsize 测试图像路径 参数配置路径 字典文件路径 是否使用benchmark参数
|
||||
./ocr_db_crnn system ch_PP-OCRv3_det_slim_opt.nb ch_PP-OCRv3_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True
|
||||
|
||||
# 仅使用文本检测模型,使用方式如下:
|
||||
./ocr_db_crnn det ch_PP-OCRv3_det_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt
|
||||
|
||||
# 仅使用文本识别模型,使用方式如下:
|
||||
./ocr_db_crnn rec ch_PP-OCRv3_rec_slim_opt.nb arm8 INT8 10 1 word_1.jpg ppocr_keys_v1.txt config.txt
|
||||
```
|
||||
|
||||
如果对代码做了修改,则需要重新编译并push到手机上。
|
||||
|
||||
运行效果如下:
|
||||
|
||||
<div align="center">
|
||||
<img src="imgs/lite_demo.png" width="600">
|
||||
</div>
|
||||
|
||||
|
||||
## FAQ
|
||||
Q1:如果想更换模型怎么办,需要重新按照流程走一遍吗?
|
||||
|
||||
A1:如果已经走通了上述步骤,更换模型只需要替换 .nb 模型文件即可,同时要注意更新字典
|
||||
|
||||
Q2:换一个图测试怎么做?
|
||||
|
||||
A2:替换debug下的.jpg测试图像为你想要测试的图像,adb push 到手机上即可
|
||||
|
||||
Q3:如何封装到手机APP中?
|
||||
|
||||
A3:此demo旨在提供能在手机上运行OCR的核心算法部分,PaddleOCR/deploy/android_demo是将这个demo封装到手机app的示例,供参考
|
||||
|
||||
Q4:运行demo时遇到报错`Error: This model is not supported, because kernel for 'io_copy' is not supported by Paddle-Lite.`
|
||||
|
||||
A4:问题是安装的paddlelite版本和下载的预测库版本不匹配,确保paddleliteopt工具和你的预测库版本匹配,重新转nb模型试试。
|
||||
Reference in New Issue
Block a user