ImageLabeler-master(图片标注工具)
可以参考配套的文档(作者的文档)
技术要点:类的继承,友元函数,自动补全,按键事件,listwidget,json数据解析,qlist,qmap,使用const宏定义,
类的继承:class CubeAnnotationItem : public AnnotationItem
友元函数:friend AnnotationContainer;
宏定义:
// for image result
const QString SUFFIX_SEG_COLOR("_segment_color.png");
const QString SUFFIX_SEG_LABELID("_segment_labelId.png");
const QString SUFFIX_SEG3D_COLOR("_segment3d_color.png");
const QString SUFFIX_SEG3D_LABELID("_segment3d_labelId.png");
/* LabelDialog(选择label的一个对话框)
* 简介:
* 界面:ui->lineEdit,LabelLineEdit 类型,绑定了 ui->listWidget(详见 LabelLineEdit 的声明)
* ui->listWidget,用于显示label,且有一个纯色的icon代表label对应的颜色
*/
/* LabelLineEdit (自动补全,绑定QlistWidget,按键上下选中)
*
简介:用作LabelDialog的一个部件,可绑定一个QListWidget实现一些联动的效果
*
功能:a.
输入时根据
labelListWidget
的
items
的
text
提供自动补全
*
b.
当
labelListWidget
的选中项改变时,相应的改变输入框中的text
*
c.
按下
up和down
键,可改变
labelListWidget
的选中项。
*/
class LabelLineEdit: public QLineEdit{ friend LabelDialog;
public: // labelListWidget 构造时默认为 nullptr explicit LabelLineEdit(QWidget *parent = nullptr);
// 设置 labelListWidget,并实现功能 a 和 b (自动补全 和 相应list的选中) void setLabelListWidget(QListWidget* listWidget);
protected: // 处理 up 和 down 键的按下事件,实现功能 c (改变list的选中项) void keyPressEvent(QKeyEvent *event);
private: QListWidget* labelListWidget; }; |
LabelLineEdit::LabelLineEdit(QWidget *parent):QLineEdit(parent), labelListWidget(nullptr) { }
void LabelLineEdit::setLabelListWidget(QListWidget *listWidget){ labelListWidget = listWidget;
// 功能 a: 输入时根据 labelListWidget 的 items 的 text 提供自动补全 QCompleter* completer = new QCompleter(this); completer->setCompletionMode(QCompleter::InlineCompletion); completer->setModel(labelListWidget->model()); this->setCompleter(completer);
// 功能 b: 当 labelListWidget 的选中项改变时,相应的改变输入框中的text connect(labelListWidget, &QListWidget::currentItemChanged, [this](QListWidgetItem *currentItem){ this->setText(currentItem->text()); }); }
void LabelLineEdit::keyPressEvent(QKeyEvent *event){ // 功能 c: 按下 up和down 键,可改变 labelListWidget 的选中项 if (event->key() == Qt::Key_Up){ if (labelListWidget!=nullptr && labelListWidget->count()>0){ int newRow = std::max(0, std::min(labelListWidget->currentRow() - 1, labelListWidget->count()-1)); labelListWidget->setCurrentRow(newRow); this->setText(labelListWidget->currentItem()->text()); } }else if (event->key() == Qt::Key_Down){ if (labelListWidget!=nullptr && labelListWidget->count()>0){ int newRow = std::max(0, std::min(labelListWidget->currentRow() + 1, labelListWidget->count()-1)); labelListWidget->setCurrentRow(newRow); this->setText(labelListWidget->currentItem()->text()); } }else{ QLineEdit::keyPressEvent(event); } } |
/* LabelManager -LabelProperty(json格式存储label数据,解析json数据)
* 简介:用来存储label的相关性质的数据类型,存储了label、color、visible、id
* Json:该类的数据与json相互转化时的格式为
* {
* "color": [
* Double, // R
* Double, // G
* Double // B
* ],
* "id": Double,
* "label": String,
* "visible": Bool
* }
//---------------------------------LabelProperty-------------------------------------//
LabelProperty::LabelProperty(QString label, QColor color, bool visible, int id) : label(label), color(color), visible(visible),id(id) { }
LabelProperty::LabelProperty():label(), color(), visible(true), id(-1) { }
QJsonObject LabelProperty::toJsonObject(){ QJsonArray colorJson; colorJson.append(color.red()); colorJson.append(color.green()); colorJson.append(color.blue()); QJsonObject json; json.insert("label", label); json.insert("id", id); json.insert("color", colorJson); json.insert("visible", visible); return json; }
void LabelProperty::fromJsonObject(QJsonObject json) { if (json.contains("label")){ QJsonValue value = json.value("label"); if (value.isString()){ label = value.toString(); }else{ throw JsonException("value of <label> is illegal"); } }else{ throw JsonException("no data <label>"); }
if (json.contains("color")){ QJsonValue value = json.value("color"); if (value.isArray()){ QJsonArray array = value.toArray(); if (!array.at(0).isDouble() || !array.at(1).isDouble() || !array.at(2).isDouble()){ throw JsonException("value of <color> is illegal"); } int r=static_cast<int>(array.at(0).toDouble()); int g=static_cast<int>(array.at(1).toDouble()); int b=static_cast<int>(array.at(2).toDouble()); color = QColor(r,g,b); }else{ throw JsonException("value of <color> is illegal"); } }else{ throw JsonException("no data <color>"); }
if (json.contains("visible")){ QJsonValue value = json.value("visible"); if (value.isBool()){ visible = value.toBool(); }else{ throw JsonException("value of <visible> is illegal"); } }else{ throw JsonException("no data <visible>"); }
if (json.contains("id")){ QJsonValue value = json.value("id"); if (value.isDouble()){ id = static_cast<int>(value.toDouble()); }else{ throw JsonException("value of <id> is illegal"); } }else{ throw JsonException("no data <id>"); } } |
/* LabelManager(增加删除修改查询label,)
* 简介:管理label相关的数据的类,主要与ui->labelListWidget同步
* 注意:在addLabel前应检查是否已经存在该label
*
* Json:该类的数据与json相互转化时的格式为
* [ LabelProperty, LabelProperty, ... ] // Array中的元素的格式为LabelProperty的格式
*/
使用map存储labels;
QMap<QString, LabelProperty> labels;
/* CustomListWidget
* 简介:作为MainWindow中的部件,根据所需提供了更方便的函数接口以及事件响应
* 是MainWindow中 annoListWidget、labelListWidget、fileListWidget的类型
* 界面:每个 item 从左至右组成为:一个用于check的小框(可有可无),一个icon(可有可无),以及text
* 事件:鼠标若点击到listwidget空白区域,即没有item的区域,则清除list的所有选中状态
* 键盘按下 up/down 键改变list的选中项
* 注意:a. 该类应为单选模式(SingleSelection),且不应该被更改;
* b. 该类不应出现不同的item的text相同的情况,
* 这种情况在该项目中是不会发生的,因为:
* 1. annoList中每个label相同的标注被一个各不相同的instance id标识
* 2. fileList中不会出现文件重名的情况
* 3. labelList在加入item时会根据labelManager检查label是否已经存在
*/
Listwidget中的右键菜单
void MainWindow::provideLabelContextMenu(const QPoint &pos) { QPoint globalPos = ui->labelListWidget->mapToGlobal(pos); QModelIndex modelIdx = ui->labelListWidget->indexAt(pos); if (!modelIdx.isValid()) return; int row = modelIdx.row(); auto item = ui->labelListWidget->item(row);
QMenu submenu; submenu.addAction("Change Color"); submenu.addAction("Delete"); QAction* rightClickItem = submenu.exec(globalPos); if (rightClickItem){ if (rightClickItem->text().contains("Delete")){ removeLabelRequest(item->text()); }else if (rightClickItem->text().contains("Change Color")){ QColor color = QColorDialog::getColor(Qt::white, this, "Choose Color"); if (color.isValid()){ labelManager.setColor(item->text(),color); } } } } |
/* FileManager(静态函数方便调用,)
* 简介:管理与文件相关的状态的类型
* 功能:a. 实现静态函数作为更便捷的处理文件名、读写文件的接口
* b. 打开文件或文件夹,生成默认的输出文件名(包括label和annotation的输出文件)
* c. 记录距离上次保存后是否有未保存的修改
* d. 可选中文件,切换显示的图像,并与 ui->fileListWidget 同步
*/
/* RectAnnotationItem(绘制矩形,标记用json格式存储)
* 简介:用于2D Detection的标注类型,比父类多记录了一个像素坐标的矩形 rect 表示bounding box
*
* Json:该类的数据与json相互转化时的格式如下,points表示两个矩形的左上和右下顶点
* {
* "label": String
* "id": Double
* "points": [
* [
* Double,
* Double
* ],
* [
* Double,
* Double
* ]
* ]
* }
*/
/* SegStroke(绘制多边形)
* 简介:用于表示2D分割标注中一 "笔画" 的数据类型
* 笔画:笔画共分为三种,分别为圆形画笔、方形画笔、轮廓
* 对应的type的值分别为 "circle_pen" "square_pen" "contour"
* 当type为画笔时,points中记录的是画笔中心经过路径,penWidth记录画笔的大小;
* 当type为轮廓时,points依次记录轮廓上的点,并用多边形连接它们,特别地,当点比较稠密时,可视为光滑闭合曲线
* 当type为轮廓时,penWidth无意义,一般记为1,也可不记
*
* Json:该类的数据与json相互转化时的格式如下,
* {
* "type": String // 取值为 "circle_pen" 或 "square_pen" 或 "contour"
* "penWidth": Double
* "points": [ [Double, Double], [Double, Double], ... ] //Array类型,表示xy坐标
* }
*/
/* Basic_SegAnnotationItem
* 简介:用于表示Segmentation标注的类模板,该项目中认为一个分割标注可由多个笔画(stroke_type类型)合并组成
* 对应2D和3D分割标注,stroke_type分别为SegStroke和SegStroke3D
*
* Json:该类的数据与json相互转化时的格式如下
* {
* "label": String
* "id": Double
* "strokes": [ stroke_type, stroke_type, ... ] // Array的元素的格式为SegStroke或SegStroke3D对应的格式
* }
*/
Common(一些基本的功能,多个命名空间)
|