0%

Ailabel+react实现图片标注

应用场景

对图片进行标注,包括画矩形框、多边形框和线条。官网传送门

实现原理

1、环境准备
1
2
// 安装ailabel依赖
# npm install ailabel --save
1
2
// 页面引入ailabel
const AILabel = require("AILabel");
2、Map对象实例化
1
const gMap = new AILabel.Map("AiLabelImg", { zoom: 0, cx: 0, cy: 0, zoomMax: 400 * 10, zoomMin: 400 / 10, autoPan: true, drawZoom: true });
1
2
3
4
5
6
render()
{
return (
<div id="AiLabelImg"></div>
);
}

map实例参数说明:

参数 说明
zoom 初始缩放级别
cx 初始中心点坐标x
cy 初始中心点坐标y
zoomMax 缩放最大级别
zoomMin 缩放最小级别
autoPan 绘制过程中是否禁止自动平移
drawZoom 绘制过程中是否禁止滑轮缩放
autoFeatureSelect 默认是否双击选中feature
3、添加图像层

为避免图像在容器中出现变形,先计算容器的宽高和图片的宽高,根据宽高比例自适应显示图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 获取容器的宽高
initWidth = document.getElementById("AiLabelImg").clientWidth;
initHeight = document.getElementById("AiLabelImg").clientHeight;
const image = new Image();
image.src = imgUrl;// 图片地址
image.onload = () =>
{
imgInitW = image.width;
imgInitH = image.height;
imgSjW = initWidth;
imgSjH = initHeight;
const sR = imgInitW / imgInitH;
const iR = initWidth / initHeight;
// 如果图片宽度大于高度 则宽度百分百 高度自适应
if (sR >= iR)
{
imgSjH = Math.floor(initWidth / imgInitW * imgInitH);
}
else
{
// 如果图片高度大于宽度 则高度百分百 宽度自适应
imgSjW = Math.floor(initHeight / imgInitH * imgInitW);
}
// 图片层实例\添加
const gImageLayer = new AILabel.Layer.Image("img", props.url, { h: imgSjH, w: imgSjW }, {});
gMap.addLayer(gImageLayer);
const gFeatureLayer = new AILabel.Layer.Feature(`featureid`, { opacity: 1, zIndex: 4 });
};
}
4、画矩形框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const gTextStyle = new AILabel.Style({ strokeColor: "#E43446", lineWeight: 2, fillColor: "rgba(0,0,0,0)" });
// 设置模式为矩形框
gMap.setMode("drawRect", gTextStyle);
// 监听绘制过程
// 绘制完成,将绘制的坐标值显示
const count = 0;
gMap.events.on("geometryDrawDone", (type, points) =>
{
// 每次画框的featureId都需要不一样,可以用count计数
feature = new AILabel.Feature.Rect(`rectid${count}`, points, { name: "矩形框" }, style);
gFeatureLayer.addFeature(feature);
count += 1;
});
// 编辑完成,替换新的点坐标值
gMap.events.on("geometryEditDone", (type, feature, newPoints) =>
{
feature.show();
feature.update({ points: newPoints });
});
// 绘制过程中,将之前的框先隐藏
gMap.events.on("geometryEditing", (type, feature) =>
{
feature.hide();
});
5、完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import React, {
useImperativeHandle,
forwardRef,
useEffect
} from "react";
import styles from "./index.less";
import { message } from "antd";
import { canvasToAil, aiToCanvas } from "@/utils/utils";
import PropTypes from "prop-types";

const AILabel = require("AILabel");

let gMap;
let gImageLayer;
let count = 0;
let gFeatureLayer;
let selectedFeature = null;
let showFlag = true;
let initWidth = 700;// 盒子宽
let initHeight = 400;// 盒子的高
let initZoom;// 初始放大值
let initCenter;// 初始中心点
let gTextStyle; // 画笔样式
let preTextStyle;
let labelList = [];// 所有框的坐标和标签数据
let current = {}; // 当前框的坐标和标签数据
/* 用于坐标值转换参数 */
let imgInitH; // 图片的初始宽度
let imgInitW; // 图片的初始高度
let imgSjH;
let imgSjW;
const MONITOR_CANVAS = (props, ref) =>
{
const textAndRect = (points, created, index, isPreAnnotation) =>
{
let feature;
let style;
if (isPreAnnotation === "1")
{
style = preTextStyle;
}
else
{
style = gTextStyle;
}
// 实例化矩形形
if (props.type === "2" || props.type === "1")
{
feature = new AILabel.Feature.Rect(`rectid${count}`, points, { name: "矩形框" }, style);
}
else if (props.type === "4")
{
feature = new AILabel.Feature.Polygon(`rectid${count}`, points, { name: "多变型" }, style);
}
gFeatureLayer.addFeature(feature);
count += 1;
};
useEffect(() =>
{
if (props.url)
{
initWidth = document.getElementById("AiLabelImgWrapper").clientWidth;
initHeight = document.getElementById("AiLabelImgWrapper").clientHeight;
const image = new Image();
image.src = props.url;
image.onload = () =>
{
imgInitW = image.width;
imgInitH = image.height;
imgSjW = initWidth;
imgSjH = initHeight;
const sR = imgInitW / imgInitH;
const iR = initWidth / initHeight;
// 如果图片宽度大于高度 则宽度百分百 高度自适应
if (sR >= iR)
{
imgSjH = Math.floor(initWidth / imgInitW * imgInitH);
}
else
{
// 如果图片高度大于宽度 则高度百分百 宽度自适应
imgSjW = Math.floor(initHeight / imgInitH * imgInitW);
}
count = 0;
labelList = [];
// document.getElementById("AiLabelImg").style.width = `${imgSjW}px`;
// document.getElementById("AiLabelImg").style.height = `${imgSjH}px`;
gMap = new AILabel.Map("AiLabelImg", { zoom: 0, cx: 0, cy: 0, zoomMax: 400 * 10, zoomMin: 400 / 10, autoPan: true, drawZoom: true });
gMap.tipLayer.hideTips(); // 关闭tip提示
// 图片层实例\添加
gImageLayer = new AILabel.Layer.Image("img", props.url, { h: imgSjH, w: imgSjW }, {});
gMap.addLayer(gImageLayer);
gFeatureLayer = new AILabel.Layer.Feature(`featureid`, { opacity: 1, zIndex: 4 });
gMap.addLayer(gFeatureLayer);
initZoom = gMap.getZoom();
initCenter = gMap.getCenter();
// 样式
gTextStyle = new AILabel.Style({ strokeColor: "#E43446", lineWeight: 2, fillColor: "rgba(0,0,0,0)" });
preTextStyle = new AILabel.Style({ strokeColor: "#0FA883", lineWeight: 2, fillColor: "rgba(0,0,0,0)" });
gMap.events.on("featureSelected", feature =>
{
// TODO 框被选中
});
gMap.events.on("featureStatusReset", () =>
{
// TODO 框失去焦点
});
};
}
}, [props.url]);
useImperativeHandle(ref, () => ({
// 窗口自适应
autoWindow()
{
gMap.centerAndZoom(initCenter, initZoom);
},
//回复浏览模式
autoWidth()
{
gMap.setMode("pan");
},
// 原始尺寸
initWidth()
{
gMap.centerAndZoom(initCenter, initZoom);
},
imgZoomIn()
{
gMap.zoomIn();
},
imgZoomOut()
{
gMap.zoomOut();
},
draw()
{
if (gFeatureLayer)
{
if (props.type === "2" || props.type === "1")
{
gMap.setMode("drawRect", gTextStyle);
}
else if (props.type === "4")
{
gMap.setMode("drawPolygon", gTextStyle);
}
gMap.events.on("geometryDrawDone", (type, points) =>
{
textAndRect(points, true, 0, null, "0");
});
gMap.events.on("geometryEditDone", (type, feature, newPoints) =>
{
feature.show();
feature.update({ points: newPoints });
});
gMap.events.on("geometryEditing", (type, feature) =>
{
feature.hide();
});
}
},
// 复制框
copyLabel()
{
if (selectedFeature === null)
{
message.error("未选择框进行复制操作");
}
else
{
const copyPoint = [];
selectedFeature.points.forEach(item =>
{
copyPoint.push({
x: item.x + 10,
y: item.y - 10
});
});
selectedFeature.deActive();
textAndRect(copyPoint, true, 0, null, "0");
}
},
// 删除框
deleteLabel()
{
if (selectedFeature === null)
{
message.error("未选择框进行删除操作");
}
else
{
// 修改操作
gFeatureLayer.removeFeature(selectedFeature);
}
},
showHideAll()
{
const allFeatures = gFeatureLayer.getAllFeatures(); // 返回所有要素数据
allFeatures.forEach(feature =>
{
if (showFlag)
{
feature.hide();
}
else
{
feature.show();
}
});
showFlag = !showFlag;
}
}));

return (
<div id="AiLabelImgWrapper" className={styles["ailabel-img"]}>
<div id="AiLabelImg" className={styles["ailabel-img"]}></div>
</div>
);
};
export default forwardRef(MONITOR_CANVAS);
-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!