Merge pull request #1418 from Evezerest/develop
Update WeChat group QR code and add save label in the menu
This commit is contained in:
commit
050a29a9fc
|
@ -61,7 +61,6 @@ from libs.zoomWidget import ZoomWidget
|
|||
from libs.autoDialog import AutoDialog
|
||||
from libs.labelDialog import LabelDialog
|
||||
from libs.colorDialog import ColorDialog
|
||||
from libs.labelFile import LabelFile, LabelFileError
|
||||
from libs.toolBar import ToolBar
|
||||
from libs.ustr import ustr
|
||||
from libs.hashableQListWidgetItem import HashableQListWidgetItem
|
||||
|
@ -373,11 +372,11 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
openPrevImg = action(getStr('prevImg'), self.openPrevImg,
|
||||
'a', 'prev', getStr('prevImgDetail'))
|
||||
|
||||
verify = action(getStr('verifyImg'), self.verifyImg,
|
||||
'space', 'verify', getStr('verifyImgDetail'))
|
||||
# verify = action(getStr('verifyImg'), self.verifyImg,
|
||||
# 'space', 'verify', getStr('verifyImgDetail'))
|
||||
|
||||
save = action(getStr('save'), self.saveFile,
|
||||
'Ctrl+S', 'save', getStr('saveDetail'), enabled=False)
|
||||
'Ctrl+V', 'verify', getStr('saveDetail'), enabled=False)
|
||||
|
||||
alcm = action(getStr('choosemodel'), self.autolcm,
|
||||
'Ctrl+M', 'next', getStr('tipchoosemodel'))
|
||||
|
@ -401,7 +400,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
delete = action(getStr('delBox'), self.deleteSelectedShape,
|
||||
'Delete', 'delete', getStr('delBoxDetail'), enabled=False)
|
||||
copy = action(getStr('dupBox'), self.copySelectedShape,
|
||||
'Ctrl+D', 'copy', getStr('dupBoxDetail'),
|
||||
'Ctrl+C', 'copy', getStr('dupBoxDetail'),
|
||||
enabled=False)
|
||||
|
||||
hideAll = action(getStr('hideBox'), partial(self.togglePolygons, False),
|
||||
|
@ -461,7 +460,10 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
'p', 'new', 'Creat Polygon', enabled=True)
|
||||
|
||||
saveRec = action(getStr('saveRec'), self.saveRecResult,
|
||||
'', 'saveRec', getStr('saveRec'), enabled=False)
|
||||
'', 'save', getStr('saveRec'), enabled=False)
|
||||
|
||||
saveLabel = action(getStr('saveLabel'), self.saveLabelFile, #
|
||||
'Ctrl+S', 'save', getStr('saveLabel'), enabled=False)
|
||||
|
||||
self.editButton.setDefaultAction(edit)
|
||||
self.newButton.setDefaultAction(create)
|
||||
|
@ -526,9 +528,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
shapeLineColor=shapeLineColor, shapeFillColor=shapeFillColor,
|
||||
zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg,
|
||||
fitWindow=fitWindow, fitWidth=fitWidth,
|
||||
zoomActions=zoomActions,
|
||||
zoomActions=zoomActions, saveLabel=saveLabel,
|
||||
fileMenuActions=(
|
||||
open, opendir, save, resetAll, quit),
|
||||
open, opendir, saveLabel, resetAll, quit),
|
||||
beginner=(), advanced=(),
|
||||
editMenu=(createpoly, edit, copy, delete,
|
||||
None, color1, self.drawSquaresOption),
|
||||
|
@ -564,7 +566,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
self.displayLabelOption.triggered.connect(self.togglePaintLabelsOption)
|
||||
|
||||
addActions(self.menus.file,
|
||||
(opendir, None, save, resetAll, deleteImg, quit))
|
||||
(opendir, None, saveLabel, saveRec, None, resetAll, deleteImg, quit))
|
||||
|
||||
addActions(self.menus.help, (showSteps, showInfo))
|
||||
addActions(self.menus.view, (
|
||||
|
@ -574,7 +576,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
zoomIn, zoomOut, zoomOrg, None,
|
||||
fitWindow, fitWidth))
|
||||
|
||||
addActions(self.menus.autolabel, (alcm, saveRec, None, help)) #
|
||||
addActions(self.menus.autolabel, (alcm, None, help)) #
|
||||
|
||||
self.menus.file.aboutToShow.connect(self.updateFileMenu)
|
||||
|
||||
|
@ -586,14 +588,14 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
|
||||
# self.tools = self.toolbar('Tools')
|
||||
|
||||
self.actions.beginner = (
|
||||
open, opendir, openNextImg, openPrevImg, verify, save, None, create, copy, delete, None,
|
||||
zoomIn, zoom, zoomOut, fitWindow, fitWidth)
|
||||
|
||||
self.actions.advanced = (
|
||||
open, opendir, openNextImg, openPrevImg, save, None,
|
||||
createMode, editMode, None,
|
||||
hideAll, showAll)
|
||||
# self.actions.beginner = (
|
||||
# open, opendir, openNextImg, openPrevImg, verify, save, None, create, copy, delete, None,
|
||||
# zoomIn, zoom, zoomOut, fitWindow, fitWidth)
|
||||
#
|
||||
# self.actions.advanced = (
|
||||
# open, opendir, openNextImg, openPrevImg, save, None,
|
||||
# createMode, editMode, None,
|
||||
# hideAll, showAll)
|
||||
|
||||
self.statusBar().showMessage('%s started.' % __appname__)
|
||||
self.statusBar().show()
|
||||
|
@ -1025,9 +1027,6 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
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)
|
||||
if self.labelFile is None:
|
||||
self.labelFile = LabelFile()
|
||||
self.labelFile.verified = self.canvas.verified
|
||||
|
||||
def format_shape(s):
|
||||
# print('s in saveLabels is ',s)
|
||||
|
@ -1062,8 +1061,8 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
# self.lineColor.getRgb(), self.fillColor.getRgb())
|
||||
# print('Image:{0} -> Annotation:{1}'.format(self.filePath, annotationFilePath))
|
||||
return True
|
||||
except LabelFileError as e:
|
||||
self.errorMessage(u'Error saving label data', u'<b>%s</b>' % e)
|
||||
except:
|
||||
self.errorMessage(u'Error saving label data')
|
||||
return False
|
||||
|
||||
def copySelectedShape(self):
|
||||
|
@ -1255,25 +1254,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
# if unicodeFilePath in self.mImgList:
|
||||
|
||||
if unicodeFilePath and os.path.exists(unicodeFilePath):
|
||||
if LabelFile.isLabelFile(unicodeFilePath):
|
||||
try:
|
||||
self.labelFile = LabelFile(unicodeFilePath)
|
||||
except LabelFileError as e:
|
||||
self.errorMessage(u'Error opening file',
|
||||
(u"<p><b>%s</b></p>"
|
||||
u"<p>Make sure <i>%s</i> is a valid label file.")
|
||||
% (e, unicodeFilePath))
|
||||
self.status("Error reading %s" % unicodeFilePath)
|
||||
return False
|
||||
self.imageData = self.labelFile.imageData
|
||||
self.lineColor = QColor(*self.labelFile.lineColor)
|
||||
self.fillColor = QColor(*self.labelFile.fillColor)
|
||||
self.canvas.verified = self.labelFile.verified
|
||||
else:
|
||||
# Load image:
|
||||
# read data first and store for saving into label file.
|
||||
self.imageData = read(unicodeFilePath, None)
|
||||
self.labelFile = None
|
||||
self.canvas.verified = False
|
||||
|
||||
image = QImage.fromData(self.imageData)
|
||||
|
@ -1286,8 +1267,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
self.image = image
|
||||
self.filePath = unicodeFilePath
|
||||
self.canvas.loadPixmap(QPixmap.fromImage(image))
|
||||
if self.labelFile:
|
||||
self.loadLabels(self.labelFile.shapes)
|
||||
|
||||
if self.validFilestate(filePath) is True:
|
||||
self.setClean()
|
||||
else:
|
||||
|
@ -1400,8 +1380,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
settings[SETTING_DRAW_SQUARE] = self.drawSquaresOption.isChecked()
|
||||
settings.save()
|
||||
try:
|
||||
self.saveFilestate()
|
||||
self.savePPlabel()
|
||||
self.saveLabelFile()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -1446,8 +1425,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
if not self.mayContinue() or not dirpath:
|
||||
return
|
||||
if self.defaultSaveDir and self.defaultSaveDir != dirpath:
|
||||
self.saveFilestate()
|
||||
self.savePPlabel()
|
||||
self.saveLabelFile()
|
||||
|
||||
if not isDelete:
|
||||
self.loadFilestate(dirpath)
|
||||
|
@ -1488,24 +1466,8 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
self.haveAutoReced = False
|
||||
self.AutoRecognition.setEnabled(True)
|
||||
self.reRecogButton.setEnabled(True)
|
||||
self.actions.saveLabel.setEnabled(True)
|
||||
|
||||
def verifyImg(self, _value=False):
|
||||
# Proceding next image without dialog if having any label
|
||||
if self.filePath is not None:
|
||||
try:
|
||||
self.labelFile.toggleVerify()
|
||||
except AttributeError:
|
||||
# If the labelling file does not exist yet, create if and
|
||||
# re-save it with the verified attribute.
|
||||
self.saveFile()
|
||||
if self.labelFile != None:
|
||||
self.labelFile.toggleVerify()
|
||||
else:
|
||||
return
|
||||
|
||||
self.canvas.verified = self.labelFile.verified
|
||||
self.paintCanvas()
|
||||
self.saveFile()
|
||||
|
||||
def openPrevImg(self, _value=False):
|
||||
if len(self.mImgList) <= 0:
|
||||
|
@ -1578,18 +1540,10 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
|
||||
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.filePath:
|
||||
imgidx = self.getImglabelidx(self.filePath)
|
||||
self._saveFile(imgidx, mode=mode)
|
||||
|
||||
else:
|
||||
imgFileDir = os.path.dirname(self.filePath)
|
||||
imgFileName = os.path.basename(self.filePath)
|
||||
savedFileName = os.path.splitext(imgFileName)[0]
|
||||
savedPath = os.path.join(imgFileDir, savedFileName)
|
||||
self._saveFile(savedPath if self.labelFile
|
||||
else self.saveFileDialog(removeExt=False), mode=mode)
|
||||
|
||||
def saveFileAs(self, _value=False):
|
||||
assert not self.image.isNull(), "cannot save empty image"
|
||||
|
@ -1967,6 +1921,10 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||
f.write(key + '\t')
|
||||
f.write(json.dumps(self.Cachelabel[key], ensure_ascii=False) + '\n')
|
||||
|
||||
def saveLabelFile(self):
|
||||
self.saveFilestate()
|
||||
self.savePPlabel()
|
||||
|
||||
def saveRecResult(self):
|
||||
if None in [self.PPlabelpath, self.PPlabel, self.fileStatedict]:
|
||||
QMessageBox.information(self, "Information", "Save file first")
|
||||
|
@ -2013,7 +1971,7 @@ def get_main_app(argv=[]):
|
|||
app.setWindowIcon(newIcon("app"))
|
||||
# Tzutalin 201705+: Accept extra agruments to change predefined class file
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument("--lang", default='ch', nargs="?")
|
||||
argparser.add_argument("--lang", default='en', nargs="?")
|
||||
argparser.add_argument("--predefined_classes_file",
|
||||
default=os.path.join(os.path.dirname(__file__), "data", "predefined_classes.txt"),
|
||||
nargs="?")
|
||||
|
|
|
@ -12,13 +12,10 @@ PPOCRLabel是一款适用于OCR领域的半自动化图形标注工具,使用p
|
|||
### 2. 安装PPOCRLabel
|
||||
#### Windows + Anaconda
|
||||
|
||||
下载安装[Anaconda](https://www.anaconda.com/download/#download) (Python 3+)
|
||||
|
||||
```
|
||||
conda install pyqt=5
|
||||
pip install pyqt5
|
||||
cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
|
||||
pyrcc5 -o libs/resources.py resources.qrc
|
||||
python PPOCRLabel.py
|
||||
python PPOCRLabel.py --lang ch
|
||||
```
|
||||
|
||||
#### Ubuntu Linux
|
||||
|
@ -27,7 +24,7 @@ python PPOCRLabel.py
|
|||
pip3 install pyqt5
|
||||
pip3 install trash-cli
|
||||
cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
|
||||
python3 PPOCRLabel.py
|
||||
python3 PPOCRLabel.py --lang ch
|
||||
```
|
||||
|
||||
#### macOS
|
||||
|
@ -36,7 +33,7 @@ pip3 install pyqt5
|
|||
pip3 uninstall opencv-python # 由于mac版本的opencv与pyqt有冲突,需先手动卸载opencv
|
||||
pip3 install opencv-contrib-python-headless # 安装headless版本的open-cv
|
||||
cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
|
||||
python3 PPOCRLabel.py
|
||||
python3 PPOCRLabel.py --lang ch
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
@ -50,9 +47,9 @@ python3 PPOCRLabel.py
|
|||
5. 标记框绘制完成后,用户点击 “确认”,检测框会先被预分配一个 “待识别” 标签。
|
||||
6. 重新识别:将图片中的所有检测画绘制/调整完成后,点击 “重新识别”,PPOCR模型会对当前图片中的**所有检测框**重新识别<sup>[3]</sup>。
|
||||
7. 内容更改:双击识别结果,对不准确的识别结果进行手动更改。
|
||||
8. 保存:点击 “保存”,图片状态切换为 “√”,跳转至下一张。
|
||||
8. 确认标记:点击 “确认”,图片状态切换为 “√”,跳转至下一张(此时不会直接将结果写入文件)。
|
||||
9. 删除:点击 “删除图像”,图片将会被删除至回收站。
|
||||
10. 标注结果:关闭应用程序或切换文件路径后,手动保存过的标签将会被存放在所打开图片文件夹下的*Label.txt*中。在菜单栏点击 “PaddleOCR” - "保存识别结果"后,会将此类图片的识别训练数据保存在*crop_img*文件夹下,识别标签保存在*rec_gt.txt*中<sup>[4]</sup>。
|
||||
10. 保存结果:用户可以通过菜单中“文件-保存标记结果”手动保存,同时程序也会在用户每确认10张图片后自动保存一次。手动确认过的标记将会被存放在所打开图片文件夹下的*Label.txt*中。在菜单栏点击 “文件” - "保存识别结果"后,会将此类图片的识别训练数据保存在*crop_img*文件夹下,识别标签保存在*rec_gt.txt*中<sup>[4]</sup>。
|
||||
|
||||
### 注意
|
||||
|
||||
|
@ -62,14 +59,14 @@ python3 PPOCRLabel.py
|
|||
|
||||
[3] 点击“重新识别”后,模型会对图片中的识别结果进行覆盖。因此如果在此之前手动更改过识别结果,有可能在重新识别后产生变动。
|
||||
|
||||
[4] PPOCRLabel产生的文件包括一下几种,请勿手动更改其中内容,否则会引起程序出现异常。
|
||||
[4] PPOCRLabel产生的文件放置于标记图片文件夹下,包括一下几种,请勿手动更改其中内容,否则会引起程序出现异常。
|
||||
|
||||
| 文件名 | 说明 |
|
||||
| :-----------: | :----------------------------------------------------------: |
|
||||
| Label.txt | 检测标签,可直接用于PPOCR检测模型训练。用户每保存10张检测结果后,程序会进行自动写入。当用户关闭应用程序或切换文件路径后同样会进行写入。 |
|
||||
| fileState.txt | 图片状态标记文件,保存当前文件夹下已经被用户手动确认过的图片名称。 |
|
||||
| Cache.cach | 缓存文件,保存模型自动识别的结果。 |
|
||||
| rec_gt.txt | 识别标签。可直接用于PPOCR识别模型训练。需用户手动点击菜单栏“PaddleOCR” - "保存识别结果"后产生。 |
|
||||
| rec_gt.txt | 识别标签。可直接用于PPOCR识别模型训练。需用户手动点击菜单栏“文件” - "保存识别结果"后产生。 |
|
||||
| crop_img | 识别数据。按照检测框切割后的图片。与rec_gt.txt同时产生。 |
|
||||
|
||||
## 说明
|
||||
|
@ -90,10 +87,14 @@ python3 PPOCRLabel.py
|
|||
### 错误提示
|
||||
- 如果同时使用whl包安装了paddleocr,其优先级大于通过paddleocr.py调用PaddleOCR类,whl包未更新时会导致程序异常。
|
||||
- PPOCRLabel**不支持对中文文件名**的图片进行自动标注。
|
||||
- 如果您在打开软件过程中出现**objc[XXXXX]**开头的错误,证明您的opencv版本太高,建议安装4.2版本:
|
||||
- 对于Linux用户,如果您在打开软件过程中出现**objc[XXXXX]**开头的错误,证明您的opencv版本太高,建议安装4.2版本:
|
||||
```
|
||||
pip install opencv-python==4.2.0.32
|
||||
```
|
||||
- 如果出现''Missing string id '开头的错误,需要重新编译资源:
|
||||
```
|
||||
pyrcc5 -o libs/resources.py resources.qrc
|
||||
```
|
||||
### 参考资料
|
||||
|
||||
1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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_en.gif" width="100%"/>
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -17,10 +17,9 @@ Refer to [PaddleOCR installation document](https://github.com/PaddlePaddle/Paddl
|
|||
Download and install [Anaconda](https://www.anaconda.com/download/#download) (Python 3+)
|
||||
|
||||
```
|
||||
conda install pyqt=5
|
||||
pip install pyqt5
|
||||
cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
|
||||
pyrcc5 -o libs/resources.py resources.qrc
|
||||
python PPOCRLabel.py --lang en
|
||||
python PPOCRLabel.py
|
||||
```
|
||||
|
||||
#### Ubuntu Linux
|
||||
|
@ -29,7 +28,7 @@ python PPOCRLabel.py --lang en
|
|||
pip3 install pyqt5
|
||||
pip3 install trash-cli
|
||||
cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
|
||||
python3 PPOCRLabel.py --lang en
|
||||
python3 PPOCRLabel.py
|
||||
```
|
||||
|
||||
#### macOS
|
||||
|
@ -38,7 +37,7 @@ pip3 install pyqt5
|
|||
pip3 uninstall opencv-python # Uninstall opencv manually as it conflicts with pyqt
|
||||
pip3 install opencv-contrib-python-headless # Install the headless version of opencv
|
||||
cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
|
||||
python3 PPOCRLabel.py --lang en
|
||||
python3 PPOCRLabel.py
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -63,11 +62,11 @@ python3 PPOCRLabel.py --lang en
|
|||
|
||||
7. Double click the result in 'recognition result' list to manually change inaccurate recognition results.
|
||||
|
||||
8. Click "Save", the image status will switch to "√",then the program automatically jump to the next.
|
||||
8. Click "Check", the image status will switch to "√",then the program automatically jump to the next(The results will not be written directly to the file at this time).
|
||||
|
||||
9. Click "Delete Image" and the image will be deleted to the recycle bin.
|
||||
|
||||
10. Labeling result: After closing the application or switching the file path, the manually saved label will be stored in *Label.txt* under the opened picture folder.
|
||||
10. Labeling result: the user can save manually through the menu "File - Save Label", while the program will also save automatically after every 10 images confirmed by the user.the manually checked label will be stored in *Label.txt* under the opened picture folder.
|
||||
Click "PaddleOCR"-"Save Recognition Results" in the menu bar, the recognition training data of such pictures will be saved in the *crop_img* folder, and the recognition label will be saved in *rec_gt.txt*<sup>[4]</sup>.
|
||||
|
||||
### Note
|
||||
|
@ -79,14 +78,14 @@ python3 PPOCRLabel.py --lang en
|
|||
[3] After clicking "Re-recognize", the model will overwrite ALL recognition results in the picture.
|
||||
Therefore, if the recognition result has been manually changed before, it may change after re-recognition.
|
||||
|
||||
[4] The files produced by PPOCRLabel include the following, please do not manually change the contents, otherwise it will cause the program to be abnormal.
|
||||
[4] The files produced by PPOCRLabel can be found under the opened picture folder including the following, please do not manually change the contents, otherwise it will cause the program to be abnormal.
|
||||
|
||||
| File name | Description |
|
||||
| :-----------: | :----------------------------------------------------------: |
|
||||
| Label.txt | The detection label file can be directly used for PPOCR detection model training. After the user saves 10 label results, the file will be automatically saved. It will also be written when the user closes the application or changes the file folder. |
|
||||
| fileState.txt | The picture status file save the image in the current folder that has been manually confirmed by the user. |
|
||||
| Cache.cach | Cache files to save the results of model recognition. |
|
||||
| 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 "File"-"Save recognition result". |
|
||||
| crop_img | The recognition data, generated at the same time with *rec_gt.txt* |
|
||||
|
||||
## Explanation
|
||||
|
@ -110,12 +109,15 @@ For some data that are difficult to recognize, the recognition results will not
|
|||
|
||||
- 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:
|
||||
- For Linux users, 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
|
||||
```
|
||||
|
||||
- If you get an error starting with **Missing string id **,you need to recompile resources:
|
||||
```
|
||||
pyrcc5 -o libs/resources.py resources.qrc
|
||||
```
|
||||
### Related
|
||||
|
||||
1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.9 MiB |
|
@ -45,7 +45,7 @@ class Worker(QThread):
|
|||
chars = res[1][0]
|
||||
cond = res[1][1]
|
||||
posi = res[0]
|
||||
self.listValue.emit("文字:" + chars + " 置信度:" + str(cond) + " 坐标:" + json.dumps(posi))
|
||||
self.listValue.emit("Transcription: " + chars + " Probability: " + str(cond) + " Location: " + json.dumps(posi))
|
||||
self.mainThread.result_dic = self.result_dic
|
||||
self.mainThread.filePath = Imgpath
|
||||
# 保存
|
||||
|
@ -88,7 +88,7 @@ class AutoDialog(QDialog):
|
|||
bb.button(BB.Ok).setEnabled(False)
|
||||
|
||||
self.setLayout(layout)
|
||||
self.setWindowTitle("自动标注中")
|
||||
# self.setWindowTitle("自动标注中")
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
|
||||
# self.setWindowFlags(Qt.WindowCloseButtonHint)
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
# Copyright (c) 2016 Tzutalin
|
||||
# Create by TzuTaLin <tzu.ta.lin@gmail.com>
|
||||
|
||||
try:
|
||||
from PyQt5.QtGui import QImage
|
||||
except ImportError:
|
||||
from PyQt4.QtGui import QImage
|
||||
|
||||
from base64 import b64encode, b64decode
|
||||
from libs.pascal_voc_io import PascalVocWriter
|
||||
from libs.yolo_io import YOLOWriter
|
||||
from libs.pascal_voc_io import XML_EXT
|
||||
from enum import Enum
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
class LabelFileFormat(Enum):
|
||||
PASCAL_VOC= 1
|
||||
YOLO = 2
|
||||
|
||||
|
||||
class LabelFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LabelFile(object):
|
||||
# It might be changed as window creates. By default, using XML ext
|
||||
# suffix = '.lif'
|
||||
suffix = XML_EXT
|
||||
|
||||
def __init__(self, filename=None):
|
||||
self.shapes = ()
|
||||
self.imagePath = None
|
||||
self.imageData = None
|
||||
self.verified = False
|
||||
|
||||
def savePascalVocFormat(self, filename, shapes, imagePath, imageData,
|
||||
lineColor=None, fillColor=None, databaseSrc=None):
|
||||
imgFolderPath = os.path.dirname(imagePath)
|
||||
imgFolderName = os.path.split(imgFolderPath)[-1]
|
||||
imgFileName = os.path.basename(imagePath)
|
||||
#imgFileNameWithoutExt = os.path.splitext(imgFileName)[0]
|
||||
# Read from file path because self.imageData might be empty if saving to
|
||||
# Pascal format
|
||||
image = QImage()
|
||||
image.load(imagePath)
|
||||
imageShape = [image.height(), image.width(),
|
||||
1 if image.isGrayscale() else 3]
|
||||
writer = PascalVocWriter(imgFolderName, imgFileName,
|
||||
imageShape, localImgPath=imagePath)
|
||||
writer.verified = self.verified
|
||||
|
||||
for shape in shapes:
|
||||
points = shape['points']
|
||||
label = shape['label']
|
||||
# Add Chris
|
||||
difficult = int(shape['difficult'])
|
||||
bndbox = LabelFile.convertPoints2BndBox(points)
|
||||
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
|
||||
|
||||
writer.save(targetFile=filename)
|
||||
return
|
||||
|
||||
def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList,
|
||||
lineColor=None, fillColor=None, databaseSrc=None):
|
||||
imgFolderPath = os.path.dirname(imagePath)
|
||||
imgFolderName = os.path.split(imgFolderPath)[-1]
|
||||
imgFileName = os.path.basename(imagePath)
|
||||
#imgFileNameWithoutExt = os.path.splitext(imgFileName)[0]
|
||||
# Read from file path because self.imageData might be empty if saving to
|
||||
# Pascal format
|
||||
image = QImage()
|
||||
image.load(imagePath)
|
||||
imageShape = [image.height(), image.width(),
|
||||
1 if image.isGrayscale() else 3]
|
||||
writer = YOLOWriter(imgFolderName, imgFileName,
|
||||
imageShape, localImgPath=imagePath)
|
||||
writer.verified = self.verified
|
||||
|
||||
for shape in shapes:
|
||||
points = shape['points']
|
||||
label = shape['label']
|
||||
# Add Chris
|
||||
difficult = int(shape['difficult'])
|
||||
bndbox = LabelFile.convertPoints2BndBox(points)
|
||||
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
|
||||
|
||||
writer.save(targetFile=filename, classList=classList)
|
||||
return
|
||||
|
||||
def toggleVerify(self):
|
||||
self.verified = not self.verified
|
||||
|
||||
''' ttf is disable
|
||||
def load(self, filename):
|
||||
import json
|
||||
with open(filename, 'rb') as f:
|
||||
data = json.load(f)
|
||||
imagePath = data['imagePath']
|
||||
imageData = b64decode(data['imageData'])
|
||||
lineColor = data['lineColor']
|
||||
fillColor = data['fillColor']
|
||||
shapes = ((s['label'], s['points'], s['line_color'], s['fill_color'])\
|
||||
for s in data['shapes'])
|
||||
# Only replace data after everything is loaded.
|
||||
self.shapes = shapes
|
||||
self.imagePath = imagePath
|
||||
self.imageData = imageData
|
||||
self.lineColor = lineColor
|
||||
self.fillColor = fillColor
|
||||
|
||||
def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None):
|
||||
import json
|
||||
with open(filename, 'wb') as f:
|
||||
json.dump(dict(
|
||||
shapes=shapes,
|
||||
lineColor=lineColor, fillColor=fillColor,
|
||||
imagePath=imagePath,
|
||||
imageData=b64encode(imageData)),
|
||||
f, ensure_ascii=True, indent=2)
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def isLabelFile(filename):
|
||||
fileSuffix = os.path.splitext(filename)[1].lower()
|
||||
return fileSuffix == LabelFile.suffix
|
||||
|
||||
@staticmethod
|
||||
def convertPoints2BndBox(points):
|
||||
xmin = float('inf')
|
||||
ymin = float('inf')
|
||||
xmax = float('-inf')
|
||||
ymax = float('-inf')
|
||||
for p in points:
|
||||
x = p[0]
|
||||
y = p[1]
|
||||
xmin = min(x, xmin)
|
||||
ymin = min(y, ymin)
|
||||
xmax = max(x, xmax)
|
||||
ymax = max(y, ymax)
|
||||
|
||||
# Martin Kersner, 2015/11/12
|
||||
# 0-valued coordinates of BB caused an error while
|
||||
# training faster-rcnn object detector.
|
||||
if xmin < 1:
|
||||
xmin = 1
|
||||
|
||||
if ymin < 1:
|
||||
ymin = 1
|
||||
|
||||
return (int(xmin), int(ymin), int(xmax), int(ymax))
|
|
@ -1,183 +0,0 @@
|
|||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
import sys
|
||||
from xml.etree import ElementTree
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
from lxml import etree
|
||||
import codecs
|
||||
from libs.constants import DEFAULT_ENCODING
|
||||
from libs.ustr import ustr
|
||||
|
||||
|
||||
XML_EXT = '.xml'
|
||||
ENCODE_METHOD = DEFAULT_ENCODING
|
||||
|
||||
class PascalVocWriter:
|
||||
|
||||
def __init__(self, foldername, filename, imgSize,databaseSrc='Unknown', localImgPath=None):
|
||||
self.foldername = foldername
|
||||
self.filename = filename
|
||||
self.databaseSrc = databaseSrc
|
||||
self.imgSize = imgSize
|
||||
self.boxlist = []
|
||||
self.localImgPath = localImgPath
|
||||
self.verified = False
|
||||
|
||||
def prettify(self, elem):
|
||||
"""
|
||||
Return a pretty-printed XML string for the Element.
|
||||
"""
|
||||
rough_string = ElementTree.tostring(elem, 'utf8')
|
||||
root = etree.fromstring(rough_string)
|
||||
return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode())
|
||||
# minidom does not support UTF-8
|
||||
'''reparsed = minidom.parseString(rough_string)
|
||||
return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD)'''
|
||||
|
||||
def genXML(self):
|
||||
"""
|
||||
Return XML root
|
||||
"""
|
||||
# Check conditions
|
||||
if self.filename is None or \
|
||||
self.foldername is None or \
|
||||
self.imgSize is None:
|
||||
return None
|
||||
|
||||
top = Element('annotation')
|
||||
if self.verified:
|
||||
top.set('verified', 'yes')
|
||||
|
||||
folder = SubElement(top, 'folder')
|
||||
folder.text = self.foldername
|
||||
|
||||
filename = SubElement(top, 'filename')
|
||||
filename.text = self.filename
|
||||
|
||||
if self.localImgPath is not None:
|
||||
localImgPath = SubElement(top, 'path')
|
||||
localImgPath.text = self.localImgPath
|
||||
|
||||
source = SubElement(top, 'source')
|
||||
database = SubElement(source, 'database')
|
||||
database.text = self.databaseSrc
|
||||
|
||||
size_part = SubElement(top, 'size')
|
||||
width = SubElement(size_part, 'width')
|
||||
height = SubElement(size_part, 'height')
|
||||
depth = SubElement(size_part, 'depth')
|
||||
width.text = str(self.imgSize[1])
|
||||
height.text = str(self.imgSize[0])
|
||||
if len(self.imgSize) == 3:
|
||||
depth.text = str(self.imgSize[2])
|
||||
else:
|
||||
depth.text = '1'
|
||||
|
||||
segmented = SubElement(top, 'segmented')
|
||||
segmented.text = '0'
|
||||
return top
|
||||
|
||||
def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult):
|
||||
bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
|
||||
bndbox['name'] = name
|
||||
bndbox['difficult'] = difficult
|
||||
self.boxlist.append(bndbox)
|
||||
|
||||
def appendObjects(self, top):
|
||||
for each_object in self.boxlist:
|
||||
object_item = SubElement(top, 'object')
|
||||
name = SubElement(object_item, 'name')
|
||||
name.text = ustr(each_object['name'])
|
||||
pose = SubElement(object_item, 'pose')
|
||||
pose.text = "Unspecified"
|
||||
truncated = SubElement(object_item, 'truncated')
|
||||
if int(float(each_object['ymax'])) == int(float(self.imgSize[0])) or (int(float(each_object['ymin']))== 1):
|
||||
truncated.text = "1" # max == height or min
|
||||
elif (int(float(each_object['xmax']))==int(float(self.imgSize[1]))) or (int(float(each_object['xmin']))== 1):
|
||||
truncated.text = "1" # max == width or min
|
||||
else:
|
||||
truncated.text = "0"
|
||||
difficult = SubElement(object_item, 'difficult')
|
||||
difficult.text = str( bool(each_object['difficult']) & 1 )
|
||||
bndbox = SubElement(object_item, 'bndbox')
|
||||
xmin = SubElement(bndbox, 'xmin')
|
||||
xmin.text = str(each_object['xmin'])
|
||||
ymin = SubElement(bndbox, 'ymin')
|
||||
ymin.text = str(each_object['ymin'])
|
||||
xmax = SubElement(bndbox, 'xmax')
|
||||
xmax.text = str(each_object['xmax'])
|
||||
ymax = SubElement(bndbox, 'ymax')
|
||||
ymax.text = str(each_object['ymax'])
|
||||
|
||||
def save(self, targetFile=None):
|
||||
root = self.genXML()
|
||||
self.appendObjects(root)
|
||||
out_file = None
|
||||
if targetFile is None:
|
||||
out_file = codecs.open(
|
||||
self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD)
|
||||
else:
|
||||
out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD)
|
||||
|
||||
prettifyResult = self.prettify(root)
|
||||
out_file.write(prettifyResult.decode('utf8'))
|
||||
out_file.close()
|
||||
|
||||
|
||||
class PascalVocReader:
|
||||
|
||||
def __init__(self, filepath):
|
||||
# shapes type:
|
||||
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
|
||||
self.shapes = []
|
||||
self.filepath = filepath
|
||||
self.verified = False
|
||||
try:
|
||||
self.parseXML()
|
||||
except:
|
||||
pass
|
||||
|
||||
def getShapes(self):
|
||||
return self.shapes
|
||||
|
||||
def addShape(self, label, bndbox, difficult):
|
||||
xmin = int(float(bndbox.find('xmin').text))
|
||||
ymin = int(float(bndbox.find('ymin').text))
|
||||
xmax = int(float(bndbox.find('xmax').text))
|
||||
ymax = int(float(bndbox.find('ymax').text))
|
||||
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
|
||||
self.shapes.append((label, points, None, None, difficult))
|
||||
|
||||
def parseXML(self):
|
||||
assert self.filepath.endswith(XML_EXT), "Unsupport file format"
|
||||
parser = etree.XMLParser(encoding=ENCODE_METHOD)
|
||||
xmltree = ElementTree.parse(self.filepath, parser=parser).getroot()
|
||||
filename = xmltree.find('filename').text
|
||||
try:
|
||||
verified = xmltree.attrib['verified']
|
||||
if verified == 'yes':
|
||||
self.verified = True
|
||||
except KeyError:
|
||||
self.verified = False
|
||||
|
||||
for object_iter in xmltree.findall('object'):
|
||||
bndbox = object_iter.find("bndbox")
|
||||
label = object_iter.find('name').text
|
||||
# Add chris
|
||||
difficult = False
|
||||
if object_iter.find('difficult') is not None:
|
||||
difficult = bool(int(object_iter.find('difficult').text))
|
||||
self.addShape(label, bndbox, difficult)
|
||||
return True
|
File diff suppressed because it is too large
Load Diff
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
import sys
|
||||
import os
|
||||
from xml.etree import ElementTree
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
from lxml import etree
|
||||
import codecs
|
||||
from libs.constants import DEFAULT_ENCODING
|
||||
|
||||
TXT_EXT = '.txt'
|
||||
ENCODE_METHOD = DEFAULT_ENCODING
|
||||
|
||||
class YOLOWriter:
|
||||
|
||||
def __init__(self, foldername, filename, imgSize, databaseSrc='Unknown', localImgPath=None):
|
||||
self.foldername = foldername
|
||||
self.filename = filename
|
||||
self.databaseSrc = databaseSrc
|
||||
self.imgSize = imgSize
|
||||
self.boxlist = []
|
||||
self.localImgPath = localImgPath
|
||||
self.verified = False
|
||||
|
||||
def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult):
|
||||
bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
|
||||
bndbox['name'] = name
|
||||
bndbox['difficult'] = difficult
|
||||
self.boxlist.append(bndbox)
|
||||
|
||||
def BndBox2YoloLine(self, box, classList=[]):
|
||||
xmin = box['xmin']
|
||||
xmax = box['xmax']
|
||||
ymin = box['ymin']
|
||||
ymax = box['ymax']
|
||||
|
||||
xcen = float((xmin + xmax)) / 2 / self.imgSize[1]
|
||||
ycen = float((ymin + ymax)) / 2 / self.imgSize[0]
|
||||
|
||||
w = float((xmax - xmin)) / self.imgSize[1]
|
||||
h = float((ymax - ymin)) / self.imgSize[0]
|
||||
|
||||
# PR387
|
||||
boxName = box['name']
|
||||
if boxName not in classList:
|
||||
classList.append(boxName)
|
||||
|
||||
classIndex = classList.index(boxName)
|
||||
|
||||
return classIndex, xcen, ycen, w, h
|
||||
|
||||
def save(self, classList=[], targetFile=None):
|
||||
|
||||
out_file = None #Update yolo .txt
|
||||
out_class_file = None #Update class list .txt
|
||||
|
||||
if targetFile is None:
|
||||
out_file = open(
|
||||
self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD)
|
||||
classesFile = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt")
|
||||
out_class_file = open(classesFile, 'w')
|
||||
|
||||
else:
|
||||
out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD)
|
||||
classesFile = os.path.join(os.path.dirname(os.path.abspath(targetFile)), "classes.txt")
|
||||
out_class_file = open(classesFile, 'w')
|
||||
|
||||
|
||||
for box in self.boxlist:
|
||||
classIndex, xcen, ycen, w, h = self.BndBox2YoloLine(box, classList)
|
||||
# print (classIndex, xcen, ycen, w, h)
|
||||
out_file.write("%d %.6f %.6f %.6f %.6f\n" % (classIndex, xcen, ycen, w, h))
|
||||
|
||||
# print (classList)
|
||||
# print (out_class_file)
|
||||
for c in classList:
|
||||
out_class_file.write(c+'\n')
|
||||
|
||||
out_class_file.close()
|
||||
out_file.close()
|
||||
|
||||
|
||||
|
||||
class YoloReader:
|
||||
|
||||
def __init__(self, filepath, image, classListPath=None):
|
||||
# shapes type:
|
||||
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
|
||||
self.shapes = []
|
||||
self.filepath = filepath
|
||||
|
||||
if classListPath is None:
|
||||
dir_path = os.path.dirname(os.path.realpath(self.filepath))
|
||||
self.classListPath = os.path.join(dir_path, "classes.txt")
|
||||
else:
|
||||
self.classListPath = classListPath
|
||||
|
||||
# print (filepath, self.classListPath)
|
||||
|
||||
classesFile = open(self.classListPath, 'r')
|
||||
self.classes = classesFile.read().strip('\n').split('\n')
|
||||
|
||||
# print (self.classes)
|
||||
|
||||
imgSize = [image.height(), image.width(),
|
||||
1 if image.isGrayscale() else 3]
|
||||
|
||||
self.imgSize = imgSize
|
||||
|
||||
self.verified = False
|
||||
# try:
|
||||
self.parseYoloFormat()
|
||||
# except:
|
||||
# pass
|
||||
|
||||
def getShapes(self):
|
||||
return self.shapes
|
||||
|
||||
def addShape(self, label, xmin, ymin, xmax, ymax, difficult):
|
||||
|
||||
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
|
||||
self.shapes.append((label, points, None, None, difficult))
|
||||
|
||||
def yoloLine2Shape(self, classIndex, xcen, ycen, w, h):
|
||||
label = self.classes[int(classIndex)]
|
||||
|
||||
xmin = max(float(xcen) - float(w) / 2, 0)
|
||||
xmax = min(float(xcen) + float(w) / 2, 1)
|
||||
ymin = max(float(ycen) - float(h) / 2, 0)
|
||||
ymax = min(float(ycen) + float(h) / 2, 1)
|
||||
|
||||
xmin = int(self.imgSize[1] * xmin)
|
||||
xmax = int(self.imgSize[1] * xmax)
|
||||
ymin = int(self.imgSize[0] * ymin)
|
||||
ymax = int(self.imgSize[0] * ymax)
|
||||
|
||||
return label, xmin, ymin, xmax, ymax
|
||||
|
||||
def parseYoloFormat(self):
|
||||
bndBoxFile = open(self.filepath, 'r')
|
||||
for bndBox in bndBoxFile:
|
||||
classIndex, xcen, ycen, w, h = bndBox.strip().split(' ')
|
||||
label, xmin, ymin, xmax, ymax = self.yoloLine2Shape(classIndex, xcen, ycen, w, h)
|
||||
|
||||
# Caveat: difficult flag is discarded when saved as yolo format.
|
||||
self.addShape(label, xmin, ymin, xmax, ymax, False)
|
|
@ -34,7 +34,6 @@
|
|||
<file alias="resetall">resources/icons/resetall.png</file>
|
||||
<file alias="verify">resources/icons/verify.png</file>
|
||||
<file alias="strings">resources/strings/strings.properties</file>
|
||||
<file alias="strings-zh-TW">resources/strings/strings-zh-TW.properties</file>
|
||||
<file alias="strings-zh-CN">resources/strings/strings-zh-CN.properties</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -27,7 +27,7 @@ boxLineColorDetail=选择线框颜色
|
|||
originalsize=原始大小
|
||||
resetAllDetail=重置所有设定
|
||||
zoomoutDetail=放大画面
|
||||
save=保存
|
||||
save=确认
|
||||
saveAs=另存为
|
||||
fitWinDetail=缩放到当前窗口大小
|
||||
openDir=打开目录
|
||||
|
@ -94,3 +94,4 @@ ok=确认
|
|||
autolabeling=自动标注中
|
||||
hideBox=隐藏所有标注
|
||||
showBox=显示所有标注
|
||||
saveLabel=保存标记结果
|
|
@ -1,70 +0,0 @@
|
|||
saveAsDetail=將標籤保存到其他文件
|
||||
changeSaveDir=改變存放目錄
|
||||
openFile=開啟檔案
|
||||
shapeLineColorDetail=更改線條顏色
|
||||
resetAll=重置
|
||||
crtBox=創建區塊
|
||||
crtBoxDetail=畫一個區塊
|
||||
dupBoxDetail=複製區塊
|
||||
verifyImg=驗證圖像
|
||||
zoominDetail=放大
|
||||
verifyImgDetail=驗證圖像
|
||||
saveDetail=將標籤存到
|
||||
openFileDetail=打開圖像
|
||||
fitWidthDetail=調整到窗口寬度
|
||||
tutorial=YouTube教學
|
||||
editLabel=編輯標籤
|
||||
openAnnotationDetail=打開標籤文件
|
||||
quit=結束
|
||||
shapeFillColorDetail=更改填充顏色
|
||||
closeCurDetail=關閉目前檔案
|
||||
closeCur=關閉
|
||||
deleteImg=刪除圖像
|
||||
deleteImgDetail=刪除目前圖像
|
||||
fitWin=調整到跟窗口一樣大小
|
||||
delBox=刪除選取區塊
|
||||
boxLineColorDetail=選擇框線顏色
|
||||
originalsize=原始大小
|
||||
resetAllDetail=重設所有設定
|
||||
zoomoutDetail=畫面放大
|
||||
save=儲存
|
||||
saveAs=另存為
|
||||
fitWinDetail=縮放到窗口一樣
|
||||
openDir=開啟目錄
|
||||
copyPrevBounding=複製當前圖像中的上一個邊界框
|
||||
showHide=顯示/隱藏標籤
|
||||
changeSaveFormat=更改儲存格式
|
||||
shapeFillColor=填充顏色
|
||||
quitApp=離開本程式
|
||||
dupBox=複製區塊
|
||||
delBoxDetail=刪除區塊
|
||||
zoomin=放大畫面
|
||||
info=資訊
|
||||
openAnnotation=開啟標籤
|
||||
prevImgDetail=上一個圖像
|
||||
fitWidth=縮放到跟畫面一樣寬
|
||||
zoomout=縮小畫面
|
||||
changeSavedAnnotationDir=更改預設標籤存的目錄
|
||||
nextImgDetail=下一個圖像
|
||||
originalsizeDetail=放大到原始大小
|
||||
prevImg=上一個圖像
|
||||
tutorialDetail=顯示示範內容
|
||||
shapeLineColor=形狀線條顏色
|
||||
boxLineColor=日期分隔線顏色
|
||||
editLabelDetail=修改所選區塊的標籤
|
||||
nextImg=下一張圖片
|
||||
useDefaultLabel=使用預設標籤
|
||||
useDifficult=有難度的
|
||||
boxLabelText=區塊的標籤
|
||||
labels=標籤
|
||||
autoSaveMode=自動儲存模式
|
||||
singleClsMode=單一類別模式
|
||||
displayLabel=顯示類別
|
||||
fileList=檔案清單
|
||||
files=檔案
|
||||
iconList=XX
|
||||
icon=XX
|
||||
advancedMode=進階模式
|
||||
advancedModeDetail=切到進階模式
|
||||
showAllBoxDetail=顯示所有區塊
|
||||
hideAllBoxDetail=隱藏所有區塊
|
|
@ -14,7 +14,7 @@ prevImg=Prev Image
|
|||
prevImgDetail=Open the previous Image
|
||||
verifyImg=Verify Image
|
||||
verifyImgDetail=Verify Image
|
||||
save=Save
|
||||
save=Check
|
||||
saveDetail=Save the labels to a file
|
||||
changeSaveFormat=Change save format
|
||||
saveAs=Save As
|
||||
|
@ -94,3 +94,4 @@ ok=OK
|
|||
autolabeling=Automatic Labeling
|
||||
hideBox=Hide All Box
|
||||
showBox=Show All Box
|
||||
saveLabel=Save Label
|
BIN
doc/joinus.PNG
BIN
doc/joinus.PNG
Binary file not shown.
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 272 KiB |
Loading…
Reference in New Issue