区域画框:前端实现的带有网格背景、支持不规则框绘制、节点可拖动、右键删除节点以及获取坐标等功能的画框,适用于——摄像头区域侦测设置、预置位设置等
一、概览
基于网格的图形编辑组件:突出其基于网格背景,同时具备图形绘制、编辑(包括节点操作等)的功能。用户可以在网格背景下绘制不规则的图形元素,并对其进行各种编辑操作,方便创建复杂的图形结构。
二、预览
三、获取坐标的格式
[{"x":240,"y":135},{"x":240,"y":225},{"x":400,"y":225},{"x":400,"y":135},...]
四、功能介绍
结合前面画框功能及常见应用场景,为你详细列举该功能可能具备的特性:
- 基础绘制功能
-
- 自由绘制不规则形状:用户可通过鼠标或触摸操作,自由绘制任意形状的框,满足对复杂图形的绘制需求,如在图像标注场景中绘制物体的不规则轮廓。
-
- 支持多种绘制模式:除了直接绘制,还提供点选模式,用户点击多个点后自动连接形成多边形框;以及手绘模式,模拟真实笔触,让绘制更加自然流畅。
- 编辑功能
-
- 节点操作:绘制的框上显示可操作节点,用户能拖动节点改变框的形状,实现对已绘制图形的精细调整,适用于对标注精度要求较高的场景。
-
- 添加和删除节点:在绘制过程中或绘制完成后,用户可以随时添加新的节点来细化图形,也能删除不需要的节点,优化绘制的框。比如在绘制复杂的地图边界时,通过添加和删除节点来精确描绘边界形状。
-
- 整体移动和缩放:支持对整个绘制的框进行移动和缩放操作,方便调整框在画布中的位置和大小,以适应不同的需求。在可视化设计中,可通过整体移动和缩放画框来调整元素布局。
- 样式与属性设置
-
- 边框样式设置:用户可以自定义框的边框样式,包括线条颜色、粗细、虚线或实线等,增强视觉区分度。在图像编辑中,不同类型的标注可以使用不同样式的边框。
-
- 填充设置:为绘制的框添加填充颜色或透明度设置,使框内区域与背景区分开来,或者根据需要设置半透明效果,以便同时查看框内和框外的内容。在数据可视化中,填充不同颜色的框可以代表不同的数据类别。
- 交互功能
-
- 鼠标交互:通过鼠标的点击、拖动、右键操作实现绘制、选择、删除等功能。例如,点击开始绘制,拖动绘制线条,右键点击删除节点或整个框。
-
- 触摸交互(适用于移动设备):支持触摸操作,如触摸屏幕绘制、缩放和移动框,满足在移动设备上的使用需求。在移动办公或现场数据采集场景中,用户可以直接在平板或手机上进行操作。
-
- 快捷键操作:提供快捷键绑定,如使用特定的按键组合来快速完成常见操作,如撤销、重做、清除等,提高操作效率。对于频繁使用该功能的用户,快捷键操作能大大提升工作速度。
- 数据处理功能
-
- 获取坐标数据:能够获取绘制框的顶点坐标数据,这些数据可以用于后续的分析、处理或存储。在机器学习数据标注中,坐标数据是训练模型的重要依据。
-
- 数据导出与导入:支持将绘制的框及其相关属性数据导出为特定格式(如 JSON、XML),方便在不同系统或应用之间共享数据;同时也能导入已有的数据,实现数据的复用和编辑。在团队协作的标注项目中,数据的导出和导入功能可以方便不同标注员之间的数据交互。
- 辅助功能
-
- 网格背景:提供可切换的网格背景,帮助用户更准确地绘制和对齐图形。在绘制具有规则形状或需要精确位置的图形时,网格背景能起到很好的辅助作用。
-
- 撤销和重做:支持撤销上一步操作和重做已撤销的操作,让用户在绘制过程中可以随时纠正错误,避免从头开始绘制的麻烦。
-
- 清除与重置:可以清除当前绘制的所有框,或者重置整个绘制区域,回到初始状态,方便用户重新开始绘制。
五、适用于
摄像头区域侦测设置、摄像头预置位设置、区域画框等
六、代码实现,提供vue2、vue3、原生js
1、 vue3组件实现,父组件注册该组件即可使用
撤销 获取坐标 重置 import { ref, onMounted, watch } from "vue"; // 定义组件接收的 props const props = defineProps({ // canvas 容器的宽度,默认为 640 canvasWidth: { type: Number, default: 640, }, // canvas 容器的高度,默认为 360 canvasHeight: { type: Number, default: 360, }, // 初始的点数组,用于数据反显,默认为空数组 initialPoints: { type: Array, // default: () => [], default: () => [ { x: 240, y: 135 }, { x: 240, y: 225 }, { x: 400, y: 225 }, { x: 400, y: 135 }, ], }, }); // 引用 canvas 元素 const gridCanvas = ref(null); // 存储当前不规则图形的点数组,初始值为传入的初始点数据 const points = ref(props.initialPoints.slice()); // 标记是否正在绘制图形 const isDrawing = ref(false); // 标记是否正在拖动某个点 const isDragging = ref(false); // 当前正在拖动的点的索引 const draggedPointIndex = ref(-1); // 网格的大小 const gridSize = ref(10); // 画布内边距,这里设置为 0 以铺满整个 canvas const framePadding = ref(0); // canvas 的 2D 上下文对象 let ctx = null; // 初始化 canvas 并绘制网格 const initCanvas = () => { const canvas = gridCanvas.value; // 设置 canvas 的宽度和高度 canvas.width = props.canvasWidth; canvas.height = props.canvasHeight; // 获取 canvas 的 2D 上下文对象 ctx = canvas.getContext("2d"); // 绘制网格 drawGrid(); }; // 绘制网格 const drawGrid = () => { // 清空 canvas 内容 ctx.clearRect(0, 0, props.canvasWidth, props.canvasHeight); // 设置网格线的颜色和宽度 ctx.strokeStyle = "rgba(0, 255, 0, 0.5)"; ctx.lineWidth = 1; // 绘制垂直网格线 for (let x = 0; x { // 限制鼠标点击的坐标在 canvas 范围内 const x = Math.min(Math.max(e.offsetX, 0), props.canvasWidth); const y = Math.min(Math.max(e.offsetY, 0), props.canvasHeight); const clickRadius = 5; // 检查是否点击在某个点上 for (let i = 0; i 0) { // 重新绘制 canvas redraw(); // 绘制临时的图形(包含当前鼠标位置) drawShape([...points.value, { x, y }]); } else if (isDragging.value) { // 更新被拖动点的坐标 points.value[draggedPointIndex.value] = { x, y }; // 重新绘制 canvas redraw(); } }; // 处理鼠标松开事件 const handleMouseUp = () => { // 停止绘制和拖动状态 isDrawing.value = false; isDragging.value = false; draggedPointIndex.value = -1; // 绘制最终的图形 drawShape(); }; // 处理鼠标右键点击事件 const handleContextMenu = (e) => { // 阻止默认的右键菜单行为 e.preventDefault(); // 限制鼠标右键点击的坐标在 canvas 范围内 const x = Math.min(Math.max(e.offsetX, 0), props.canvasWidth); const y = Math.min(Math.max(e.offsetY, 0), props.canvasHeight); const clickRadius = 5; // 检查是否右键点击在某个点上 for (let i = 0; i { ctx.beginPath(); ctx.arc(x, y, 5, 0, 2 * Math.PI); ctx.fill(); }); }; // 重新绘制 canvas(先初始化,再绘制图形) const redraw = () => { initCanvas(); drawShape(); }; // 撤销上一步操作 const undo = () => { if (points.value.length > 0) { // 移除最后一个点 points.value.pop(); // 重新绘制 canvas redraw(); } }; // 获取当前不规则图形各点的坐标 const getCoordinates = () => { console.log( "不规则框坐标:", points.value.map((p) => ({ x: p.x, y: p.y })) ); }; // 重置 canvas 内容 const reset = () => { // 清空点数组 // points.value = []; points.value = [ { x: 240, y: 135 }, { x: 240, y: 225 }, { x: 400, y: 225 }, { x: 400, y: 135 }, ]; // 重新绘制 canvas redraw(); }; onMounted(() => { // 初始化 canvas initCanvas(); // 绘制初始的不规则图形 drawShape(); const canvas = gridCanvas.value; // 绑定鼠标事件 canvas.addEventListener("mousedown", handleMouseDown); canvas.addEventListener("mousemove", handleMouseMove); canvas.addEventListener("mouseup", handleMouseUp); canvas.addEventListener("contextmenu", handleContextMenu); }); // 监听 canvas 宽高的变化,重新初始化 canvas 并绘制图形 watch([() => props.canvasWidth, () => props.canvasHeight], () => { initCanvas(); drawShape(); }); // 监听初始点数据的变化,更新 points 数组,重新初始化 canvas 并绘制图形 watch( () => props.initialPoints, (newPoints) => { points.value = newPoints.slice(); initCanvas(); drawShape(); } ); // 导出函数以供外部调用,可以使用refs方法调用 defineExpose({ getCoordinates, reset, undo, }); .canvas-container { position: relative; margin: 20px auto; border: 1px solid #ccc; overflow: hidden; } canvas { display: block; width: 100%; height: 100%; background-color: #f0f0f0; } .button-group { position: absolute; top: 10px; left: 10px; z-index: 10; } button { padding: 6px 12px; font-size: 14px; cursor: pointer; background-color: #fff; border: 1px solid #ddd; margin-right: 8px; }
2、vue2 组件实现,父组件注册该组件即可使用
撤销 获取坐标 重置 export default { // 接收父组件传递的参数 props: { // canvas 容器的宽度,默认为 640 canvasWidth: { type: Number, default: 640, }, // canvas 容器的高度,默认为 360 canvasHeight: { type: Number, default: 360, }, // 初始的点数组,用于数据反显,默认为空数组 initialPoints: { type: Array, // default: () => [], default: () => [ { x: 240, y: 135 }, { x: 240, y: 225 }, { x: 400, y: 225 }, { x: 400, y: 135 }, ], }, }, data() { return { // 存储当前不规则图形的点数组 points: [], // 标记是否正在绘制图形 isDrawing: false, // 标记是否正在拖动某个点 isDragging: false, // 当前正在拖动的点的索引 draggedPointIndex: -1, // 网格的大小 gridSize: 10, // 画布内边距,这里设置为 0 以铺满整个 canvas framePadding: 0, // canvas 的 2D 上下文对象 ctx: null, }; }, mounted() { // 将初始点数据赋值给 points 数组,实现数据反显 this.points = this.initialPoints.slice(); // 初始化 canvas this.initCanvas(); // 绑定鼠标事件 this.bindEvents(); // 绘制初始的不规则图形 this.drawShape(); }, methods: { // 初始化 canvas 并绘制网格 initCanvas() { const canvas = this.$refs.gridCanvas; // 设置 canvas 的宽度和高度 canvas.width = this.canvasWidth; canvas.height = this.canvasHeight; // 获取 canvas 的 2D 上下文对象 this.ctx = canvas.getContext("2d"); // 绘制网格 this.drawGrid(); }, // 绘制网格 drawGrid() { const ctx = this.ctx; // 清空 canvas 内容 ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // 设置网格线的颜色和宽度 ctx.strokeStyle = "rgba(0, 255, 0, 0.5)"; ctx.lineWidth = 1; // 绘制垂直网格线 for (let x = 0; x 0) { // 移除最后一个点 this.points.pop(); // 重新绘制 canvas this.redraw(); } }, // 获取当前不规则图形各点的坐标 getCoordinates() { console.log( "不规则框坐标:", this.points.map((p) => ({ x: p.x, y: p.y })) ); }, // 重置 canvas 内容 reset() { // 清空点数组 // points.value = []; points.value = [ { x: 240, y: 135 }, { x: 240, y: 225 }, { x: 400, y: 225 }, { x: 400, y: 135 }, ]; // 重新绘制 canvas this.redraw(); }, // 限制坐标在指定范围内 limitCoordinate(value, max) { return Math.min(Math.max(value, 0), max); }, // 计算两点之间的距离 calculateDistance(x1, y1, x2, y2) { const dx = x1 - x2; const dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); }, // 绑定鼠标事件 bindEvents() { const canvas = this.$refs.gridCanvas; canvas.addEventListener("mousedown", this.handleMouseDown); canvas.addEventListener("mousemove", this.handleMouseMove); canvas.addEventListener("mouseup", this.handleMouseUp); canvas.addEventListener("contextmenu", this.handleContextMenu); }, }, }; .canvas-container { position: relative; margin: 20px auto; border: 1px solid #ccc; overflow: hidden; } canvas { display: block; width: 100%; height: 100%; background-color: #f0f0f0; } .button-group { position: absolute; top: 10px; left: 10px; z-index: 10; } button { padding: 6px 12px; font-size: 14px; cursor: pointer; background-color: #fff; border: 1px solid #ddd; margin-right: 8px; }
3、原生js实现,在HTML文件里面运行即可使用
Camera Grid Frame with Draggable, Deletable, Coordinate Retrieval and Reset body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; } canvas { border: 1px solid #ccc; } button { position: absolute; padding: 8px 16px; font-size: 14px; cursor: pointer; } #undoButton { top: 20px; left: 20px; } #getCoordinatesButton { top: 20px; left: 120px; } #resetButton { top: 20px; left: 240px; } 撤销 获取坐标 重置 const canvas = document.getElementById('gridCanvas'); canvas.width = 640; canvas.height = 360; const ctx = canvas.getContext('2d'); const undoButton = document.getElementById('undoButton'); const getCoordinatesButton = document.getElementById('getCoordinatesButton'); const resetButton = document.getElementById('resetButton'); const gridSize = 10; const framePadding = 0; // 边框内边距 const frameWidth = canvas.width - 2 * framePadding; const frameHeight = canvas.height - 2 * framePadding; // 预设的 points 数据,可根据需要修改 const initialPoints = [ { x: 240, y: 135 }, { x: 240, y: 225 }, { x: 400, y: 225 }, { x: 400, y: 135 }, ]; const points = initialPoints.slice(); // 复制预设数据到 points // 绘制方格 function drawGrid() { for (let x = framePadding; x 1) { drawIrregularShape(); } canvas.addEventListener('mousedown', (e) => { const x = e.offsetX; const y = e.offsetY; const clickRadius = 5; // 检查是否点击在节点上 for (let i = 0; i 0) { redrawCanvas(); drawIrregularShape([...points, { x, y }]); } else if (isDragging) { points[draggedPointIndex] = { x, y }; redrawCanvas(); drawIrregularShape(); } }); canvas.addEventListener('mouseup', () => { isDrawing = false; isDragging = false; draggedPointIndex = -1; }); canvas.addEventListener('contextmenu', (e) => { e.preventDefault(); const x = e.offsetX; const y = e.offsetY; const clickRadius = 5; for (let i = 0; i 0) { points.pop(); redrawCanvas(); drawIrregularShape(); } }); getCoordinatesButton.addEventListener('click', () => { const coordinates = points.map(point => ({ x: point.x, y: point.y })); console.log('不规则框各点位的坐标:', coordinates); alert('不规则框各点位的坐标:' + JSON.stringify(coordinates)); // 你可以在这里将坐标信息用于其他用途,比如发送到服务器等 }); resetButton.addEventListener('click', () => { points.length = 0; redrawCanvas(); }); function redrawCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); } function drawIrregularShape(pointsToDraw = points) { if (pointsToDraw.length > 1) { ctx.beginPath(); ctx.moveTo(pointsToDraw[0].x, pointsToDraw[0].y); for (let i = 1; i
- 清除与重置:可以清除当前绘制的所有框,或者重置整个绘制区域,回到初始状态,方便用户重新开始绘制。
-
- 撤销和重做:支持撤销上一步操作和重做已撤销的操作,让用户在绘制过程中可以随时纠正错误,避免从头开始绘制的麻烦。
-
- 网格背景:提供可切换的网格背景,帮助用户更准确地绘制和对齐图形。在绘制具有规则形状或需要精确位置的图形时,网格背景能起到很好的辅助作用。
- 数据导出与导入:支持将绘制的框及其相关属性数据导出为特定格式(如 JSON、XML),方便在不同系统或应用之间共享数据;同时也能导入已有的数据,实现数据的复用和编辑。在团队协作的标注项目中,数据的导出和导入功能可以方便不同标注员之间的数据交互。
-
- 获取坐标数据:能够获取绘制框的顶点坐标数据,这些数据可以用于后续的分析、处理或存储。在机器学习数据标注中,坐标数据是训练模型的重要依据。
- 快捷键操作:提供快捷键绑定,如使用特定的按键组合来快速完成常见操作,如撤销、重做、清除等,提高操作效率。对于频繁使用该功能的用户,快捷键操作能大大提升工作速度。
-
- 触摸交互(适用于移动设备):支持触摸操作,如触摸屏幕绘制、缩放和移动框,满足在移动设备上的使用需求。在移动办公或现场数据采集场景中,用户可以直接在平板或手机上进行操作。
-
- 鼠标交互:通过鼠标的点击、拖动、右键操作实现绘制、选择、删除等功能。例如,点击开始绘制,拖动绘制线条,右键点击删除节点或整个框。
- 填充设置:为绘制的框添加填充颜色或透明度设置,使框内区域与背景区分开来,或者根据需要设置半透明效果,以便同时查看框内和框外的内容。在数据可视化中,填充不同颜色的框可以代表不同的数据类别。
-
- 边框样式设置:用户可以自定义框的边框样式,包括线条颜色、粗细、虚线或实线等,增强视觉区分度。在图像编辑中,不同类型的标注可以使用不同样式的边框。
- 整体移动和缩放:支持对整个绘制的框进行移动和缩放操作,方便调整框在画布中的位置和大小,以适应不同的需求。在可视化设计中,可通过整体移动和缩放画框来调整元素布局。
-
- 添加和删除节点:在绘制过程中或绘制完成后,用户可以随时添加新的节点来细化图形,也能删除不需要的节点,优化绘制的框。比如在绘制复杂的地图边界时,通过添加和删除节点来精确描绘边界形状。
-
- 节点操作:绘制的框上显示可操作节点,用户能拖动节点改变框的形状,实现对已绘制图形的精细调整,适用于对标注精度要求较高的场景。
- 支持多种绘制模式:除了直接绘制,还提供点选模式,用户点击多个点后自动连接形成多边形框;以及手绘模式,模拟真实笔触,让绘制更加自然流畅。
-
- 自由绘制不规则形状:用户可通过鼠标或触摸操作,自由绘制任意形状的框,满足对复杂图形的绘制需求,如在图像标注场景中绘制物体的不规则轮廓。
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。