【优化】Vue前端实现在图片上画线, 并将端点坐标传给后端 您所在的位置:网站首页 怎么用电脑在图片上画线 【优化】Vue前端实现在图片上画线, 并将端点坐标传给后端

【优化】Vue前端实现在图片上画线, 并将端点坐标传给后端

2024-07-09 11:54| 来源: 网络整理| 查看: 265

上一篇:Vue前端实现在图片上画线, 并将端点坐标传给后端

只能适用于两车道,不满足通用需求,对用户也限制很多,很不友好。

优化后:

1. 不限制画线数量;

2. 不限制画线的方向和排序,可以任意绘制,最后统一处理数据;

3. 不需要双击,现在只需要单击就可以划线,符合使用习惯。

更新后的代码:

请按照以下绘制要求进行操作: 1. 点击"绘制标线"按钮, 开始绘制; 2. 鼠标单击选取起点, 再选取终点, 完成一条线的绘制; 3. 全部绘制完成后, 勾选需要的功能, 点击"保存", 完成配置。

绘制标线 清除标线 import { reqPic, reqPostConfig, reqTestConfig } from '@/api/camera' export default { data() { return { // imgUrl: require('./test.png'), imgUrl: '', isDrawing: false, // 是否正在绘制 ratio: 1, imgWidth: '863px', imgHeight: '360px', wrapWidth: '863px', wrapHeight: '360px', canvasWidth: '863px', canvasHeight: '360px', drawingPoints: [], drawedPoints: [], imgCanvas: null, imgCtx: null, drawCanvas: null, drawCtx: null, saveCanvas: null, saveCtx: null, submitData: [ { polygon: { x1: 330, y1: 200, x2: 190, y2: 350, }, }, { polygon: { x1: 440, y1: 200, x2: 430, y2: 350, }, }, { polygon: { x1: 535, y1: 200, x2: 638, y2: 350, }, }, ], columns: [ { title: '车辆类别', dataIndex: 'vehicle_type', align: 'center', }, { title: '标准车牌', dataIndex: 'standard_plate', align: 'center', }, { title: '喷漆车牌', dataIndex: 'penqi_plate', align: 'center', }, { title: '车牌颜色', dataIndex: 'plate_color', align: 'center', }, { title: '车身颜色', dataIndex: 'car_color', align: 'center', }, { title: '轴数', dataIndex: 'car_axle', align: 'center', }, { title: '车道', dataIndex: 'lane', align: 'center', }, ], loadData: [], loading: false, picData: {}, submitPoints: {}, parameter: {}, } }, beforeCreate() { this.form = this.$form.createForm(this, { name: 'validate_other' }) }, mounted() { this.$nextTick(() => { this.initCanvas() // this.initImgCanvas() // Call initImgCanvas here this.getImage() }) }, methods: { initCanvas() { // 初始化canvas画布 let canvasWrap = document.getElementsByClassName('canvas-wrap') console.log(canvasWrap) this.wrapWidth = canvasWrap[0].clientWidth this.wrapHeight = canvasWrap[0].clientHeight this.imgCanvas = document.getElementById('imgCanvas') this.imgCtx = this.imgCanvas.getContext('2d') // 绘制canvas this.drawCanvas = document.getElementById('drawCanvas') this.drawCtx = this.drawCanvas.getContext('2d') // 保存绘制区域 saveCanvas this.saveCanvas = document.getElementById('saveCanvas') this.saveCtx = this.saveCanvas.getContext('2d') // this.initImgCanvas() }, initImgCanvas() { // 计算宽高比 let ww = this.wrapWidth // 画布宽度 let wh = this.wrapHeight // 画布高度 let iw = this.imgWidth // 图片宽度 let ih = this.imgHeight // 图片高度 if (iw / ih < ww / wh) { // 以高为主 this.ratio = ih / wh this.canvasHeight = wh this.canvasWidth = (wh * iw) / ih } else { // 以宽为主 this.ratio = iw / ww this.canvasWidth = ww this.canvasHeight = (ww * ih) / iw } // 初始化画布大小 this.imgCanvas.width = this.canvasWidth this.imgCanvas.height = this.canvasHeight this.drawCanvas.width = this.canvasWidth this.drawCanvas.height = this.canvasHeight this.saveCanvas.width = this.canvasWidth this.saveCanvas.height = this.canvasHeight // 图片加载绘制 let img = document.createElement('img') img.src = this.imgUrl img.onload = () => { console.log('图片已加载') this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight) this.renderDatas() // 渲染原有数据 } }, startDraw() { // 绘制区域 if (this.isDrawing) return this.isDrawing = true // 绘制逻辑 this.drawCanvas.addEventListener('click', this.drawImageClickFn) this.drawCanvas.addEventListener('dblclick', this.drawImageDblClickFn) this.drawCanvas.addEventListener('mousemove', this.drawImageMoveFn) }, clearAll() { // 清空所有绘制区域 this.saveCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) this.drawedPoints = [] this.submitData = [] }, async getImage() { const res = await reqPic() console.log('pic data', res) // 这里请求接口 ... this.imgUrl = 'data:image/png;base64,' + res.scribing_image // this.imgUrl = require('@/views/DrawLine/test.png') const reqPoints = res.coordinate // const reqPoints = { // lineone: [1600, 702, 1632, 1902], // linetwo: [3150, 702, 4098, 1698], // linethree: [2448, 702, 2850, 1902], // linefour: [3448, 702, 4850, 1902], // } // 遍历 reqPoints 中的对象 for (let key in reqPoints) { // 获取子对象或数组 let subObjectOrArray = reqPoints[key] // 打印子对象或数组 // console.log(subObjectOrArray) // 检查是否为数组 if (Array.isArray(subObjectOrArray) && subObjectOrArray.length === 4) { // 将数组中的每个数字除以6 let modifiedArray = subObjectOrArray.map((num) => (Number(num) / 6).toFixed() ) // 在 reqPoints 对象中更新数组 reqPoints[key] = modifiedArray } } // 打印修改后的 reqPoints 对象 console.log('drawline points', reqPoints) // 动态生成 submitData this.submitData = Object.keys(reqPoints) .map((key) => { const points = reqPoints[key] if (Array.isArray(points) && points.length === 4) { return { polygon: { x1: points[0], y1: points[1], x2: points[2], y2: points[3], }, } } return null }) .filter((item) => item !== null) // console.log('base64 file', this.imgUrl) this.imgWidth = 683 this.imgHeight = 360 this.imgUrl && this.initImgCanvas() // Assume backendData is the response from the backend containing checkbox values const backendData = { vehicle_type: res.vehicle_type, standard_plate: res.standard_plate, penqi_plate: res.penqi_plate, plate_color: res.plate_color, car_color: res.car_color, car_axle: res.car_axle, } // const backendData = { // vehicle_type: 1, // standard_plate: 1, // penqi_plate: 1, // plate_color: 1, // car_color: 1, // car_axle: 1, // } // Extract checkbox values into an array const selectedCheckboxes = Object.keys(backendData).filter( (key) => backendData[key] ) // Update the value property of the checkbox group this.form.setFieldsValue({ 'checkbox-group': selectedCheckboxes, }) }, drawImageClickFn(e) { let saveCtx = this.saveCtx // 使用保存画布的上下文进行绘制 if (e.offsetX || e.layerX) { let pointX = e.offsetX == undefined ? e.layerX : e.offsetX let pointY = e.offsetY == undefined ? e.layerY : e.offsetY // 添加点到绘制点数组 this.drawingPoints.push([pointX, pointY]) // 当有两个点时,绘制一条线段并重置 drawingPoints if (this.drawingPoints.length === 2) { // 在保存画布上绘制线条 saveCtx.beginPath() saveCtx.moveTo(...this.drawingPoints[0]) saveCtx.lineTo(...this.drawingPoints[1]) saveCtx.strokeStyle = 'yellow' saveCtx.lineWidth = '5' saveCtx.stroke() saveCtx.closePath() // 将绘制的线段保存到 drawedPoints 用于后续处理 this.drawedPoints.push([...this.drawingPoints]) console.log('finish points', this.drawedPoints) // 重置 drawingPoints 以开始新的线段 this.drawingPoints = [] } } }, drawImageMoveFn(e) { let drawCtx = this.drawCtx if (e.offsetX || e.layerX) { let pointX = e.offsetX == undefined ? e.layerX : e.offsetX let pointY = e.offsetY == undefined ? e.layerY : e.offsetY // 绘制 drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) // 绘制点 drawCtx.fillStyle = 'yellow' this.drawingPoints.forEach((item, i) => { drawCtx.beginPath() drawCtx.arc(...item, 6, 0, 180) drawCtx.fill() //填充 }) // 绘制动态区域 drawCtx.save() drawCtx.beginPath() this.drawingPoints.forEach((item, i) => { drawCtx.lineTo(...item) }) drawCtx.lineTo(pointX, pointY) drawCtx.lineWidth = '3' drawCtx.strokeStyle = 'yellow' drawCtx.fillStyle = 'rgba(255, 0, 0, 0.3)' drawCtx.stroke() drawCtx.fill() //填充 drawCtx.restore() } }, drawSaveArea(points) { // console.log(points, "points"); if (points.length === 0) return this.saveCtx.save() this.saveCtx.beginPath() points.forEach((item, i) => { this.saveCtx.lineTo(...item) }) this.saveCtx.closePath() this.saveCtx.lineWidth = '5' this.saveCtx.fillStyle = 'rgba(255,0, 255, 0.3)' this.saveCtx.strokeStyle = 'yellow' this.saveCtx.stroke() this.saveCtx.fill() //填充 this.saveCtx.restore() }, savePoints() { if (this.drawedPoints.length > 10) { this.$message.warn('线段数量不能超过10条') return } // 将画布坐标数据转换成提交数据 let objectPoints = [] // "object": [{"polygon": {"x1":700,"y1":273,"x2":975,"y2":278,"x3":1107,"y3":368,"x4":718,"y4":354} }] objectPoints = this.drawedPoints.map((area) => { let polygon = {} area.forEach((point, i) => { polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio) polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio) }) return { polygon: polygon, } }) console.log('original points', objectPoints) objectPoints.forEach((data) => { if (data.polygon.y1 > data.polygon.y2) { // 交换 x1 和 x2 ;[data.polygon.x1, data.polygon.x2] = [ data.polygon.x2, data.polygon.x1, ] // 交换 y1 和 y2 ;[data.polygon.y1, data.polygon.y2] = [ data.polygon.y2, data.polygon.y1, ] } }) objectPoints.sort((a, b) => { let midPointX1 = (a.polygon.x1 + a.polygon.x2) / 2 let midPointX2 = (b.polygon.x1 + b.polygon.x2) / 2 return midPointX1 - midPointX2 }) console.log('switched points', objectPoints) const lineNames = [ 'lineone', 'linetwo', 'linethree', 'linefour', 'linefive', 'linesix', 'lineseven', 'lineeight', 'linenine', 'lineten', ] const coordinates = { lineone: [], linetwo: [], linethree: [], } this.drawedPoints.forEach((points, index) => { if (index < 10) { let lineName = lineNames[index] coordinates[lineName] = points.map((point) => Math.round(point * this.ratio) ) } }) objectPoints.forEach((data, index) => { const { x1, y1, x2, y2 } = data.polygon let lineName = lineNames[index] coordinates[lineName] = [x1 * 6, y1 * 6, x2 * 6, y2 * 6] }) console.log('converted points', coordinates) this.submitPoints = coordinates console.log('最终提交坐标', this.submitPoints) // 获取所有选项 const allOptions = [ 'vehicle_type', 'standard_plate', 'penqi_plate', 'plate_color', 'car_color', 'car_axle', ] // 获取勾选的值 const checkedValues = this.form.getFieldValue('checkbox-group') // 构建要发送到后台的数据 let dataToSend = {} allOptions.forEach((option) => { dataToSend[option] = checkedValues.includes(option) ? 1 : 0 }) dataToSend = { ...dataToSend, coordinates: coordinates, } console.log('combined value', dataToSend) this.parameter = dataToSend this.postConfig() }, async postConfig() { const res = await reqPostConfig(this.parameter) if (res.result_code === 200) { this.$message.success('保存配置成功') } else { this.$message.error('配置失败') } }, async testConfig() { this.loading = true const res = await reqTestConfig() console.log('test config', res) if (res.result_code === 200) { this.loadData = [res] // console.log('test data', this.loadData) this.$message.success('获取测试数据成功') this.loading = false this.initCanvas() // this.initImgCanvas() // Call initImgCanvas here this.getImage() this.imgUrl = 'data:image/png;base64,' + res.scribing_image } else { this.$message.error('获取测试数据失败') this.loading = false } }, renderDatas() { // 将提交数据数据转换成画布坐标 this.drawedPoints = this.submitData.map((item) => { let polygon = item.polygon let points = [] for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) { if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) { points.push([ polygon[`x${i}`] / this.ratio, polygon[`y${i}`] / this.ratio, ]) } } this.drawSaveArea(points) return points }) }, }, } .main { margin: 0; padding: 0; } .upper { display: flex; height: 400px; width: 100%; margin-bottom: -25px; .upper-left { background-color: skyblue; width: 683px; min-width: 683px; height: 360px; min-height: 360px; position: relative; } .upper-right { margin: 0 0 0 20px; display: flex; flex-direction: column; .draw-buttons { display: flex; flex-direction: column; .button-group { margin-bottom: 10px; .upper-button { margin: 0 10px 5px 0; height: 5.5vh; width: 8.5vw; font-size: 1vw; font-weight: bold; } } } .function { width: 27vw; margin: -20px 0; .submit-button { height: 5.5vh; width: 8vw; font-size: 16px; margin: 0 0 0 2.5vw; font-weight: bold; } } } } .test-button { height: 5.5vh; width: 8vw; font-size: 16px; margin-bottom: 10px; } /deep/ .ant-form-item label { font-weight: bold; font-size: 1vw; } .canvas-wrap { width: 683px; height: 360px; min-width: 683px; min-height: 360px; // margin: 0px auto; background-color: skyblue; position: relative; } #imgCanvas, #drawCanvas, #saveCanvas { background: rgba(255, 0, 255, 0); position: absolute; // top: 50%; // left: 50%; // transform: translate(-50%, -50%); width: 683px; height: 360px; } #drawCanvas { z-index: 2; }



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有