前言
前端有一个需求,对上传的图片进行检测识别,通过返回的接口坐标数据,对图片的某些区域进行框选并标注。如图:
开始
1、上传功能使用elementui的upload插件;
2、在图片上进行标注功能是元素定位在图片上层,通过坐标进行框选;
代码:
<template>
<div class="container">
<!-- form表单 -->
<div>
<el-form ref="form" :model="form" label-width="80px" style="width:500px;">
<el-form-item label="模型目录">
<el-input v-model="form.trainId" disabled></el-input>
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="form.deviceName" disabled></el-input>
</el-form-item>
<el-form-item label="图片">
<el-upload
action="#"
list-type="picture-card"
:auto-upload="false"
:on-change="handleImgChange"
:file-list="fileList"
:limit="1"
:on-exceed="handleExceed">
<i slot="default" class="el-icon-plus"></i>
<div slot="file" slot-scope="{file}">
<img
class="el-upload-list__item-thumbnail"
:src="file.url" alt=""
>
<span class="el-upload-list__item-actions">
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" :loading = "loading">立即检测</el-button>
<el-button type="default" @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 结果显示区域 -->
<div class="title">图像检测结果</div>
<div class="imgContainer">
<img :src="imgUrl" alt="" v-if="imgUrl" @load="imageLoad">
<div class="mask" v-if="imgUrl">
<div class="ltBox"
v-for="(item,idx) in imgData"
:key="idx"
:style="{left: Number(item.bbox[0])/ratio +'px', top: item.bbox[1]/ratio+'px', width: item.bbox[2]/ratio+'px', height: item.bbox[3]/ratio+'px' }">
<span class="boxTitle">{{item.category_name}}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "HomeView",
data() {
return {
form: {
trainId: 'model_39164_cpu', deviceName: 'cpu', pic01: {},
},
imgUrl: '', // 图片url,用于检测完显示用
fileList: [], // 上传的图片列表
disabled: false,
imgData: [], // 图片标注数据
ratio: 1, // 图片显示到框里时的缩放比值
loading: false,
};
},
mounted() {
},
methods: {
handleImgChange(res, file) {
this.fileList = file;
// this.imgUrl = file[0].url;
this.form.pic01 = file[0].raw;
console.log(file);
},
handleRemove(file) {
this.fileList = [];
// this.imgUrl = '';
this.form.pic01 = {};
this.imgUrl = '';
this.imgData = [];
},
onSubmit(){
let _self = this;
if (JSON.stringify(this.form.pic01) == "{}") {
this.$message.error("请上传图片");
return;
}
this.loading = true;
let formData = new FormData();
formData.append("trainId", this.form.trainId);
formData.append("device_name", this.form.deviceName);
formData.append("pic01", this.form.pic01);
this.$axios({
method: 'POST',
url: '/api/prediction',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
'charset': 'UTF-8'
}
}).then(function(res) {
console.log(res);
if (res.data.code === 200) {
_self.loading = false;
// 检测框显示图片
_self.imgUrl = _self.fileList[0].url;
// 获取标注数据
let name = _self.fileList[0].name; // 键名为上传图片名称
_self.imgData = res.data[name].result;
} else {
_self.$message({
message: res.data.errorMsg,
type: 'error',
duration: 5 * 1000
})
}
});
},
imageLoad() {
console.log('图片加载完毕');
// 计算检测框与实际图片大小的比值
// this.imgUrl = file[0].url;
let image = new Image();
image.src = this.imgUrl;
let imgWidth = image.width;
if (imgWidth <= 1000) { // 框大于图片真实尺寸,不用缩放,按照正常偏移数据
this.ratio = 1;
} else { // 图片真实尺寸大于框宽度,算出缩放比例。模版里:正常偏移数据/缩放比例ratio
this.ratio = image.width / 1000;
}
console.log(image.width);
},
reset() {
this.fileList = [];
this.imgUrl = '';
this.form.pic01 = {};
this.imgData = [];
},
handleExceed(files, fileList) {
this.$message.warning("只能选择一张图片");
},
}
};
</script>
<style scope>
.container{
width: 1000px;
box-sizing: border-box;
margin: 80px auto;
}
.title{
font-size: 16px;
margin: 60px 0 20px;
font-weight: bold;
}
.imgContainer{
position: relative;
/* box-sizing: border-box; */
border: 1px dashed #000;
width: 100%;
min-height: 300px;
}
.imgContainer img{
width: 100%;
}
.mask{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.mask .ltBox{
position: absolute;
border: 1px solid red;
/* width: 100px;
height:100px;
left: 100px;
top: 100px; */
}
.mask .ltBox span{
display: inline-block;
font-size: 14px;
padding-left: 2px;
color: #fff;
width: 16px;
height: 20px;
background: rgba(0,0,0,.4);
}
</style>
返回数据格式为:
{"03.png":{"result":[{"angle":0,"bbox":[909.2269287109375,357.05908203125,86.85955810546875,130.1535034179688],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9995452761650085,"type":"resultRect"},{"angle":0,"bbox":[909.5748901367188,190.1406097412109,86.5870361328125,132.7530364990234],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9995043277740479,"type":"resultRect"},{"angle":0,"bbox":[427.7415161132813,599.2159423828125,108.297607421875,105.1603393554688],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9994520545005798,"type":"resultRect"},{"angle":0,"bbox":[910.0458984375,46.47737503051758,86.25689697265625,130.2275543212891],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9993937015533447,"type":"resultRect"},{"angle":0,"bbox":[427.6333923339844,863.2357177734375,108.3089904785156,99.88446044921875],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9991784691810608,"type":"resultRect"},{"angle":0,"bbox":[790.2532958984375,191.2887725830078,93.9698486328125,129.5539703369141],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9990857839584351,"type":"resultRect"},{"angle":0,"bbox":[544.385498046875,602.537841796875,98.578125,98.70477294921875],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9990641474723816,"type":"resultRect"},{"angle":0,"bbox":[428.9845275878906,739.2496337890625,106.2877502441406,95.54400634765625],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9990469813346863,"type":"resultRect"},{"angle":0,"bbox":[546.028076171875,739.0665893554688,97.27117919921875,96.72930908203125],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9989651441574097,"type":"resultRect"},{"angle":0,"bbox":[549.2886352539063,866.919677734375,92.69256591796875,99.43603515625],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9987188577651978,"type":"resultRect"},{"angle":0,"bbox":[791.1380004882813,48.05636215209961,95.0599365234375,126.0226898193359],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9986415505409241,"type":"resultRect"},{"angle":0,"bbox":[791.9155883789063,358.5489501953125,91.2598876953125,125.5012512207031],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9971223473548889,"type":"resultRect"},{"angle":0,"bbox":[425.3143310546875,47.26962661743164,109.3796997070313,127.9037933349609],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9970883727073669,"type":"resultRect"},{"angle":0,"bbox":[911.8796997070313,736.1849365234375,83.3861083984375,104.4035034179688],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9966270923614502,"type":"resultRect"},{"angle":0,"bbox":[330.7501220703125,600.6314086914063,74.79995727539063,102.3368530273438],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9962628483772278,"type":"resultRect"},{"angle":0,"bbox":[911.684814453125,598.4668579101563,83.82977294921875,107.4171752929688],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9958639144897461,"type":"resultRect"},{"angle":0,"bbox":[332.485107421875,738.0242309570313,73.73397827148438,98.7982177734375],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9956913590431213,"type":"resultRect"},{"angle":0,"bbox":[678.7830200195313,192.1672668457031,88.1395263671875,131.537109375],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.995394229888916,"type":"resultRect"},{"angle":0,"bbox":[680.2387084960938,358.936767578125,86.17327880859375,126.4320983886719],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9953811168670654,"type":"resultRect"},{"angle":0,"bbox":[427.2458190917969,356.8890380859375,105.9344787597656,126.5965881347656],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9953643083572388,"type":"resultRect"},{"angle":0,"bbox":[426.9499206542969,193.8685760498047,107.8788757324219,123.8152008056641],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9953345656394958,"type":"resultRect"},{"angle":0,"bbox":[911.595703125,862.9237060546875,84.64520263671875,108.9039916992188],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9952036142349243,"type":"resultRect"},{"angle":0,"bbox":[792.5260009765625,859.0792236328125,93.56689453125,111.516845703125],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9951752424240112,"type":"resultRect"},{"angle":0,"bbox":[542.8145751953125,44.25643539428711,97.0166015625,130.5228576660156],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9950712919235229,"type":"resultRect"},{"angle":0,"bbox":[328.7076110839844,45.84556579589844,79.90731811523438,130.7186737060547],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9948970675468445,"type":"resultRect"},{"angle":0,"bbox":[328.0628662109375,865.086181640625,80.9405517578125,99.62249755859375],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9948451519012451,"type":"resultRect"},{"angle":0,"bbox":[329.5053405761719,356.4360046386719,77.6827392578125,129.4084167480469],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9946455955505371,"type":"resultRect"},{"angle":0,"bbox":[329.6501770019531,192.0917816162109,79.248291015625,129.7602996826172],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9944493770599365,"type":"resultRect"},{"angle":0,"bbox":[546.0228271484375,192.6796722412109,94.2720947265625,126.7196197509766],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9937253594398499,"type":"resultRect"},{"angle":0,"bbox":[679.3350830078125,45.87873077392578,86.26666259765625,129.9478454589844],"category_id":"0","category_name":"d","pointsArr":null,"res_num":36,"scores":0.9934400916099548,"type":"resultRect"},{"angle":0,"bbox":[794.0667114257813,736.28662109375,92.5123291015625,106.004150390625],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9919258952140808,"type":"resultRect"},{"angle":0,"bbox":[544.1696166992188,356.3648986816406,96.20233154296875,126.95068359375],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9917498230934143,"type":"resultRect"},{"angle":0,"bbox":[678.4888305664063,863.577392578125,92.967529296875,106.4488525390625],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9908513426780701,"type":"resultRect"},{"angle":0,"bbox":[681.7239379882813,736.389404296875,87.13897705078125,107.5022583007813],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9900141954421997,"type":"resultRect"},{"angle":0,"bbox":[679.4708251953125,599.4293212890625,90.96051025390625,106.7920532226563],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.9883459210395813,"type":"resultRect"},{"angle":0,"bbox":[793.232177734375,599.1714477539063,93.35357666015625,106.982421875],"category_id":"1","category_name":"l","pointsArr":null,"res_num":36,"scores":0.986026406288147,"type":"resultRect"}]},"code":200,"errorCode":0,"errorMsg":"ok"}
需要注意的是:
1、上传图片时候,后台需要formData格式。
打印upload插件的file参数,
handleImgChange(res, file) {
this.fileList = file;
this.form.pic01 = file[0].raw;
console.log(file);
},
结果为:
这里需要把’raw’拿出来,放到formData里面,传给后台。有其他参数,一并传送。例如:
let formData = new FormData();
formData.append("trainId", this.form.trainId);
formData.append("device_name", this.form.deviceName);
formData.append("pic01", file.raw);
2、监听图片加载完成事件,获取图片真实宽高。
由于我们需要在一个固定宽度的区域中显示图片(图片宽度充满区域,也就是说宽度为区域的百分之百)并框选标注,涉及到选框的偏移位置信息,所以必须拿到图片的真实宽度,算出比值,再进行偏移量的计算。
模版字符串里面:
<img :src="imgUrl" alt="" @load="imageLoad">
js里面:
imageLoad() {
console.log('图片加载完毕');
let image = new Image();
image.src = this.imgUrl;
let imgWidth = image.width;
if (imgWidth <= 1000) { // 框大于图片真实尺寸,不用缩放,按照正常偏移数据
this.ratio = 1;
} else { // 图片真实尺寸大于框宽度,算出缩放比例。模版里:正常偏移数据/缩放比例ratio
this.ratio = image.width / 1000;
}
console.log(image.width);
},