server.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import random
  2. from fastapi import FastAPI, Request, Form, File, UploadFile
  3. from fastapi.templating import Jinja2Templates
  4. from typing import Dict, List, Optional
  5. from sx_utils import web_try
  6. import cv2
  7. import numpy as np
  8. import base64
  9. from core.predictor import LayoutBox, predict_img
  10. from sx_utils import format_print
  11. app = FastAPI()
  12. templates = Jinja2Templates(directory='templates')
  13. format_print()
  14. colors = [tuple([random.randint(0, 255) for _ in range(3)]) for _ in range(100)] # for bbox plotting
  15. model_selection_options = [
  16. 'ocr-layout',
  17. 'ocr-layout-paddle'
  18. ]
  19. clazz_names = [
  20. "code",
  21. "logo_hb",
  22. "logo_qz",
  23. "logo_rain",
  24. "logo_stack",
  25. "logo_sun",
  26. "logo_up",
  27. "logo_ys",
  28. "style",
  29. "table",
  30. "text",
  31. "text_jhl",
  32. "title",
  33. ]
  34. ##############################################
  35. # -------------GET Request Routes--------------
  36. ##############################################
  37. @app.get("/")
  38. def home(request: Request):
  39. ''' Returns html jinja2 template render for home page form
  40. '''
  41. return templates.TemplateResponse('home.html', {
  42. "request": request,
  43. "model_selection_options": model_selection_options,
  44. })
  45. @app.get("/drag_and_drop_detect")
  46. def drag_and_drop_detect(request: Request):
  47. ''' drag_and_drop_detect detect page. Uses a drag and drop
  48. file interface to upload files to the server, then renders
  49. the image + bboxes + labels on HTML canvas.
  50. '''
  51. return templates.TemplateResponse('drag_and_drop_detect.html',
  52. {"request": request,
  53. "model_selection_options": model_selection_options,
  54. })
  55. ##############################################
  56. # ------------POST Request Routes--------------
  57. ##############################################
  58. @app.post("/")
  59. def detect_via_web_form(request: Request,
  60. file_list: List[UploadFile] = File(...),
  61. model_name: str = Form(...),
  62. img_size: int = Form(1824)):
  63. '''
  64. Requires an image file upload, model name (ex. yolov5s). Optional image size parameter (Default 1824).
  65. Intended for human (non-api) users.
  66. Returns: HTML template render showing bbox data and base64 encoded image
  67. '''
  68. img_batch = [cv2.imdecode(np.fromstring(file.file.read(), np.uint8), cv2.IMREAD_COLOR)
  69. for file in file_list]
  70. # create a copy that corrects for cv2.imdecode generating BGR images instead of RGB
  71. # using cvtColor instead of [...,::-1] to keep array contiguous in RAM
  72. img_batch_rgb = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in img_batch]
  73. results = [predict_img(img, model_name, img_size) for img in img_batch_rgb]
  74. json_results = boxes_list_to_json(results, clazz_names)
  75. img_str_list = []
  76. # plot bboxes on the image
  77. for img, bbox_list in zip(img_batch, json_results):
  78. for bbox in bbox_list:
  79. label = f'{bbox["class_name"]} {bbox["confidence"]:.2f}'
  80. plot_one_box(bbox['bbox'], img, label=label,
  81. color=colors[int(bbox['class'])], line_thickness=3)
  82. img_str_list.append(base64EncodeImage(img))
  83. # escape the apostrophes in the json string representation
  84. encoded_json_results = str(json_results).replace("'", r"\'").replace('"', r'\"')
  85. return templates.TemplateResponse('show_results.html', {
  86. 'request': request,
  87. 'bbox_image_data_zipped': zip(img_str_list, json_results), # unzipped in jinja2 template
  88. 'bbox_data_str': encoded_json_results,
  89. })
  90. @app.post("/detect")
  91. @web_try()
  92. def detect_via_api(request: Request,
  93. file_list: List[UploadFile] = File(...),
  94. model_name: str = Form(...),
  95. img_size: int = Form(1920),
  96. download_image: Optional[bool] = Form(False)):
  97. '''
  98. Requires an image file upload, model name (ex. yolov5s).
  99. Optional image size parameter (Default 1920)
  100. Optional download_image parameter that includes base64 encoded image(s) with bbox's drawn in the json response
  101. Returns: JSON results of running YOLOv5 on the uploaded image. If download_image parameter is True, images with
  102. bboxes drawn are base64 encoded and returned inside the json response.
  103. Intended for API usage.
  104. '''
  105. img_batch = [cv2.imdecode(np.fromstring(file.file.read(), np.uint8), cv2.IMREAD_COLOR)
  106. for file in file_list]
  107. # create a copy that corrects for cv2.imdecode generating BGR images instead of RGB,
  108. # using cvtColor instead of [...,::-1] to keep array contiguous in RAM
  109. # 转换图片格式
  110. img_batch_rgb = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in img_batch]
  111. # 选用相关模型进行模版识别
  112. results = [predict_img(img, model_name, img_size) for img in img_batch_rgb]
  113. # 处理结果数据
  114. json_results = boxes_list_to_json(results, clazz_names)
  115. # 如果需要下载图片,在图片上绘制框
  116. if download_image:
  117. for idx, (img, bbox_list) in enumerate(zip(img_batch, json_results)):
  118. for bbox in bbox_list:
  119. label = f'{bbox["class_name"]} {bbox["confidence"]:.2f}'
  120. plot_one_box(bbox['bbox'], img, label=label,
  121. color=colors[int(bbox['class'])], line_thickness=3)
  122. payload = {'image_base64': base64EncodeImage(img)}
  123. json_results[idx].append(payload)
  124. encoded_json_results = str(json_results).replace("'", r'"')
  125. return encoded_json_results
  126. ##############################################
  127. # --------------Helper Functions---------------
  128. ##############################################
  129. def results_to_json(results, model):
  130. ''' Converts yolo model output to json (list of list of dicts)'''
  131. return [
  132. [
  133. {
  134. "class": int(pred[5]),
  135. "class_name": model.model.names[int(pred[5])],
  136. "bbox": [int(x) for x in pred[:4].tolist()], # convert bbox results to int from float
  137. "confidence": float(pred[4]),
  138. }
  139. for pred in result
  140. ]
  141. for result in results.xyxy
  142. ]
  143. # 在图像上绘制框
  144. def plot_one_box(x, im, color=(128, 128, 128), label=None, line_thickness=3):
  145. # Directly copied from: https://github.com/ultralytics/yolov5/blob/cd540d8625bba8a05329ede3522046ee53eb349d/utils/plots.py
  146. # Plots one bounding box on image 'im' using OpenCV
  147. assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to plot_on_box() input image.'
  148. tl = line_thickness or round(0.002 * (im.shape[0] + im.shape[1]) / 2) + 1 # line/font thickness
  149. c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
  150. cv2.rectangle(im, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
  151. if label:
  152. tf = max(tl - 1, 1) # font thickness
  153. t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
  154. c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
  155. cv2.rectangle(im, c1, c2, color, -1, cv2.LINE_AA) # filled
  156. cv2.putText(im, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
  157. def base64EncodeImage(img):
  158. ''' Takes an input image and returns a base64 encoded string representation of that image (jpg format)'''
  159. _, im_arr = cv2.imencode('.jpg', img)
  160. im_b64 = base64.b64encode(im_arr.tobytes()).decode('utf-8')
  161. return im_b64
  162. def boxes_list_to_json(boxes_list: List[List[LayoutBox]], clazz_names: List[str]) -> List[List[Dict]]:
  163. for boxes in boxes_list:
  164. for box in boxes:
  165. box.clazz_name = clazz_names[box.clazz]
  166. return [
  167. [
  168. box.to_service_dict()
  169. for box in boxes
  170. ]
  171. for boxes in boxes_list
  172. ]
  173. @app.get("/ping", description="健康检查")
  174. def ping():
  175. print("->ping")
  176. return "pong!"
  177. # if __name__ == '__main__':
  178. # import uvicorn
  179. # import argparse
  180. #
  181. # parser = argparse.ArgumentParser()
  182. # parser.add_argument('--host', default='localhost')
  183. # parser.add_argument('--port', default=8080)
  184. # parser.add_argument('--precache-models', action='store_true',
  185. # help='Pre-cache all models in memory upon initialization, otherwise dynamically caches models')
  186. # opt = parser.parse_args()
  187. #
  188. # # if opt.precache_models:
  189. # # model_dict = {model_name: torch.hub.load('ultralytics/yolov5', model_name, pretrained=True)
  190. # # for model_name in model_selection_options}
  191. #
  192. # app_str = 'server:app' # make the app string equal to whatever the name of this file is
  193. # uvicorn.run(app_str, host=opt.host, port=int(opt.port), reload=True)