使用ailabel对图片进行标注,vue3/js+ailabel.js

news/2025/1/30 15:10:57/文章来源:https://www.cnblogs.com/tdyp/p/18695184

一、实现效果

对方案可以添加多张图片,并在图片上进行标注。并且可以通过下方的缩略图切换方案图片
(demo)

二、效果图


三、页面元素

<div class="w-full overflow-auto p-2" style="height: calc(100% - 7rem)"><div class="btns mb-2"><el-button size="small" type="primary" @click="setMode('CIRCLE')">{{ Translate.i18n("圆") }}</el-button><el-button size="small" type="primary" @click="setMode('RECT')">{{ Translate.i18n("矩形") }}</el-button><el-button size="small" type="primary" @click="setMode('POLYGON')">{{ Translate.i18n("多边形") }}</el-button><el-button size="small" type="primary" @click="setMode('PAN')">{{ Translate.i18n("平移模式") }}</el-button></div><div v-for="(v, k) in imgs" :key="k" :id="v.id" v-show="v.id == selectedId" class="relative w-full overflow-hidden bg-gray-110" style="height: calc(100% - 2rem)"></div></div><div class="thumbnail flex h-16 w-full items-center justify-center overflow-auto border-t p-2"><divclass="mr-2 flex h-12 w-12 flex-shrink-0 cursor-pointer items-center justify-center rounded border p-1"v-for="(v, k) in imgs":key="k"@click="changeImg(v, k)":class="selectedId == v.id ? 'border-green-550' : ''"><img class="h-full w-full" :src="v.url" /></div></div>

四、基础定义

npm install ailabel
import AILabel from "ailabel"

let selectedId = ref(1), // 选中的图片ID
imgs = ref([]), // 图片列表
gMap = {}, // 地图实例drawingStyle = {}, // 绘制样式featureLayer = {}, // 绘制图层textLayer = {}, // 文字图层imgLayer = {}, // 图片图层graphCount = ref(0); // 图片计数
const textStyle = { fillStyle: "#F4A460", strokeStyle: "#D2691E", background: true, globalAlpha: 1, fontColor: "#0f0" }; // 文本样式

五、主要逻辑

5.1 添加图片

在添加图片的时候进行画布初始化

// 添加图片
const addImg = async () => {graphCount.value++;if (!selectedId.value) {selectedId.value = graphCount.value;}const n = imgs.value.length;//推荐以网络图片地址为例测试const obj = {url: `http://192.168.2.179:9100/template/img-${n + 1}.jpg`,count: 0,id: graphCount.value};imgs.value.push(obj);await nextTick();initGraph(graphCount.value);
};

5.2 画布初始化

//初始化画布
const initGraph = (id) => {if (selectedId.value != id) return;gMap[id] = new AILabel.Map(id, {center: { x: 400, y: 250 },zoom: 500,mode: "PAN",refreshDelayWhenZooming: true,zoomWhenDrawing: true,panWhenDrawing: true,zoomWheelRatio: 5,withHotKeys: true});// click单击事件gMap[id].events.on("click", (point) => {const feature = gMap[id].getTargetFeatureWithPoint(point.global) || {};if (feature.id) {gMap[id].setActiveFeature(feature);addDeleteIcon(id, feature);showItem.value = true; //可以控制详情显示}});// 绘制完成事件gMap[id].events.on("drawDone", (type, data) => {addFeature(id, type, data);});// 双击编辑gMap[id].events.on("featureSelected", (feature) => {gMap[id].setActiveFeature(feature);// 增加删除按钮addDeleteIcon(id, feature);});// 单机空白取消编辑gMap[id].events.on("featureUnselected", () => {gMap[id].markerLayer.removeAllMarkers();gMap[id].setActiveFeature(null);});// 更新完gMap[id].events.on("featureUpdated", (feature, shape) => {// 更新或者移动需要重新设置删除图标gMap[id].markerLayer.removeAllMarkers();// 更新text的位置const text = textLayer[id].getTextById(feature.id);text.updatePosition(getIndexPosition(feature, shape));feature.updateShape(shape);addDeleteIcon(id, feature);});// 删除gMap[id].events.on("FeatureDeleted", () => {});// 初始化图层featureLayer[id] = new AILabel.Layer.Feature("featureLayer", // 图层id{ name: "featureLayer" },{ zIndex: 10 });gMap[id].addLayer(featureLayer[id]);textLayer[id] = new AILabel.Layer.Text("text",{ name: "textLayer" }, // props{ zIndex: 12, opacity: 1 } // style);gMap[id].addLayer(textLayer[id]);const imgurl = imgs.value.find((v) => v.id == id)?.url;imgLayer[id] = new AILabel.Layer.Image("img",{src: imgurl,width: 800,height: 500,position: {// 左上角相对中心点偏移量x: 0,y: 0}// grid: {//   // 3 * 3//   columns: [{ color: "#9370DB" }, { color: "#FF6347" }],//   rows: [{ color: "#9370DB" }, { color: "#FF6347" }]// } // 网格线颜色},{ zIndex: 5 });gMap[id].addLayer(imgLayer[id]);
};

5.3 增加删除按钮

// 增加删除图标
const addDeleteIcon = (id, feature) => {// 添加delete-iconlet points = getPoints(feature);const gFirstMarker = new AILabel.Marker(+new Date(), // id{src: "http://192.168.2.179:9100/template/delete.png",position: points[1], // 矩形右上角offset: {x: -16,y: -16}}, // markerInfo{ name: "delete" } // props);gFirstMarker.events.on("click", (marker) => {// 首先删除当前markergMap[id].markerLayer.removeMarkerById(marker.id);// 删除对应texttextLayer[id].removeTextById(feature.id);// 删除对应featurefeatureLayer[id].removeFeatureById(feature.id);});gMap[id].markerLayer.addMarker(gFirstMarker);
};

5.4 获取所有点

// 获取所有点
const getPoints = (feature) => {switch (feature.type) {case "RECT":return feature.getPoints();case "CIRCLE":return feature.getEdgePoints();case "POLYGON":return feature.shape.points;default:return [];}
};

5.5 设置模式

// 设置模式
const setMode = (mode) => {const layers = gMap[selectedId.value].getLayers()?.filter((v) => v.type == "IMAGE") || [];if (mode != "PAN" && !layers.length) {elMessage(Translate.i18n("请先添加方案图片"), "warning");return;}gMap[selectedId.value].setMode(mode);if (mode == "CIRCLE") {drawingStyle = { fillStyle: "#9370DB", strokeStyle: "#0000FF", lineWidth: 1 };gMap[selectedId.value].setDrawingStyle(drawingStyle);} else if (mode == "RECT") {drawingStyle = { strokeStyle: "#0f0", lineWidth: 1 };gMap[selectedId.value].setDrawingStyle(drawingStyle);} else if (mode == "POLYGON") {drawingStyle = { strokeStyle: "#00f", fillStyle: "#0f0", globalAlpha: 0.3, lineWidth: 1, fill: true, stroke: true };gMap[selectedId.value].setDrawingStyle(drawingStyle);}
};

5.6 添加图层元素

// 添加图层元素-元素数字标记
const addFeature = (id, type, data, name) => {let count = imgs.value.find((v) => v.id == id)?.count;count++;imgs.value.forEach((v) => {if (v.id == id) {v.count = count;}});if (type == "CIRCLE") {const circleFeature = new AILabel.Feature.Circle(count, // iddata, // shape{ name }, // propsdrawingStyle // style);featureLayer[id].addFeature(circleFeature);addText(id, count, { x: data.cx, y: data.cy });} else if (type == "RECT") {const rectFeature = new AILabel.Feature.Rect(count, // iddata, // shape{ name }, // propsdrawingStyle // style);featureLayer[id].addFeature(rectFeature);addText(id, count, { x: data.x, y: data.y });} else if (type == "POLYGON") {const polygonFeature = new AILabel.Feature.Polygon(count, // id{ points: data }, // shape{ name }, // propsdrawingStyle // style);featureLayer[id].addFeature(polygonFeature);addText(id, count, data[0]);}
};

5.7 添加标记序号文本

// 添加文本
const addText = (layerId, textId, point) => {const textPointer = new AILabel.Text(textId, // id{ text: textId, position: point, offset: { x: 0, y: 0 } }, // shape{ name: "" }, // propstextStyle // style);textLayer[layerId].addText(textPointer);
};

5.8 获取序号文本位置

// 获取序号位置
const getIndexPosition = (feature, shape) => {switch (feature.type) {case "RECT":return { x: shape.x, y: shape.y };case "CIRCLE":return { x: shape.cx, y: shape.cy };case "POLYGON":return shape.points[0];default:return {};}
};

至此大致标注逻辑已全部完成,略微补充一段切换底下缩略图变化画布的逻辑

六、切换缩略图

const changeImg = async (v, k) => {selectedId.value = v.id;await nextTick();if (!gMap[v.id]) {initGraph(v.id);}
};

七、画布resize

const resizeMap = () => {nextTick(() => {gMap[selectedId.value] && gMap[selectedId.value].resize();gMap[selectedId.value] && gMap[selectedId.value].centerAndZoom({ center: { x: 400, y: 250 }, zoom: 500 });});
};watch(() => showItem.value,() => {resizeMap();}
);

八、官方文档

ailabel官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/877055.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【模拟电子技术】17-基本放大电路的派生电路与场效应管放大电路的分析原则

【模拟电子技术】17-基本放大电路的派生电路与场效应管放大电路的分析原则 现在提出要求,要用NPN,PNP三极管各一个来构造放大电路,要求在大功率的时候,从输出端看都是PNP型,复合管就可以做到这一点可以看出第一个管子的功率肯定没第二个大,但是第一级管子决定了复合管的类…

Java Collection集合

目录集合概述集合框架Collection 常用功能 集合概述集合:集合是java中提供的一种容器,可以用来存储多个数据。 集合和数组既然都是容器,它们有啥区别呢? 数组的长度是固定的。集合的长度是可变的。 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对…

Java StringBuilder类

目录字符串拼接问题StringBuilder概述构造方法常用方法append方法toString方法 字符串拼接问题 由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。例如: public class StringDemo {public static void main(String[] args) {Strin…

[2025.1.28 MySQL学习] 锁

锁 全局锁全局锁就是对整个数据库实例加锁,加锁后处于只读状态,DML写语句、DDL语句、已经更新的事务提交语句都会被阻塞,典型使用场景时全库的逻辑备份 加全局锁:flush tables with read lock; 数据备份:mysqldump -uroot -p1234 itcast>itcast.sql 解锁:unlock table…

网络流量优化问题

问题描述 给定一个网络G(V, E) ,其中V为节点集合,E为链路集合。网络中的每条链路e的容量为Ce拓扑上的数字为链路的容量,假设网络中有K条单向网络流(k=n*(n-1),n为网络节点的数目),假定第i条网络流为fi,流的大小从[10, 100]区间中随机产生。现需要对K条网络流进行合理的…

8. Reading attributes 之 ATT_READ_BY_TYPE

1. ATT_READ_BY_TYPE_REQ 1.1 请求格式

Kafka 的部署(单机和集群)和 SpringBoot 访问

Kafka 由 Scala 和 Java 编写,最初由 LinkedIn 开发,后来成为 Apache 顶级项目,是一种高吞吐量的分布式发布/订阅消息系统。 Kafka 不仅仅是一个消息队列,还支持实时数据处理,其高吞吐、可扩展和持久化特性使其在大数据领域广泛应用。 本篇博客不详细介绍 Kafka,主要聚焦…

DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」

前言 最近 DeepSeek 狠狠刷了一波屏,国产大模型真的越来越厉害了👍,官方的服务器已经爆满了,以至于频繁出现反应迟缓甚至宕机的情况,和两年多之前 ChatGPT 的遭遇颇为相似。 我已经好久没有本地部署模型了(现在各厂商的模型都便宜量大),这次正好来试试 DeepSeek 开源模…

CF1000

A link首先,对于一个数(比如说\(x\)),它和它加一一定互质(也就是\(x\)和\(x+1\)一定互质),那么它和它加一组成的区间(\([x,x+1]\))一定是好区间,也一定是最小好区间,因为除了本身\([x,x+1]\)、两个数\([x,x]\),\([x+1,x+1]\)和空集不包含其他区间了,而相等两个数一…

大模型

目录大模型的演变大模型的使用与训练大模型的特点与分类大模型的工作流程大模型的应用 大模型的演变机器学习:深度学习:大模型的使用与训练 大模型的特点与分类 大模型的工作流程 大模型的应用本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.co…

“星门计划对AI未来的意义——以及谁将掌控它”

“星门计划对AI未来的意义——以及谁将掌控它”图片由DALL-E 3生成就在几天前,唐纳德特朗普宣布了“星门计划”,OpenAI随即跟进,分享了更多细节。他们明确表示,计划在未来四年内投资5000亿美元,在美国为OpenAI构建一个全新的AI基础设施。这让我颇感意外,尤其是考虑到埃隆…