未验证 提交 23942cf7 编写于 作者: D Daniel Yang 提交者: GitHub

Merge pull request #1347 from Evezerest/develop

Enable difficult mode, fix a bug on Mac and update icon
...@@ -107,7 +107,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -107,7 +107,7 @@ class MainWindow(QMainWindow, WindowMixin):
getStr = lambda strId: self.stringBundle.getString(strId) getStr = lambda strId: self.stringBundle.getString(strId)
self.defaultSaveDir = defaultSaveDir self.defaultSaveDir = defaultSaveDir
self.ocr = PaddleOCR(use_pdserving=False, use_angle_cls=True, det=True, cls=True, use_gpu=False, lang=lang) self.ocr = PaddleOCR(use_pdserving=False, use_angle_cls=True, det=True, cls=True, use_gpu=True, lang=lang)
if os.path.exists('./data/paddle.png'): if os.path.exists('./data/paddle.png'):
result = self.ocr.ocr('./data/paddle.png', cls=True, det=True) result = self.ocr.ocr('./data/paddle.png', cls=True, det=True)
...@@ -161,9 +161,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -161,9 +161,6 @@ class MainWindow(QMainWindow, WindowMixin):
self.AutoRecognition = QToolButton() self.AutoRecognition = QToolButton()
self.AutoRecognition.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.AutoRecognition.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.AutoRecognition.setIcon(newIcon('Auto')) self.AutoRecognition.setIcon(newIcon('Auto'))
# self.AutoRecognition.setIconSize(QSize(100,20))
# self.AutoRecognition.setFixedSize(QSize(80,30))
# self.AutoRecognition.setStyleSheet('text-align:center;')#border:none;font-size : 12pt;
autoRecLayout = QHBoxLayout() autoRecLayout = QHBoxLayout()
autoRecLayout.setContentsMargins(0, 0, 0, 0) autoRecLayout.setContentsMargins(0, 0, 0, 0)
autoRecLayout.addWidget(self.AutoRecognition) autoRecLayout.addWidget(self.AutoRecognition)
...@@ -182,25 +179,17 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -182,25 +179,17 @@ class MainWindow(QMainWindow, WindowMixin):
listLayout = QVBoxLayout() listLayout = QVBoxLayout()
listLayout.setContentsMargins(0, 0, 0, 0) listLayout.setContentsMargins(0, 0, 0, 0)
# Create a widget for edit and diffc button
self.diffcButton = QCheckBox(getStr('useDifficult'))
self.diffcButton.setChecked(False)
self.diffcButton.stateChanged.connect(self.btnstate)
self.editButton = QToolButton() self.editButton = QToolButton()
self.reRecogButton = QToolButton() self.reRecogButton = QToolButton()
self.reRecogButton.setIcon(newIcon('reRec', 30)) self.reRecogButton.setIcon(newIcon('reRec', 30))
# self.reRecogButton.setFixedSize(QSize(80,30))
self.reRecogButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.reRecogButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.newButton = QToolButton() self.newButton = QToolButton()
self.newButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.newButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
# self.newButton.setFixedSize(QSize(80, 30))
self.SaveButton = QToolButton() self.SaveButton = QToolButton()
self.SaveButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.SaveButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
# self.SaveButton.setFixedSize(QSize(60, 30))
self.DelButton = QToolButton() self.DelButton = QToolButton()
self.DelButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.DelButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
# self.DelButton.setFixedSize(QSize(80, 30))
lefttoptoolbox = QHBoxLayout() lefttoptoolbox = QHBoxLayout()
...@@ -285,12 +274,9 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -285,12 +274,9 @@ class MainWindow(QMainWindow, WindowMixin):
hlayout.setSpacing(0) hlayout.setSpacing(0)
hlayout.setContentsMargins(*m) hlayout.setContentsMargins(*m)
self.preButton = QToolButton() self.preButton = QToolButton()
# self.preButton.setFixedHeight(100)
# self.preButton.setText(getStr("prevImg"))
self.preButton.setIcon(newIcon("prev",40)) self.preButton.setIcon(newIcon("prev",40))
self.preButton.setIconSize(QSize(40, 100)) self.preButton.setIconSize(QSize(40, 100))
self.preButton.clicked.connect(self.openPrevImg) self.preButton.clicked.connect(self.openPrevImg)
# self.preButton.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.preButton.setStyleSheet('border: none;') self.preButton.setStyleSheet('border: none;')
self.iconlist = QListWidget() self.iconlist = QListWidget()
self.iconlist.setViewMode(QListView.IconMode) self.iconlist.setViewMode(QListView.IconMode)
...@@ -299,19 +285,14 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -299,19 +285,14 @@ class MainWindow(QMainWindow, WindowMixin):
self.iconlist.setIconSize(QSize(50, 50)) self.iconlist.setIconSize(QSize(50, 50))
self.iconlist.setMovement(False) self.iconlist.setMovement(False)
self.iconlist.setResizeMode(QListView.Adjust) self.iconlist.setResizeMode(QListView.Adjust)
# self.iconlist.itemDoubleClicked.connect(self.iconitemDoubleClicked)
self.iconlist.itemClicked.connect(self.iconitemDoubleClicked) self.iconlist.itemClicked.connect(self.iconitemDoubleClicked)
self.iconlist.setStyleSheet("background-color:transparent; border: none;") self.iconlist.setStyleSheet("background-color:transparent; border: none;")
self.iconlist.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.iconlist.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# self.iconlist.setStyleSheet('border: none;')
self.nextButton = QToolButton() self.nextButton = QToolButton()
# self.nextButton.setFixedHeight(100)
# self.nextButton.setText(getStr("nextImg"))
self.nextButton.setIcon(newIcon("next", 40)) self.nextButton.setIcon(newIcon("next", 40))
self.nextButton.setIconSize(QSize(40, 100)) self.nextButton.setIconSize(QSize(40, 100))
self.nextButton.setStyleSheet('border: none;') self.nextButton.setStyleSheet('border: none;')
self.nextButton.clicked.connect(self.openNextImg) self.nextButton.clicked.connect(self.openNextImg)
# self.nextButton.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
hlayout.addWidget(self.preButton) hlayout.addWidget(self.preButton)
hlayout.addWidget(self.iconlist) hlayout.addWidget(self.iconlist)
...@@ -874,7 +855,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -874,7 +855,7 @@ class MainWindow(QMainWindow, WindowMixin):
# shape.line_color = generateColorByText(shape.label) # shape.line_color = generateColorByText(shape.label)
self.setDirty() self.setDirty()
else: # User probably changed item visibility else: # User probably changed item visibility
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked) self.canvas.setShapeVisible(shape, True)#item.checkState() == Qt.Checked
def editBox(self): # ADD def editBox(self): # ADD
if not self.canvas.editing(): if not self.canvas.editing():
...@@ -945,33 +926,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -945,33 +926,6 @@ class MainWindow(QMainWindow, WindowMixin):
if len(self.mImgList) > 0: if len(self.mImgList) > 0:
self.zoomWidget.setValue(self.zoomWidgetValue + self.imgsplider.value()) self.zoomWidget.setValue(self.zoomWidgetValue + self.imgsplider.value())
# Add chris
def btnstate(self, item=None):
""" Function to handle difficult examples
Update on each object """
if not self.canvas.editing():
return
item = self.currentItem()
if not item: # If not selected Item, take the first one
item = self.labelList.item(self.labelList.count() - 1)
difficult = self.diffcButton.isChecked()
try:
shape = self.itemsToShapes[item]
except:
pass
# Checked and Update
try:
if difficult != shape.difficult:
shape.difficult = difficult
self.setDirty()
else: # User probably changed item visibility
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
except:
pass
# React to canvas signals. # React to canvas signals.
def shapeSelectionChanged(self, selected=False): def shapeSelectionChanged(self, selected=False):
if self._noSelectionSlot: if self._noSelectionSlot:
...@@ -993,7 +947,8 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -993,7 +947,8 @@ class MainWindow(QMainWindow, WindowMixin):
shape.paintLabel = self.displayLabelOption.isChecked() shape.paintLabel = self.displayLabelOption.isChecked()
item = HashableQListWidgetItem(shape.label) item = HashableQListWidgetItem(shape.label)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked) item.setCheckState(Qt.Unchecked) if shape.difficult else item.setCheckState(Qt.Checked)
# Checked means difficult is False
# item.setBackground(generateColorByText(shape.label)) # item.setBackground(generateColorByText(shape.label))
self.itemsToShapes[item] = shape self.itemsToShapes[item] = shape
self.shapesToItems[shape] = item self.shapesToItems[shape] = item
...@@ -1002,10 +957,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1002,10 +957,6 @@ class MainWindow(QMainWindow, WindowMixin):
# ADD for box # ADD for box
item = HashableQListWidgetItem(str([(int(p.x()), int(p.y())) for p in shape.points])) item = HashableQListWidgetItem(str([(int(p.x()), int(p.y())) for p in shape.points]))
# item = QListWidgetItem(str([(p.x(), p.y()) for p in shape.points]))
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked)
# item.setBackground(generateColorByText(shape.label))
self.itemsToShapesbox[item] = shape self.itemsToShapesbox[item] = shape
self.shapesToItemsbox[shape] = item self.shapesToItemsbox[shape] = item
self.BoxList.addItem(item) self.BoxList.addItem(item)
...@@ -1072,6 +1023,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1072,6 +1023,7 @@ class MainWindow(QMainWindow, WindowMixin):
# self.comboBox.update_items(uniqueTextList) # self.comboBox.update_items(uniqueTextList)
def saveLabels(self, annotationFilePath, mode='Auto'): def saveLabels(self, annotationFilePath, mode='Auto'):
# Mode is Auto means that labels will be loaded from self.result_dic totally, which is the output of ocr model
annotationFilePath = ustr(annotationFilePath) annotationFilePath = ustr(annotationFilePath)
if self.labelFile is None: if self.labelFile is None:
self.labelFile = LabelFile() self.labelFile = LabelFile()
...@@ -1090,17 +1042,16 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1090,17 +1042,16 @@ class MainWindow(QMainWindow, WindowMixin):
[format_shape(shape) for shape in self.canvas.shapes] [format_shape(shape) for shape in self.canvas.shapes]
# Can add differrent annotation formats here # Can add differrent annotation formats here
if self.model == 'paddle': for box in self.result_dic:
for box in self.result_dic: trans_dic = {"label": box[1][0], "points": box[0], 'difficult': False}
trans_dic = {"label": box[1][0], "points": box[0], 'difficult': False} if trans_dic["label"] is "" and mode == 'Auto':
if trans_dic["label"] is "" and mode == 'Auto': continue
continue shapes.append(trans_dic)
shapes.append(trans_dic)
try: try:
trans_dic = [] trans_dic = []
for box in shapes: for box in shapes:
trans_dic.append({"transcription": box['label'], "points": box['points'], 'difficult': False}) trans_dic.append({"transcription": box['label'], "points": box['points'], 'difficult': box['difficult']})
self.PPlabel[annotationFilePath] = trans_dic self.PPlabel[annotationFilePath] = trans_dic
if mode == 'Auto': if mode == 'Auto':
...@@ -1127,8 +1078,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1127,8 +1078,6 @@ class MainWindow(QMainWindow, WindowMixin):
self._noSelectionSlot = True self._noSelectionSlot = True
self.canvas.selectShape(self.itemsToShapes[item]) self.canvas.selectShape(self.itemsToShapes[item])
shape = self.itemsToShapes[item] shape = self.itemsToShapes[item]
# Add Chris
self.diffcButton.setChecked(shape.difficult)
def boxSelectionChanged(self): def boxSelectionChanged(self):
item = self.currentBox() item = self.currentBox()
...@@ -1136,8 +1085,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1136,8 +1085,6 @@ class MainWindow(QMainWindow, WindowMixin):
self._noSelectionSlot = True self._noSelectionSlot = True
self.canvas.selectShape(self.itemsToShapesbox[item]) self.canvas.selectShape(self.itemsToShapesbox[item])
shape = self.itemsToShapesbox[item] shape = self.itemsToShapesbox[item]
# Add Chris
self.diffcButton.setChecked(shape.difficult)
def labelItemChanged(self, item): def labelItemChanged(self, item):
shape = self.itemsToShapes[item] shape = self.itemsToShapes[item]
...@@ -1146,8 +1093,12 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1146,8 +1093,12 @@ class MainWindow(QMainWindow, WindowMixin):
shape.label = item.text() shape.label = item.text()
# shape.line_color = generateColorByText(shape.label) # shape.line_color = generateColorByText(shape.label)
self.setDirty() self.setDirty()
elif not ((item.checkState()== Qt.Unchecked) ^ (not shape.difficult)):
shape.difficult = True if item.checkState() == Qt.Unchecked else False
self.setDirty()
else: # User probably changed item visibility else: # User probably changed item visibility
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked) self.canvas.setShapeVisible(shape, True) # item.checkState() == Qt.Checked
# self.actions.save.setEnabled(True)
# Callback functions: # Callback functions:
def newShape(self): def newShape(self):
...@@ -1166,8 +1117,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1166,8 +1117,6 @@ class MainWindow(QMainWindow, WindowMixin):
text = self.labelDialog.popUp(text=self.prevLabelText) text = self.labelDialog.popUp(text=self.prevLabelText)
self.lastLabel = text self.lastLabel = text
# Add Chris
self.diffcButton.setChecked(False)
if text is not None: if text is not None:
self.prevLabelText = self.stringBundle.getString('tempLabel') self.prevLabelText = self.stringBundle.getString('tempLabel')
# generate_color = generateColorByText(text) # generate_color = generateColorByText(text)
...@@ -1264,7 +1213,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1264,7 +1213,7 @@ class MainWindow(QMainWindow, WindowMixin):
def togglePolygons(self, value): def togglePolygons(self, value):
for item, shape in self.itemsToShapes.items(): for item, shape in self.itemsToShapes.items():
item.setCheckState(Qt.Checked if value else Qt.Unchecked) self.canvas.setShapeVisible(shape, value)
def loadFile(self, filePath=None): def loadFile(self, filePath=None):
"""Load the specified file, or the last opened file if None.""" """Load the specified file, or the last opened file if None."""
...@@ -1628,6 +1577,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1628,6 +1577,7 @@ class MainWindow(QMainWindow, WindowMixin):
pass pass
def saveFile(self, _value=False, mode='Manual'): def saveFile(self, _value=False, mode='Manual'):
# Manual mode is used for users click "Save" manually,which will change the state of the image
if self.defaultSaveDir is not None and len(ustr(self.defaultSaveDir)): if self.defaultSaveDir is not None and len(ustr(self.defaultSaveDir)):
if self.filePath: if self.filePath:
imgidx = self.getImglabelidx(self.filePath) imgidx = self.getImglabelidx(self.filePath)
...@@ -1925,8 +1875,6 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1925,8 +1875,6 @@ class MainWindow(QMainWindow, WindowMixin):
self.comboBox = QComboBox() self.comboBox = QComboBox()
self.comboBox.setObjectName("comboBox") self.comboBox.setObjectName("comboBox")
self.comboBox.addItems(['Chinese & English', 'English', 'French', 'German', 'Korean', 'Japanese']) self.comboBox.addItems(['Chinese & English', 'English', 'French', 'German', 'Korean', 'Japanese'])
# self.comboBox_lg = QComboBox()
# self.comboBox_lg.setObjectName("comboBox_language")
vbox.addWidget(self.panel) vbox.addWidget(self.panel)
vbox.addWidget(self.comboBox) vbox.addWidget(self.comboBox)
self.dialog = QDialog() self.dialog = QDialog()
...@@ -1994,6 +1942,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -1994,6 +1942,7 @@ class MainWindow(QMainWindow, WindowMixin):
file, label = each.split('\t') file, label = each.split('\t')
if label: if label:
label = label.replace('false', 'False') label = label.replace('false', 'False')
label = label.replace('true', 'True')
labeldict[file] = eval(label) labeldict[file] = eval(label)
else: else:
labeldict[file] = [] labeldict[file] = []
...@@ -2004,7 +1953,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -2004,7 +1953,7 @@ class MainWindow(QMainWindow, WindowMixin):
savedfile = [self.getImglabelidx(i) for i in self.fileStatedict.keys()] savedfile = [self.getImglabelidx(i) for i in self.fileStatedict.keys()]
with open(self.PPlabelpath, 'w', encoding='utf-8') as f: with open(self.PPlabelpath, 'w', encoding='utf-8') as f:
for key in self.PPlabel: for key in self.PPlabel:
if key in savedfile: if key in savedfile and self.PPlabel[key] != []:
f.write(key + '\t') f.write(key + '\t')
f.write(json.dumps(self.PPlabel[key], ensure_ascii=False) + '\n') f.write(json.dumps(self.PPlabel[key], ensure_ascii=False) + '\n')
...@@ -2032,6 +1981,7 @@ class MainWindow(QMainWindow, WindowMixin): ...@@ -2032,6 +1981,7 @@ class MainWindow(QMainWindow, WindowMixin):
for key in self.fileStatedict: for key in self.fileStatedict:
idx = self.getImglabelidx(key) idx = self.getImglabelidx(key)
for i, label in enumerate(self.PPlabel[idx]): for i, label in enumerate(self.PPlabel[idx]):
if label['difficult']: continue
img = cv2.imread(key) img = cv2.imread(key)
img_crop = get_rotate_crop_image(img, np.array(label['points'], np.float32)) img_crop = get_rotate_crop_image(img, np.array(label['points'], np.float32))
img_name = os.path.splitext(os.path.basename(idx))[0] + '_crop_'+str(i)+'.jpg' img_name = os.path.splitext(os.path.basename(idx))[0] + '_crop_'+str(i)+'.jpg'
...@@ -2070,8 +2020,7 @@ def get_main_app(argv=[]): ...@@ -2070,8 +2020,7 @@ def get_main_app(argv=[]):
args = argparser.parse_args(argv[1:]) args = argparser.parse_args(argv[1:])
# Usage : labelImg.py image predefClassFile saveDir # Usage : labelImg.py image predefClassFile saveDir
win = MainWindow(lang=args.lang, win = MainWindow(lang=args.lang,
defaultPrefdefClassFile=args.predefined_classes_file, defaultPrefdefClassFile=args.predefined_classes_file)
)
win.show() win.show()
return app, win return app, win
......
...@@ -74,18 +74,26 @@ python3 PPOCRLabel.py ...@@ -74,18 +74,26 @@ python3 PPOCRLabel.py
## 说明 ## 说明
### 内置模型 ### 内置模型
- 默认模型:PPOCRLabel默认使用PaddleOCR中的中英文超轻量OCR模型,支持中英文与数字识别,多种语言检测。 - 默认模型:PPOCRLabel默认使用PaddleOCR中的中英文超轻量OCR模型,支持中英文与数字识别,多种语言检测。
- 模型语言切换:用户可通过菜单栏中 "PaddleOCR" - "选择模型" 切换内置模型语言,目前支持的语言包括法文、德文、韩文、日文。具体模型下载链接可参考[PaddleOCR模型列表](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/models_list.md). - 模型语言切换:用户可通过菜单栏中 "PaddleOCR" - "选择模型" 切换内置模型语言,目前支持的语言包括法文、德文、韩文、日文。具体模型下载链接可参考[PaddleOCR模型列表](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/models_list.md).
- 自定义模型:用户可根据[自定义模型代码使用](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/whl.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9E%8B),通过修改PPOCRLabel.py中针对[PaddleOCR类的实例化](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110)替换成自己训练的模型
- 自定义模型:用户可根据[自定义模型代码使用](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/whl.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9E%8B),通过修改PPOCRLabel.py中针对[PaddleOCR类的实例化](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110)替换成自己训练的模型。
### 导出部分识别结果
针对部分难以识别的数据,通过在识别结果的复选框中**取消勾选**相应的标记,其识别结果不会被导出。
*注意:识别结果中的复选框状态仍需用户手动点击保存后才能保留*
### 错误提示 ### 错误提示
- 如果同时使用whl包安装了paddleocr,其优先级大于通过paddleocr.py调用PaddleOCR类,whl包未更新时会导致程序异常。 - 如果同时使用whl包安装了paddleocr,其优先级大于通过paddleocr.py调用PaddleOCR类,whl包未更新时会导致程序异常。
- PPOCRLabel**不支持对中文文件名**的图片进行自动标注。 - PPOCRLabel**不支持对中文文件名**的图片进行自动标注。
- 如果您在打开软件过程中出现**objc[XXXXX]**开头的错误,证明您的opencv版本太高,建议安装4.2版本: - 如果您在打开软件过程中出现**objc[XXXXX]**开头的错误,证明您的opencv版本太高,建议安装4.2版本:
``` ```
pip install opencv-python==4.2.0.32 pip install opencv-python==4.2.0.32
``` ```
### 参考资料 ### 参考资料
1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg) 1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)
# PPOCRLabel # PPOCRLabel
PPOCRLabel is a semi-automatic graphic annotation tool suitable for OCR field. It is written in python3 and pyqt5. Support rectangular frame labeling and four-point labeling mode. Annotations can be directly used for the training of PPOCR detection and recognition models. PPOCRLabel is a semi-automatic graphic annotation tool suitable for OCR field. It is written in python3 and pyqt5, supporting rectangular box annotation and four-point annotation modes. Annotations can be directly used for the training of PPOCR detection and recognition models.
<img src="./data/gif/steps.gif" width="100%"/> <img src="./data/gif/steps.gif" width="100%"/>
...@@ -89,14 +89,33 @@ Therefore, if the recognition result has been manually changed before, it may ch ...@@ -89,14 +89,33 @@ Therefore, if the recognition result has been manually changed before, it may ch
| rec_gt.txt | The recognition label file, which can be directly used for PPOCR identification model training, is generated after the user clicks on the menu bar "PaddleOCR"-"Save recognition result". | | rec_gt.txt | The recognition label file, which can be directly used for PPOCR identification model training, is generated after the user clicks on the menu bar "PaddleOCR"-"Save recognition result". |
| crop_img | The recognition data, generated at the same time with *rec_gt.txt* | | crop_img | The recognition data, generated at the same time with *rec_gt.txt* |
## Explanation
### Built-in Model ### Built-in Model
- Default model: PPOCRLabel uses the Chinese and English ultra-lightweight OCR model in PaddleOCR by default, supports Chinese, English and number recognition, and multiple language detection. - Default model: PPOCRLabel uses the Chinese and English ultra-lightweight OCR model in PaddleOCR by default, supports Chinese, English and number recognition, and multiple language detection.
- Model language switching: Changing the built-in model language is supportable by clicking "PaddleOCR"-"Choose OCR Model" in the menu bar. Currently supported languages​include French, German, Korean, and Japanese. - Model language switching: Changing the built-in model language is supportable by clicking "PaddleOCR"-"Choose OCR Model" in the menu bar. Currently supported languages​include French, German, Korean, and Japanese.
For specific model download links, please refer to [PaddleOCR Model List](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/models_list_en.md#multilingual-recognition-modelupdating) For specific model download links, please refer to [PaddleOCR Model List](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/models_list_en.md#multilingual-recognition-modelupdating)
- Custom model: The model trained by users can be replaced by modifying PPOCRLabel.py in [PaddleOCR class instantiation](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110) referring [Custom Model Code](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/whl_en.md#use-custom-model) - Custom model: The model trained by users can be replaced by modifying PPOCRLabel.py in [PaddleOCR class instantiation](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110) referring [Custom Model Code](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/whl_en.md#use-custom-model)
### Export partial recognition results
For some data that are difficult to recognize, the recognition results will not be exported by **unchecking** the corresponding tags in the recognition results checkbox.
*Note: The status of the checkboxes in the recognition results still needs to be saved manually by clicking Save Button.*
### Error message
- If paddleocr is installed with whl, it has a higher priority than calling PaddleOCR class with paddleocr.py, which may cause an exception if whl package is not updated.
- If you get an error starting with **objc[XXXXX]** when opening the software, it proves that your opencv version is too high. It is recommended to install version 4.2:
```
pip install opencv-python==4.2.0.32
```
## Related ### Related
1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg) 1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)
...@@ -25,6 +25,7 @@ class Worker(QThread): ...@@ -25,6 +25,7 @@ class Worker(QThread):
self.mImgList = mImgList self.mImgList = mImgList
self.mainThread = mainThread self.mainThread = mainThread
self.model = model self.model = model
self.setStackSize(1024*1024)
def run(self): def run(self):
try: try:
......
此差异已折叠。
PPOCRLabel/resources/icons/Auto.png

7.7 KB | W: | H:

PPOCRLabel/resources/icons/Auto.png

471 字节 | W: | H:

PPOCRLabel/resources/icons/Auto.png
PPOCRLabel/resources/icons/Auto.png
PPOCRLabel/resources/icons/Auto.png
PPOCRLabel/resources/icons/Auto.png
  • 2-up
  • Swipe
  • Onion skin
PPOCRLabel/resources/icons/app.png

7.7 KB | W: | H:

PPOCRLabel/resources/icons/app.png

57.8 KB | W: | H:

PPOCRLabel/resources/icons/app.png
PPOCRLabel/resources/icons/app.png
PPOCRLabel/resources/icons/app.png
PPOCRLabel/resources/icons/app.png
  • 2-up
  • Swipe
  • Onion skin
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605840513947" class="icon" viewBox="0 0 1036 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="570" xmlns:xlink="http://www.w3.org/1999/xlink" width="202.34375" height="200"><defs><style type="text/css"></style></defs><path d="M938.74776 69.162438L893.455144 52.837457a67.26383 67.26383 0 1 0-20.007307 64.317969l42.469499 15.465771c28.722146 10.433258 53.516478 49.097686 53.516478 83.466066v598.869028c0 34.000148-24.548843 72.664576-53.639223 82.729601l-38.296195 13.501863a67.26383 67.26383 0 1 0 14.115585 66.650109l46.397313-16.447724c55.60313-19.51633 98.195372-83.711555 98.195373-146.311105V216.210008c0-62.354061-42.960475-126.917519-98.195373-147.293059zM222.16703 888.971053a66.895597 66.895597 0 0 0-52.166292 24.548843L121.516773 897.440403A89.1123 89.1123 0 0 1 67.26383 822.443688V208.722611a89.97151 89.97151 0 0 1 54.252943-76.101414l51.920803-18.657121a67.632063 67.632063 0 1 0-15.465771-65.913643L98.195372 69.285182A157.358084 157.358084 0 0 0 0 208.722611v613.721077a156.253386 156.253386 0 0 0 99.30007 138.332731L159.56748 982.011168a67.509318 67.509318 0 1 0 62.59955-93.040115z" fill="#444444" p-id="571"></path><path d="M426.29066 572.413721l86.780161-205.105584 91.935417 205.105584z m282.311696 230.759125h119.798354l-267.58239-592.854561-98.195372 1.104698-251.625642 592.118096 118.079936-1.350187L380.507068 680.183142h272.492158l55.112153 122.744215z" fill="#444444" p-id="572"></path></svg> <?xml version="1.0" encoding="UTF-8"?>
\ No newline at end of file <svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>APPicon备份 3@2x</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#A5ACFF" offset="0%"></stop>
<stop stop-color="#545DFF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="APPicon备份-3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="编组-3备份-2" fill="url(#linearGradient-1)">
<rect id="矩形" x="0" y="0" width="200" height="200" rx="4"></rect>
</g>
<g id="编组-4备份" transform="translate(11.000000, 11.000000)">
<polyline id="路径" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" points="104 167 11 167 11 11 167 11 167 96.7728865"></polyline>
<circle id="椭圆形" fill="#FFFFFF" cx="167" cy="11" r="11"></circle>
<circle id="椭圆形备份-8" fill="#FFFFFF" cx="11" cy="11" r="11"></circle>
<circle id="椭圆形备份-9" fill="#FFFFFF" cx="11" cy="167" r="11"></circle>
</g>
<path d="M85.9991492,141 L69,141 L69,60 L97.061099,60.0005168 C106.622339,60.0180588 113.008403,60.4807625 115.775763,61.1684309 C120.478388,62.336394 124.240393,64.9067781 127.297375,69.1122872 C130.353887,73.08404 132,78.4580969 132,84.7663606 C132,89.6731387 131.059499,93.8786478 129.413268,97.1497163 C127.767626,100.420785 125.416373,103.224458 122.594751,105.09369 C119.773129,106.962805 116.951625,108.364641 114.130121,108.83192 C110.367998,109.766594 104.724872,110 97.4355156,110 L97.4355156,110 L85.999,110 L85.9991492,141 Z M86,74.2965717 L85.9299926,74.2965717 L85.929,96.849 L95.7229134,96.8497738 C101.895297,96.8497738 106.289281,96.4080944 108.473591,95.739226 L108.655396,95.681343 C110.771288,94.9806587 112.417401,93.5787054 113.828271,91.7095902 C115.239023,89.840475 115.709274,87.7373697 115.709274,85.4009757 C115.709274,82.3637805 114.768772,80.0273866 113.122542,78.1581545 C111.476782,76.2890393 109.125646,75.1206084 106.538915,74.6533297 C104.879206,74.2409217 101.572085,74.1924031 96.7786667,74.186695 L95.8002135,74.1860291 C95.4674884,74.1859339 95.1282054,74.1859339 94.7824121,74.1859339 L86,74.185 L86,74.2965717 Z" id="形状结合" fill="#FFFFFF"></path>
<g id="编组" transform="translate(158.000000, 153.000000) rotate(65.000000) translate(-158.000000, -153.000000) translate(126.000000, 113.000000)">
<path d="M32,8 C14.326888,8 0,22.326888 0,40 C0,49.0801087 3.78188139,57.2769117 9.85641113,63.1011759 M32,72 C49.673112,72 64,57.673112 64,40 C64,30.9865538 60.2734449,22.8434911 54.2770078,17.0274853" id="形状备份-2" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round"></path>
<polygon id="三角形备份-3" fill="#FFFFFF" transform="translate(38.000000, 8.000000) scale(-1, 1) rotate(-90.000000) translate(-38.000000, -8.000000) " points="38 2 46 14 30 14"></polygon>
<polygon id="三角形备份-4" fill="#FFFFFF" transform="translate(26.000000, 72.000000) rotate(-90.000000) translate(-26.000000, -72.000000) " points="26 66 34 78 18 78"></polygon>
</g>
</g>
</svg>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册