diff --git a/Dockerfile b/Dockerfile index 0c73e6d..49bf957 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,10 @@ FROM python:3.7-slim - -RUN sed -i 's#http://deb.debian.org#https://mirrors.163.com#g' /etc/apt/sources.list -RUN apt update && apt install -y libglib2.0-dev libsm6 libxrender1 libxext-dev supervisor build-essential \ - && rm -rf /var/lib/apt/lists/* - -RUN /usr/local/bin/python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple -COPY ./requirements.txt ./TrWebOCR/ -RUN pip3 install -r ./TrWebOCR/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ COPY . ./TrWebOCR - -RUN python3 ./TrWebOCR/install.py +RUN sed -i 's#http://deb.debian.org#https://mirrors.163.com#g' /etc/apt/sources.list \ + && apt update && apt install -y libglib2.0-dev libsm6 libxrender1 libxext-dev supervisor build-essential \ + && rm -rf /var/lib/apt/lists/* \ + && /usr/local/bin/python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple \ + && pip3 install -r ./TrWebOCR/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ \ + && python3 ./TrWebOCR/install.py EXPOSE 8089 CMD ["supervisord","-c","/TrWebOCR/supervisord.conf"] diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index 14e06b5..50a4e61 100644 --- a/README.md +++ b/README.md @@ -117,15 +117,18 @@ res = requests.post(url=url, data={'img': img_b64}) ![验证码识别](https://images.alisen39.com/20200501173211.png) ## 更新记录 +* 2022年01月23日 + 更新tr2.3.1版模型 + > 模型本身支持多线程了~~现在直接可以打满CPU了!所有核! +* 2022年01月16日 + 更新接口,增加不返回图片参数 + * 2020年08月17日 更新Dockerfile,docker镜像支持tr2.3 * 2020年07月30日 支持启动命令选择GPU/CPU -* 2020年07月26日 - 更新tr2.0版,支持GPU - [更多记录 >>>](https://github.com/alisen39/TrWebOCR/blob/master/updateHistory.md) diff --git a/backend/tools/version_map.txt b/backend/tools/version_map.txt index c61cfab..e860cdd 100644 --- a/backend/tools/version_map.txt +++ b/backend/tools/version_map.txt @@ -1 +1 @@ -{"c08afbf0f98626d4dbf7a5cf616c9582558edf602477c3374bde8377e3f1b30c13510942f91169ca7e379aef7fcf3f2b2754f685375645c5eb8e158cc1023cf2": "gpu", "14c25201ca9ccbc8e9a8a565beaf676fc3da2faa93d7ed2e75f6c7dc947a180cd6aa89fbaf83076fa973805a218ac4838a2daf849f2cc9abd679cbee0db83350": "cpu"} \ No newline at end of file +{"f6fdb880e3da689a45bc9d53446707f175400bad46a15bea5740f8d7cb4e004d13510942f91169ca7e379aef7fcf3f2b2754f685375645c5eb8e158cc1023cf2": "gpu", "1a8f8da33c1c29f679aaa897ebdfc6b517b6e645d51012890745fe97db79ddf9d6aa89fbaf83076fa973805a218ac4838a2daf849f2cc9abd679cbee0db83350": "cpu"} \ No newline at end of file diff --git a/backend/tr/char_table.txt b/backend/tr/char_table.txt old mode 100644 new mode 100755 diff --git a/backend/tr/crnn.bin b/backend/tr/crnn.bin old mode 100644 new mode 100755 diff --git a/backend/tr/ctpn.bin b/backend/tr/ctpn.bin old mode 100644 new mode 100755 diff --git a/backend/tr/libtr.so b/backend/tr/libtr.so index 0d337d0..6f84243 100755 Binary files a/backend/tr/libtr.so and b/backend/tr/libtr.so differ diff --git a/backend/tr/tr.py b/backend/tr/tr.py index 966a8a6..3ec1288 100755 --- a/backend/tr/tr.py +++ b/backend/tr/tr.py @@ -1,248 +1,252 @@ -# coding: utf-8 -import os -import platform -import ctypes -import numpy as np - -try: - unichr -except NameError: - unichr = chr - -CV_8UC1 = 0 -CV_32FC1 = 5 -CV_8UC3 = 16 -CV_32FC3 = 21 - -FLAG_RECT = (1 << 0) -FLAG_ROTATED_RECT = (1 << 1) - -ORT_DISABLE_ALL = 0 -ORT_ENABLE_BASIC = 1 -ORT_ENABLE_EXTENDED = 2 -ORT_ENABLE_ALL = 99 - -RECT_SIZE = 6 -ORT_SIZE = 256 -_BASEDIR = os.path.dirname(os.path.abspath(__file__)) - -if platform.system() == "Windows": - raise NotImplementedError() -else: - _libc = ctypes.cdll.LoadLibrary(os.path.join(_BASEDIR, 'libtr.so')) -assert _libc is not None - -_libc.tr_init.argtypes = ( - ctypes.c_int, - ctypes.c_int, - ctypes.c_void_p, - ctypes.c_void_p -) - -_libc.tr_release.argtypes = (ctypes.c_int,) - -_libc.tr_detect.restype = ctypes.c_int -_libc.tr_detect.argtypes = ( - ctypes.c_int, - ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, - ctypes.c_void_p, ctypes.c_int -) - -_libc.tr_recognize.restype = ctypes.c_int -_libc.tr_recognize.argtypes = ( - ctypes.c_int, - ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_int -) - -_libc.tr_run.restype = ctypes.c_int -_libc.tr_run.argtypes = ( - ctypes.c_int, ctypes.c_int, - ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, - ctypes.c_void_p, ctypes.c_int, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_int -) - -_libc.tr_crnn.restype = ctypes.c_int -_libc.tr_crnn.argtypes = ( - ctypes.c_int, - ctypes.c_void_p, ctypes.c_int, ctypes.c_int, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_int -) - -def c_ptr(arr): - if not isinstance(arr, (np.ndarray, str)): - arr = np.array(arr) - - if isinstance(arr, np.ndarray): - assert arr.flags['C_CONTIGUOUS'] - return np.ctypeslib.as_ctypes(arr) - elif isinstance(arr, str): - return ctypes.create_string_buffer(arr.encode()) - else: - raise NotImplementedError() - - -def c_img(arr): - if not isinstance(arr, (np.ndarray, str)): - arr = np.array(arr) - - if isinstance(arr, str): - return c_ptr(arr), 0, 0, 0 - elif isinstance(arr, np.ndarray): - res = [None, 0, 0, 0] - res[0] = c_ptr(arr) - res[1] = arr.shape[0] - res[2] = arr.shape[1] - - channel = 0 - if arr.ndim == 2: - channel = 1 - elif arr.ndim == 3: - channel = arr.shape[2] - - if channel not in [1, 3]: - raise NotImplementedError() - - if arr.dtype == np.uint8: - res[3] = CV_8UC3 if channel == 3 else CV_8UC1 - elif arr.dtype == np.float32: - res[3] = CV_32FC3 if channel == 3 else CV_32FC1 - - return tuple(res) - else: - raise NotImplementedError() - - -def init(pid, id, model, arg=None): - """ - :param pid: process id - :param id: session id - :param model: model path - :param arg: extra arguments - :return: None - """ - _cwd = os.getcwd() - os.chdir(_BASEDIR) - - _libc.tr_init(pid, id, c_ptr(model), arg) - - os.chdir(_cwd) - - -def _parse(unicode_arr, prob_arr, num): - txt = "" - prob = 0. - unicode_pre = -1 - count = 0 - for pos in range(num): - unicode = unicode_arr[pos] - if unicode >= 0: - if unicode != unicode_pre: - txt += unichr(unicode) - - count += 1 - prob += prob_arr[pos] - - unicode_pre = unicode - - return txt, float(prob / max(count, 1)) - - -def crnn(img, max_items=512*7000, crnn_id=1): - buf_arr = np.zeros((max_items,), dtype="float32") - shape_arr = np.zeros((8,), dtype="int32") - img = c_img(img) - - assert img[3] == CV_32FC1 - assert img[1] == 32 - - num = _libc.tr_crnn( - crnn_id, - img[0], img[1], img[2], - c_ptr(buf_arr), - c_ptr(shape_arr), - max_items - ) - - buf_arr = buf_arr[:num] - return buf_arr.reshape(shape_arr[0], shape_arr[2]) - - -def recognize(img, max_width=512, crnn_id=1): - unicode_arr = np.zeros((max_width,), dtype="int32") - prob_arr = np.zeros((max_width,), dtype="float32") - img = c_img(img) - num = _libc.tr_recognize( - crnn_id, - img[0], img[1], img[2], img[3], - c_ptr(unicode_arr), - c_ptr(prob_arr), - max_width - ) - - return _parse(unicode_arr, prob_arr, num) - - -def detect(img, max_lines=512, flag=FLAG_ROTATED_RECT, ctpn_id=0): - rect_arr = np.zeros((max_lines, RECT_SIZE), dtype="float32") - img = c_img(img) - num = _libc.tr_detect( - ctpn_id, - img[0], img[1], img[2], img[3], - flag, - c_ptr(rect_arr), - max_lines - ) - - return rect_arr[:num, :5].tolist() - - -def release(*args): - for arg in args: - _libc.tr_release(arg) - - -def run(img, - max_lines=512, - flag=FLAG_ROTATED_RECT, - max_width=512, - ctpn_id=0, - crnn_id=1): - rect_arr = np.zeros((max_lines, RECT_SIZE), dtype="float32") - unicode_arr = np.zeros((max_lines, max_width), dtype="int32") - prob_arr = np.zeros((max_lines, max_width), dtype="float32") - img = c_img(img) - line_num = _libc.tr_run( - ctpn_id, crnn_id, - img[0], img[1], img[2], img[3], - flag, - c_ptr(rect_arr), - max_lines, - c_ptr(unicode_arr), - c_ptr(prob_arr), - max_width - ) - - results = [] - for i in range(line_num): - num = int(rect_arr[i][-1] + 0.5) - txt, confidence = _parse(unicode_arr[i], prob_arr[i], num) - results.append((rect_arr[i][:5].tolist(), txt, confidence)) - - return results - - -init(0, 0, "ctpn.bin") -init(0, 1, "crnn.bin") - -if __name__ == "__main__": - pass +# coding: utf-8 +import os +import platform +import ctypes +import numpy as np + +try: + unichr +except NameError: + unichr = chr + +CV_8UC1 = 0 +CV_32FC1 = 5 +CV_8UC3 = 16 +CV_32FC3 = 21 + +FLAG_RECT = (1 << 0) +FLAG_ROTATED_RECT = (1 << 1) + +ORT_DISABLE_ALL = 0 +ORT_ENABLE_BASIC = 1 +ORT_ENABLE_EXTENDED = 2 +ORT_ENABLE_ALL = 99 + +RECT_SIZE = 6 +ORT_SIZE = 256 +_BASEDIR = os.path.dirname(os.path.abspath(__file__)) + +_cwd = os.getcwd() +os.chdir(_BASEDIR) + +if platform.system() == "Windows": + raise NotImplementedError() +else: + _libc = ctypes.cdll.LoadLibrary(os.path.join(_BASEDIR, 'libtr.so')) +assert _libc is not None +os.chdir(_cwd) + +_libc.tr_init.argtypes = ( + ctypes.c_int, + ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p +) + +_libc.tr_release.argtypes = (ctypes.c_int,) + +_libc.tr_detect.restype = ctypes.c_int +_libc.tr_detect.argtypes = ( + ctypes.c_int, + ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, + ctypes.c_int, + ctypes.c_void_p, ctypes.c_int +) + +_libc.tr_recognize.restype = ctypes.c_int +_libc.tr_recognize.argtypes = ( + ctypes.c_int, + ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_int +) + +_libc.tr_run.restype = ctypes.c_int +_libc.tr_run.argtypes = ( + ctypes.c_int, ctypes.c_int, + ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, + ctypes.c_int, + ctypes.c_void_p, ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_int +) + +_libc.tr_crnn.restype = ctypes.c_int +_libc.tr_crnn.argtypes = ( + ctypes.c_int, + ctypes.c_void_p, ctypes.c_int, ctypes.c_int, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_int +) + +def c_ptr(arr): + if not isinstance(arr, (np.ndarray, str)): + arr = np.array(arr) + + if isinstance(arr, np.ndarray): + assert arr.flags['C_CONTIGUOUS'] + return np.ctypeslib.as_ctypes(arr) + elif isinstance(arr, str): + return ctypes.create_string_buffer(arr.encode()) + else: + raise NotImplementedError() + + +def c_img(arr): + if not isinstance(arr, (np.ndarray, str)): + arr = np.array(arr) + + if isinstance(arr, str): + return c_ptr(arr), 0, 0, 0 + elif isinstance(arr, np.ndarray): + res = [None, 0, 0, 0] + res[0] = c_ptr(arr) + res[1] = arr.shape[0] + res[2] = arr.shape[1] + + channel = 0 + if arr.ndim == 2: + channel = 1 + elif arr.ndim == 3: + channel = arr.shape[2] + + if channel not in [1, 3]: + raise NotImplementedError() + + if arr.dtype == np.uint8: + res[3] = CV_8UC3 if channel == 3 else CV_8UC1 + elif arr.dtype == np.float32: + res[3] = CV_32FC3 if channel == 3 else CV_32FC1 + + return tuple(res) + else: + raise NotImplementedError() + + +def init(pid, id, model, arg=None): + """ + :param pid: process id + :param id: session id + :param model: model path + :param arg: extra arguments + :return: None + """ + _cwd = os.getcwd() + os.chdir(_BASEDIR) + + _libc.tr_init(pid, id, c_ptr(model), arg) + + os.chdir(_cwd) + + +def _parse(unicode_arr, prob_arr, num): + txt = "" + prob = 0. + unicode_pre = -1 + count = 0 + for pos in range(num): + unicode = unicode_arr[pos] + if unicode >= 0: + if unicode != unicode_pre: + txt += unichr(unicode) + + count += 1 + prob += prob_arr[pos] + + unicode_pre = unicode + + return txt, float(prob / max(count, 1)) + + +def crnn(img, max_items=512*7000, crnn_id=1): + buf_arr = np.zeros((max_items,), dtype="float32") + shape_arr = np.zeros((8,), dtype="int32") + img = c_img(img) + + assert img[3] == CV_32FC1 + assert img[1] == 32 + + num = _libc.tr_crnn( + crnn_id, + img[0], img[1], img[2], + c_ptr(buf_arr), + c_ptr(shape_arr), + max_items + ) + + buf_arr = buf_arr[:num] + return buf_arr.reshape(shape_arr[0], shape_arr[2]) + + +def recognize(img, max_width=512, crnn_id=1): + unicode_arr = np.zeros((max_width,), dtype="int32") + prob_arr = np.zeros((max_width,), dtype="float32") + img = c_img(img) + num = _libc.tr_recognize( + crnn_id, + img[0], img[1], img[2], img[3], + c_ptr(unicode_arr), + c_ptr(prob_arr), + max_width + ) + + return _parse(unicode_arr, prob_arr, num) + + +def detect(img, max_lines=512, flag=FLAG_ROTATED_RECT, ctpn_id=0): + rect_arr = np.zeros((max_lines, RECT_SIZE), dtype="float32") + img = c_img(img) + num = _libc.tr_detect( + ctpn_id, + img[0], img[1], img[2], img[3], + flag, + c_ptr(rect_arr), + max_lines + ) + + return rect_arr[:num, :5].tolist() + + +def release(*args): + for arg in args: + _libc.tr_release(arg) + + +def run(img, + max_lines=512, + flag=FLAG_ROTATED_RECT, + max_width=512, + ctpn_id=0, + crnn_id=1): + rect_arr = np.zeros((max_lines, RECT_SIZE), dtype="float32") + unicode_arr = np.zeros((max_lines, max_width), dtype="int32") + prob_arr = np.zeros((max_lines, max_width), dtype="float32") + img = c_img(img) + line_num = _libc.tr_run( + ctpn_id, crnn_id, + img[0], img[1], img[2], img[3], + flag, + c_ptr(rect_arr), + max_lines, + c_ptr(unicode_arr), + c_ptr(prob_arr), + max_width + ) + + results = [] + for i in range(line_num): + num = int(rect_arr[i][-1] + 0.5) + txt, confidence = _parse(unicode_arr[i], prob_arr[i], num) + results.append((rect_arr[i][:5].tolist(), txt, confidence)) + + return results + + +init(0, 0, "ctpn.bin") +init(0, 1, "crnn.bin") + +if __name__ == "__main__": + pass diff --git a/backend/tr_cpu/libtr.so b/backend/tr_cpu/libtr.so index 0d337d0..6f84243 100755 Binary files a/backend/tr_cpu/libtr.so and b/backend/tr_cpu/libtr.so differ diff --git a/backend/tr_gpu/libtr.so b/backend/tr_gpu/libtr.so index 08454bb..88fc221 100755 Binary files a/backend/tr_gpu/libtr.so and b/backend/tr_gpu/libtr.so differ diff --git a/backend/webInterface/tr_run.py b/backend/webInterface/tr_run.py index 30800b3..724bdcc 100644 --- a/backend/webInterface/tr_run.py +++ b/backend/webInterface/tr_run.py @@ -47,6 +47,7 @@ def post(self): img_up = self.request.files.get('file', None) img_b64 = self.get_argument('img', None) compress_size = self.get_argument('compress', None) + is_draw = self.get_argument("is_draw", None) # 判断是上传的图片还是base64 self.set_header('content-type', 'application/json') @@ -65,6 +66,7 @@ def post(self): self.finish(json.dumps({'code': 400, 'msg': '没有传入参数'}, cls=NpEncoder)) return + # 旋转图片 try: if hasattr(img, '_getexif') and img._getexif() is not None: orientation = 274 @@ -109,43 +111,46 @@ def post(self): new_height = int(img.height / scale + 0.5) img = img.resize((new_width, new_height), Image.ANTIALIAS) + # 进行ocr res = tr.run(img.copy().convert("L"), flag=tr.FLAG_ROTATED_RECT) - img_detected = img.copy() - img_draw = ImageDraw.Draw(img_detected) - colors = ['red', 'green', 'blue', "purple"] - - for i, r in enumerate(res): - rect, txt, confidence = r - ''' - cx: 中心点x - xy: 中心点y - w: 宽度 - h: 高度 - a: 旋转角度 - ''' - cx, cy, w, h, a = rect - box = cv2.boxPoints(((cx, cy), (w, h), a)) - box = np.int0(np.round(box)) - - for p1, p2 in [(0, 1), (1, 2), (2, 3), (3, 0)]: - img_draw.line(xy=(box[p1][0], box[p1][1], box[p2][0], box[p2][1]), fill=colors[i % len(colors)], - width=2) - - output_buffer = BytesIO() - img_detected.save(output_buffer, format='JPEG') - byte_data = output_buffer.getvalue() - img_detected_b64 = base64.b64encode(byte_data).decode('utf8') - + response_data = {'code': 200, 'msg': '成功', + 'data': {'raw_out': res, + 'speed_time': round(time.time() - start_time, 2)}} + if is_draw != '0': + img_detected = img.copy() + img_draw = ImageDraw.Draw(img_detected) + colors = ['red', 'green', 'blue', "purple"] + + for i, r in enumerate(res): + rect, txt, confidence = r + ''' + cx: 中心点x + xy: 中心点y + w: 宽度 + h: 高度 + a: 旋转角度 + ''' + cx, cy, w, h, a = rect + box = cv2.boxPoints(((cx, cy), (w, h), a)) + box = np.int0(np.round(box)) + + for p1, p2 in [(0, 1), (1, 2), (2, 3), (3, 0)]: + img_draw.line(xy=(box[p1][0], box[p1][1], box[p2][0], box[p2][1]), fill=colors[i % len(colors)], + width=2) + + output_buffer = BytesIO() + img_detected.save(output_buffer, format='JPEG') + byte_data = output_buffer.getvalue() + img_detected_b64 = base64.b64encode(byte_data).decode('utf8') + + response_data['data']['img_detected'] = 'data:image/jpeg;base64,' + img_detected_b64 log_info = { 'ip': self.request.host, - 'return': res, + 'return': response_data, 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } logger.info(json.dumps(log_info, cls=NpEncoder)) - self.finish(json.dumps( - {'code': 200, 'msg': '成功', - 'data': {'img_detected': 'data:image/jpeg;base64,' + img_detected_b64, 'raw_out': res, - 'speed_time': round(time.time() - start_time, 2)}}, - cls=NpEncoder)) + self.finish(json.dumps(response_data, + cls=NpEncoder)) return diff --git a/updateHistory.md b/updateHistory.md index 65d3431..2428c80 100644 --- a/updateHistory.md +++ b/updateHistory.md @@ -1,4 +1,11 @@ ## 更新记录 +* 2022年01月23日 + 更新tr2.3.1版模型 + > 模型本身支持多线程了~~现在直接可以打满CPU了!所有核! + +* 2022年01月16日 + 更新接口,增加不返回图片参数 + * 2020年08月17日 更新Dockerfile,docker镜像支持tr2.3