如何使用 Fabric.js 实现动态图片标注
通过本文的详细步骤和注意事项,你可以轻松地在项目中实现基于 Fabric.js 的图片标注功能。在项目中,我们需要根据后端返回的坐标对图片进行标注,比如画矩形框或多边形。COCO格式中的bbox表示的是对象在图像中的边界框坐标和尺寸信息,可用于目标检测等计算机视觉任务。在获取到数据后,我们需要加载图片并进行标注。我们从后端获取数据后,开始渲染图片,并在图片加载到画布中后,进行标注绘制。矩形标注是根
1.Fabric.js 实现图片标注
在项目中,我们需要根据后端返回的坐标对图片进行标注,比如画矩形框或多边形。本文将详细介绍如何使用 Fabric.js 实现这一功能,包括初始化 Fabric.js、加载图片、绘制标注等步骤。
1.1.初始化 Fabric.js
首先,我们需要在页面初始化时对 Fabric.js 进行初始化。需要注意的是,初始化操作应该在 nextTick
中执行,否则可能不会生效。另外,初始化只需进行一次,不要重复初始化。
const canvas = ref();
const fabricInit = () => {
canvas.value = new fabric.Canvas('fabric'); // 初始化 fabric 画布
};
onMounted(() => {
nextTick(() => {
fabricInit();
});
});
1.2.加载图片并进行标注
加载图片是进行标注的第一步。我们从后端获取数据后,开始渲染图片,并在图片加载到画布中后,进行标注绘制。
const { execute, isLoading } = useAsyncStateWithError(
() => {
return getAnnotatorData(props.fileId); // 获取标注数据
},
{},
{
onSuccess(data) {
categoriesAnnotations.value = data.categories; // 存储分类标注数据
checkedVals.value = categoriesAnnotations.value.map((item) => item.name); // 存储选中的分类名称
allAnnotationsCount.value = categoriesAnnotations.value.reduce(
(acc, category) => {
return acc + category.annotations.length; // 计算所有标注的总数
},
0,
);
addDetectionImage(props.imgUrl); // 加载图片
},
immediate: false,
},
);
在获取到数据后,我们需要加载图片并进行标注。这里要注意,我们需要在每次加载新图片前清除画布,以防止图片叠加:
const maxWidth = ref(1200);
const maxHeight = ref(600);
const scaleX = ref();
const scaleY = ref();
const addDetectionImage = (imageUrl) => {
// 清除原来的画布,防止图片叠加
if (canvas.value) {
canvas.value.clear();
}
fabric.Image.fromURL(
imageUrl,
(fabricImg) => {
// 计算缩放比例
const scale = Math.min(
maxWidth.value / fabricImg.width!, // 计算宽度缩放比例
maxHeight.value / fabricImg.height!, // 计算高度缩放比例
);
// 计算缩放后的尺寸
const scaledWidth = fabricImg.width! * scale;
const scaledHeight = fabricImg.height! * scale;
// 设置画布的宽度和高度为缩放后的尺寸
canvas.value.setWidth(scaledWidth);
canvas.value.setHeight(scaledHeight);
// 设置图片属性
fabricImg.set({
selectable: false, // 不可选中
hasControls: false, // 无控制点
hasRotatingPoint: true, // 允许旋转
lockRotation: false, // 不锁定旋转
lockScalingX: false, // 不锁定水平缩放
lockScalingY: false, // 不锁定垂直缩放
scaleX: (scaleX.value = scaledWidth / fabricImg.width!), // 设置水平缩放比例
scaleY: (scaleY.value = scaledHeight / fabricImg.height!), // 设置垂直缩放比例
});
// 将图片添加到画布中
canvas.value.add(fabricImg);
draw(categoriesAnnotations.value); // 绘制标注
},
{ crossOrigin: 'anonymous' }, // 允许跨域
);
};
1.2.1.计算图片的缩放比例
为了确保图片能在画布中完整显示,我们需要计算图片的缩放比例。具体步骤如下:
- 计算宽度和高度的缩放比例:分别为
maxWidth / fabricImg.width
和maxHeight / fabricImg.height
。 - 选择较小的缩放比例:以确保图片的宽度和高度都能在画布中显示完整。
- 计算缩放后的尺寸:使用较小的缩放比例,计算出缩放后的宽度和高度。
- 设置画布尺寸:将画布的宽度和高度设置为缩放后的尺寸。
1.3.绘制多边形标注
多边形标注是根据后端返回的坐标绘制的,我们将坐标转换为 fabric.Point
对象,并通过 fabric.Polygon
绘制多边形。
/**
* 绘制多边形标注
* @param {Array} polygonPoints 多边形的坐标数组
* @param {string} borderColor 多边形的边框颜色
* @param {string} labelText 多边形的标签文本
*/
function processPolygon(polygonPoints, borderColor, labelText) {
// 处理多边形坐标
const points = polygonPoints[0].reduce(
(acc, val, index, array) => {
if (index % 2 === 0) {
acc.push(
new fabric.Point(val * scaleX.value, array[index + 1] * scaleY.value), // 转换为 fabric.Point 对象
);
}
return acc;
},
[],
);
// 创建多边形对象
const polygon = new fabric.Polygon(points, {
stroke: borderColor, // 边框颜色
strokeWidth: 2, // 边框宽度
fill: borderColor, // 填充颜色
opacity: 0.6, // 透明度
selectable: false, // 不可选中
});
// 创建文本对象
const text = processText(labelText, polygon.left, polygon.top, borderColor);
// 将多边形和文本添加到画布中
canvas.value.add(polygon, text);
}
1.4. 绘制矩形标注
矩形标注是根据后端返回的矩形坐标绘制的,我们将坐标转换为 fabric.Rect
对象进行绘制。
这里我们使用的是COCO数据集,他的bbox表示为
COCO数据集bbox
在COCO (Common Objects in Context)数据集中,bbox (boundingbox)表示的是对象的边界框坐标。具体来说:
- bbox是一个包含4个元素的列表或元组,表示为[x, y, width,height]。
- x, y表示边界框左上角的坐标。
- width和height分别表示边界框的宽度和高度。
例如,一个边界框的坐标为[100,200,50,80],表示:
- 左上角的x坐标为100
- 左上角的y坐标为200
- 边界框的宽度为50
- 边界框的高度为80
这样的坐标和尺寸信息可以用来定位和描述图像中的对象位置。COCO格式中的bbox表示的是对象在图像中的边界框坐标和尺寸信息,可用于目标检测等计算机视觉任务。
/**
* 绘制矩形标注
* @param {Array} rectangleCoords 矩形的坐标数组 [x1, y1, x2, y2]
* @param {string} borderColor 矩形的边框颜色
* @returns {fabric.Rect} 返回绘制的矩形对象
*/
function processRectangle(rectangleCoords, borderColor) {
const [x1, y1, x2, y2] = rectangleCoords; // 提取矩形的四个顶点坐标
// 创建矩形对象
return new fabric.Rect({
left: x1 * scaleX.value, // 左上角 x 坐标
top: y1 * scaleY.value, // 左上角 y 坐标
width: x2 * scaleX.value, // 宽度
height: y2 * scaleY.value, // 高度
stroke: borderColor, // 边框颜色
strokeWidth: 2, // 边框宽度
fill: borderColor, // 填充颜色
opacity: 0.6, // 透明度
selectable: false, // 不可选中
evented: false, // 事件不可用
});
}
1.5. 绘制文本
文本标注用于显示标注的名称,我们使用 fabric.Text
对象进行绘制。
/**
* 绘制文本标注
* @param {string} labelText 文本内容
* @param {number} leftPos 文本的左侧位置
* @param {number} topPos 文本的顶部位置
* @param {string} bgColor 文本背景颜色
* @returns {fabric.Text} 返回绘制的文本对象
*/
function processText(labelText, leftPos, topPos, bgColor) {
// 创建文本对象
return new fabric.Text(labelText, {
fontSize: 14, // 字体大小
fill: 'white', // 字体颜色
left: leftPos, // 左侧位置
top: topPos - 14, // 顶部位置(略微向上偏移)
backgroundColor: bgColor, // 背景颜色
padding: 5, // 内边距
selectable: false, // 不可选中
evented: false, // 事件不可用
});
}
1.6. 绘制标注集合
根据分类标注数据进行遍历,并根据模型类型绘制相应的标注。
/**
* 绘制矩形和文本标注
* @param {Array} rectangleCoords 矩形的坐标数组 [x1, y1, x2, y2]
* @param {string} borderColor 矩形的边框颜色
* @param {string} labelText 文本内容
*/
function drawRectAndText(rectangleCoords, borderColor, labelText) {
const rect = processRectangle(rectangleCoords, borderColor); // 处理矩形
const text = processText(labelText, rect.left, rect.top, borderColor); // 处理文本
canvas.value.add(rect, text); // 将矩形和文本添加到画布中
}
/**
* 绘制标注集合
* @param {Array} categoryAnnotations 分类标注数据
*/
const draw = (categoryAnnotations) => {
// 清除之前的图形和文本,防止叠加
canvas.value.getObjects().forEach((obj) => {
const typeArr = ['rect', 'text', 'polygon']; // 标注对象类型
if (typeArr.includes(obj.type)) {
canvas.value.remove(obj); // 移除标注对象
}
});
// 遍历分类标注数据
categoryAnnotations.forEach((category) => {
if (category.annotations && category.annotations.length) {
category.annotations.forEach((annotation) => {
if (props.modelType === 'IMAGE_SEGMENTATION' && annotation.segmentation.length)
processPolygon(annotation.segmentation, category.color, category.name); // 绘制多边形标注
if (props.modelType === 'OBJECT_DETECTION' && annotation.bbox.length)
drawRectAndText(annotation.bbox, category.color, category.name); // 绘制矩形标注
});
}
});
};
1.7. 注意事项
- 初始化时机:Fabric.js 的初始化需要在
nextTick
中执行,以确保 DOM 元素已经加载完毕。这是因为 Fabric.js 需要操作页面中的 DOM 元素,如果这些元素还没有加载完毕,初始化操作将无法成功。 - 防止图片叠加:每次加载新图片前需要清除画布,否则会导致图片叠加。这是因为 Fabric.js 的画布是持久化的,即上一次绘制的内容会保留在画布上。如果不清除画布,新的图片将叠加在旧的图片上,导致显示混乱。
- 标注绘制时机:标注绘制应在图片加载后进行,以避免标注被图片覆盖。由于图片加载是异步的,如果在图片加载前就进行标注绘制,图片加载完成后会将标注覆盖。因此,确保图片加载完成后再进行标注绘制。
结论
希望本文能帮助你更好地使用 Fabric.js 进行图片标注。如果你有任何问题或建议,欢迎在评论区留言讨论。通过本文的详细步骤和注意事项,你可以轻松地在项目中实现基于 Fabric.js 的图片标注功能。
文章转自:https://juejin.cn/post/7372070947819733019
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)