当前位置:   article > 正文

一文走通label studio OCR任务的半自动标注_labelstudio配置自定义ml backend

labelstudio配置自定义ml backend

#站在巨人的肩膀上,搬运官方+踩坑实录

一、环境配置

1、环境安装(不是我干的,跳过)

pip install label-studio

二、创建ML后端

1、clone 整个项目:

git clone https://github.com/HumanSignal/label-studio-ml-backend.git

会得到这个东西

2、设置环境

  1. cd label-studio-ml-backend/
  2. pip install -U -e .

3、创建一个新的后端

label-studio-ml create my_ml_backend

会创建以下文件目录:

my_ml_backend/
├── Dockerfile #用于docker-compose.yml通过 Docker 运行 ML 后端。
├── docker-compose.yml
├── model.py #是主文件,您可以在其中实现自己的训练和推理逻辑
├── _wsgi.py #是一个帮助程序文件,用于通过 Docker 运行 ML 后端(您不需要修改它)。
├── README.md #有关如何运行 ML 后端的说明。
└── requirements.txt #是一个具有 Python 依赖项的文件。

4、运行后端服务器和启动

(1)基于docker启动

  1. cd my_ml_backend/
  2. docker-compose up

ML 后端服务器位于http://localhost:9090。将 ML 后端连接到 Label Studio 时,您可以使用此 URL。

label-studio start

Label Studio 开始于http://localhost:8080.

(2)无需docker启动,用于调试

Label Studio 可以自动创建从新创建的模型运行 ML 后端所需的所有必要配置和脚本。

调用您的 ML 后端my_backend并从命令行初始化 ML 后端目录./my_backend

  1. label-studio-ml init my_ml_backend \
  2. --script ./my_ml_backend/model.py  create my_ml_backend --force
由于每次初始化都会创建一个文件夹,因此在调试时,会碰到文件夹重复的错误。所以添加第二行强制创建目录。
label-studio-ml start my_ml_backend

不同用户的后端应当设置不同的端口...不然多个后端会冲突启动不了

label-studio-ml start my_ml_backend -p 9091

服务器启动http://localhost:9090并在控制台中输出日志。

三、在label studio中调用ml backend

1、进入服务器8080端口

http://localhost:8080

2、用邮箱注册个人账户登录

跳过

3、创建项目后,进入设置-Machine Learning界面Add model,如下:

然后输入后端的地址,两个选项都选上就ok啦:

4、标注界面自动加载模型的预测

如图,在标注界面,就会有个后端模型为我们自动打标的INITIAL标签(不可修改),并有个副本标签用于修改

每个样本在人工完成标注并在右下角submit后(第一次为submit后续都是update),会显示标注的数量,用于区分有没有经过人工标注。

同时,在数据界面可以看到,自动标注的数据和OCR后端自动标注的数据:

四:如何根据具体任务和特定的模型开发模型后端

第二章提到,my_ml_backend/model.py是主要的文件,在这实现推理和训练(暂时没试过)。

这里以百度PaddleOCR为例,记录一下主要踩坑的点。

1、根据任务目标选择具体要开发的项目类型,如图,选择一个模板:

这里当然选择了OCR任务

选择完后,界面会跳转回来,用于设置标签等,不同的任务会有些不同,在Add label names输入aa并add,右边就多了一类aa标签。

2、label studio的数据结构

为了开发ML后端,还需要看上图code这里和导出的数据结果。

图1 此处的是前端的一些用于标注组件,这里的组件类型,决定了标注结果的数据格式。

图2下图是根据上面的设置标注完的数据结果:

怎么对应数据呢?

1、图1中的  <Image name="image" value="$ocr" >,就对应了图2中的红框1

2、图1中接下来还有三块内容,<Labels>,<Retangle>or<Polygon>,<Textarea>。这三块内容分别对应了OCR任务的三个产出,即标签类型(text、handwrting、aa),区域类型(矩形、多边型)以及文本内容,在后面标注的时候均会产出数据。

<Labels>对应的数据为标注的时候使用的标签类型+区域坐标。(这也是个坑点,后面再说)

<Retangle>or<Polygon>对应的数据为标注的时候使用的 区域类型+区域坐标,区域类型为矩形或者多边形。

<Textarea>对应的数据为标注的时候填写的文字和区域坐标。

这三块内容,对应了图二中的result中的数据(红色大框区域)。以图2为例,图二是<Labels>对应的数据。小框2表名了数据是<Labels>部分,即'type':'labels',同理还有'type':'retangle'和'type':'textarea';小框3即为标签类型“Text”+区域坐标。

可以看下其他两个的数据结构,写后端代码时就根据这些来组装结果了。

总结一下:

以OCR任务为例,在前端设置标签时,会有三个任务,即标签类型、标注区域类型和文本结果。其他任务同理,对齐数据即可。

3、构建ML后端predict函数

先根据官方的样例介绍一下:

label studio平台核心是调用my_ml_backend/model.py中的predict函数来获取模型打标的结果。因此主要需要改动的就是根据具体任务(这里为OCR),完成predict函数中图片OCR并封装成平台给定的格式。

  1. from label_studio_ml.model import LabelStudioMLBase
  2. class DummyModel(LabelStudioMLBase):
  3. def __init__(self, **kwargs):
  4. # 继承一下LabelStudioMLBase
  5. super(DummyModel, self).__init__(**kwargs)
  6. # 然后初始化一下要用的模型即可
  7. from_name, schema = list(self.parsed_label_config.items())[0]
  8. self.from_name = from_name
  9. self.to_name = schema['to_name'][0]
  10. self.labels = schema['labels']
  11. def predict(self, tasks, **kwargs):
  12. """ 核心的推理函数,在这完成label studio中图片获取、模型推理和数据打包返回
  13. """
  14. predictions = []
  15. for task in tasks:
  16. predictions.append({
  17. 'score': 0.987, # prediction overall score, visible in the data manager columns
  18. 'model_version': 'delorean-20151021', # all predictions will be differentiated by model version
  19. 'result': [{
  20. 'from_name': self.from_name,
  21. 'to_name': self.to_name,
  22. 'type': 'choices',
  23. 'score': 0.5, # per-region score, visible in the editor
  24. 'value': {
  25. 'choices': [self.labels[0]]
  26. }
  27. }]
  28. })
  29. return predictions
  30. def fit(self, annotations, **kwargs):
  31. """边打标边训练使用的
  32. """
  33. return {'path/to/created/model': 'my/model.bin'}
  34. def other(self,xx)
  35. '''其他辅助函数
  36. '''
  37. return xx

直接贴一下个人写的基于百度paddleOCR的模型后端代码,坑点都在注释中了,大概有4个大坑,每个都坑的我非常难受...:

  1. from typing import List, Dict, Optional
  2. from label_studio_ml.model import LabelStudioMLBase
  3. import cv2
  4. import sys
  5. sys.path.append('./PaddleOCR')#自己的目录
  6. from paddleocr import PaddleOCR
  7. import os
  8. from PIL import Image
  9. import pandas as pd
  10. import numpy as np
  11. import requests
  12. from io import BytesIO
  13. import math
  14. global_ocr_instance = PaddleOCR(
  15. # 坑点1
  16. # PaddleOCR初始化参数,这里把模型提到外面来了,不然整页整页都在打印加载模型时的参数
  17. det_model_dir='./inference/ch_PP-OCRv4_det_infer/',
  18. rec_model_dir='./inference/ch_PP-OCRv4_rec_infer/',
  19. rec_char_dict_path='./PaddleOCR/ppocr/utils/ppocr_keys_v1.txt',
  20. lang="ch",det_algorithm='DB',use_gpu=True,ocr_version='PP-OCRv4'
  21. #其他推理参数根据情况自己调整
  22. )
  23. class NewModel(LabelStudioMLBase):
  24. def __init__(self,project_id=None,**kwargs):
  25. super(NewModel, self).__init__(**kwargs)
  26. self.ocr = global_ocr_instance
  27. self.token = '376641251a1be5a6d94******5767b2113c1afe'
  28. def predict(self, tasks, **kwargs):
  29. results = []
  30. for task in tasks:
  31. '''坑点2:读取图像文件。虽然label studio和模型在同一台服务器上,但是在不同的端口。这样就导致了:(1)label studio上传图片时,无法直接加载模型服务器目录下的图片;(2)模型后端无法直接读取label studio中上传的图片,source中显示的直接上传的图片目录为"/data/upload/12/bf68a25f-0034.jpg"。因此这里选择通过request请求获取数据。这里还有个小坑,每个账号有不同的token,请求的时候需要带上'''
  32. image_path = task['data']['ocr']
  33. image_url = 'http://localhost:8080'+image_path
  34. image = self.load_image_from_url(image_url,self.token)
  35. # 使用OCR模型处理图像
  36. ocr_results = self.ocr.ocr(np.array(image), cls=True)
  37. # 转换OCR结果为Label Studio所需的格式
  38. predictions = []
  39. '''#坑点3,必须带上id,上面说了,ocr任务有三个结果,如果没有id,前端就变成了3个结果'''
  40. ocr_id = 0
  41. for result in ocr_results[0]:
  42. points, text_score = result
  43. text, score = text_score
  44. x, y, width, height, rotation = self.convert_points_to_relative_xywhr(points,np.array(image))
  45. '''坑点4:显示的区域坐标并不是像素的绝对值位置,而是相对位置...因此要转换成百分比,并且是0-100之间的数字,所以在下面有个坐标转换函数'''
  46. # 标签(Labels)组件预测
  47. label_prediction = {
  48. 'from_name': 'label',
  49. 'id':str(ocr_id),
  50. 'to_name': 'image',
  51. 'type': 'labels',
  52. 'value': {
  53. 'x': x,
  54. 'y': y,
  55. 'width': width,
  56. 'height': height,
  57. 'rotation':rotation,
  58. 'labels': ['Text']
  59. }
  60. }
  61. # 矩形框(Rectangle)组件预测
  62. rectangle_prediction = {
  63. 'from_name': 'bbox',
  64. 'id':str(ocr_id),
  65. 'to_name': 'image',
  66. 'type': 'rectangle',
  67. 'value': {
  68. 'x': x,
  69. 'y': y,
  70. 'width': width,
  71. 'height': height,
  72. 'rotation':rotation
  73. }
  74. }
  75. # 文本区域(TextArea)组件预测
  76. textarea_prediction = {
  77. 'from_name': 'transcription',
  78. 'id':str(ocr_id),
  79. 'to_name': 'image',
  80. 'type': 'textarea',
  81. 'value': {
  82. 'x': x,
  83. 'y': y,
  84. 'width': width,
  85. 'height': height,
  86. 'rotation':rotation,
  87. 'text':[text]
  88. }
  89. }
  90. predictions.extend([label_prediction, rectangle_prediction, textarea_prediction])
  91. ocr_id += 1
  92. results.append({
  93. 'result': predictions
  94. })
  95. return results
  96. def load_image_from_url(self,url,token):
  97. headers = {'Authorization': f'Token {token}'}
  98. response = requests.get(url, headers=headers)
  99. if response.status_code == 200:
  100. image = Image.open(BytesIO(response.content))
  101. return image
  102. else:
  103. raise Exception(f"Error loading image from {url}")
  104. def convert_points_to_relative_xywhr(self, points, image):
  105. """
  106. Convert a list of points representing a rectangle to relative x, y, width, height, and rotation.
  107. The values are relative to the dimensions of the given image.
  108. Points are expected to be in the order: top-left, top-right, bottom-right, bottom-left.
  109. The rotation is calculated as the clockwise angle between the top edge and the horizontal line.
  110. Args:
  111. - points (list of lists): A list of four points, each point is a list of two coordinates [x, y].
  112. - image (numpy array): An image array.
  113. Returns:
  114. - tuple: (x, y, width, height, rotation) where x and y are the relative coordinates of the top-left point,
  115. width and height are the relative dimensions of the rectangle, and rotation is the angle in degrees.
  116. """
  117. # Extracting points
  118. top_left, top_right, bottom_right, bottom_left = points
  119. # Image dimensions
  120. img_height, img_width = image.shape[:2]
  121. # Calculate width and height of the rectangle
  122. width = math.sqrt((top_right[0] - top_left[0])**2 + (top_right[1] - top_left[1])**2)
  123. height = math.sqrt((bottom_right[0] - top_right[0])**2 + (bottom_right[1] - top_right[1])**2)
  124. # Calculate rotation in radians
  125. dx = top_right[0] - top_left[0]
  126. dy = top_right[1] - top_left[1]
  127. angle_radians = math.atan2(dy, dx)
  128. # Convert rotation to degrees
  129. rotation = math.degrees(angle_radians)
  130. # The top-left point is the origin (x, y)
  131. x, y = top_left
  132. # Convert dimensions to relative values (percentage of image dimensions)
  133. rel_x = x / img_width * 100
  134. rel_y = y / img_height * 100
  135. rel_width = width / img_width * 100
  136. rel_height = height / img_height * 100
  137. return rel_x, rel_y, rel_width, rel_height, rotation

保存后,按照前面的教程,原...后端,启动!

再在label studio中配置一下,就能获取自动打标结果了。

这个版本是一次性获得整张图片的预打标结果的。

有空再更新一下边画框边实时获得框内的OCR结果的教程

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/492248
推荐阅读
相关标签
  

闽ICP备14008679号