功能描述:
1、web端界面展示,支持上传本地视频,进行逐帧识别并且返回模型推理结果进行展示(前端上传原视频,后端返回逐帧识别的视频)
2、支持设定电子围栏区域,设定围栏后界面需要相应可视化,只有区域内的事件才能报警(后端返回一个图片,前端在图片上绘制多边形,把多边形的顶点传给后端,后端返回带有图标的视频)
详细代码
<template>
<div class="home">
<div style="display:flex;justify-content: center;">
<el-upload
class="upload-demo"
action="http://10.127.173.6:30023/api/upload"
multiple
:limit="1"
accept=".mp4"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleSuccess"
:on-error="handleError"
v-loading="loading"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button v-if="videoSrc" type="primary" @click="initKonvaStage('poly')" style="height: 40px;margin: 0 10px;">获取视频当前帧</el-button>
<el-button v-if="videoSrc" type="primary" @click="checkVideo" style="height: 40px">视频检测</el-button>
</div>
<!-- 视频 -->
<div style="display: flex;">
<!-- 原始视频 -->
<div v-loading="loading" v-if="videoSrc">
<video ref="video" :src="videoSrc" controls style="width: 640px; height: 360px;margin-top: 20px;"></video>
<div>原始视频</div>
<!-- 多边形绘制 -->
<div v-if="videoSrc" id="map" ref="map"></div>
</div>
<!-- 检测视频 -->
<div v-loading="checkVideoLoading" element-loading-text="识别中,请等待..." style="width: 640px;height: 360px;">
<div v-if="checkVideoSrc">
<video :src="checkVideoSrc" controls style="width: 640px; height: 360px; margin-top: 20px;margin-left: 10px;"></video>
<div>检测视频</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import Konva from "konva";
export default {
name: 'HomeView',
data() {
return {
w: 1920,
h: 1080,
isShow: false,
videoSrc: '',
loading: false,
checkVideoLoading: false,
// 图片
capturedImage: null,
// 多边形绘制
stage: null,
layer: null,
shape: null,
// image: { src: `${this.capturedImage}`},
currentTool: "",
drawing: false, //一开始不能绘画
currentDrawingShape: null, //现在绘画的图形
pointStart: [], //记录鼠标按下的起始坐标
polygonPoints: [], //存储绘画多边形各个顶点的数组
uploadPointArr: [],
filePath: '',
checkVideoSrc: ''
};
},
methods: {
beforeUpload(file) {
this.videoSrc = ''
const fileName = file.name
const extention = fileName.split('.')[1]
this.loading = true
if( extention != 'mp4') {
this.$message.error('上传的文件格式不正确,请上传MP4格式的文件!');
this.loading = false
return false
}
},
handleExceed(files, fileList) {
this.loading = false
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
handleSuccess(data){
this.filePath = data.file_path
this.videoSrc = 'http://10.127.173.6:30023/static/' + data.file_path
this.loading = false
this.capturedImage = 'http://10.127.173.6:30023/static/' + data.frame_file
},
handleError() {
this.loading = false
},
// 视频检测
checkVideo() {
this.uploadPointArr.forEach((item,index) => {
if(INDEX % 2 == 0) {
this.uploadPointArr[index] = (item*this.w) / 640
} else {
this.uploadPointArr[index] = (item * this.h) / 360
}
})
this.checkVideoLoading = true
axios.post('http://10.127.173.6:30023/api/detect',
{
fileName: this.filePath,
polygon_points: this.uploadPointArr
}
).then((res) => {
this.checkVideoLoading = false
this.checkVideoSrc = 'http://10.127.173.6:30023/static/' + res.data.file_path
})
.catch(() => {
this.checkVideoLoading = false
})
},
/**
*初始化konva舞台
*/
initKonvaStage(key) {
this.currentTool = key
//1实例化stage层
this.stage = new Konva.Stage({
container: "map",
width: this.$refs.map.clientWidth,
height: this.$refs.map.clientHeight,
});
this.stage.container().style.cursor = "crosshair";
//2实例化layer层
this.layer = new Konva.Layer();
var imageObj = new Image();
//imageObj的this是imagedom对象,不是vc
var vc_this = this;
imageObj.onload = function () {
//3实例化shape层
vc_this.shape = new Konva.Image({
x: 0,
y: 0,
width: vc_this.stage.width(),
height: vc_this.stage.height(),
image: imageObj,
});
//4将layer层添加到stage层
vc_this.stage.add(vc_this.layer);
// 5将shape层添加到layer层
vc_this.layer.add(vc_this.shape);
};
imageObj.src = this.capturedImage;
// imageObj.src = require(this.capturedImage)
//给***舞台***绑定事件
//鼠标按下
this.stage.on("mousedown", (e) => {
//图形起始点只能在图片层上,移除变形框
if (e.target === this.shape) {
// 如果有,就移除舞台上唯一一个的变形框
if (this.stage.find("Transformer").length != 0) {
this.stage.find("Transformer")[0].destroy();
}
//如果不在绘画且舞台上的多边形被选中
if (!this.drawing && this.stage.find("Circle").length != 0) {
var circlePoints = this.stage.find("Circle");
for (var i = 0; i < circlePoints.length; i++) {
if (circlePoints[i].visible()) {
//隐藏顶点
this.stage.find("Circle").forEach((element) => {
element.hide();
});
return;
}
}
}
this.layer.draw();
//开始初始绘画
this.stageMousedown(this.currentTool, e);
return;
}
//允许后续点绘画在其他图形上
if (this.drawing) {
this.stageMousedown(this.currentTool, e);
return;
}
});
//鼠标移动
this.stage.on("mousemove", (e) => {
if (this.currentTool && this.drawing) {
//绘画中
this.stageMousemove(this.currentTool, e);
}
});
//鼠标放开
this.stage.on("mouseup", (e) => {
this.stageMouseup(this.currentTool, e);
});
},
/**
* 圆形
* @param //x x坐标
* @param //y y坐标
*/
drawCircle(x, y) {
const circle = new Konva.Circle({
name: "circle",
x: x,
y: y,
radius: 5,
visible: true, //是否显示
fill: "#ffffff",
stroke: "#333333",
draggable: false,
strokeWidth: 0.5,
});
var vc_this = this;
var xChange, yChange;
this.layer.add(circle);
this.layer.draw();
circle.on("dragstart", () => {
var polyPoints = vc_this.currentDrawingShape
.getChildren((element) => {
return element.getClassName() === "Line";
})[0]
.points();
//查找拖拽了多边形的哪个点
for (var i = 0; i < polyPoints.length; i += 2) {
if (
circle.getAttr("x") == polyPoints[i] &&
circle.getAttr("y") == polyPoints[i + 1]
) {
xChange = i;
yChange = i + 1;
break;
}
}
});
circle.on("dragmove", (e) => {
//更改拖拽多边形点的位置
var polyPoints = vc_this.currentDrawingShape
.getChildren((element) => {
return element.getClassName() === "Line";
})[0]
.points();
/* e.evt.offsetX - vc_this.currentDrawingShape.getAttr('x') ---> 抵消拖动组的xy影响 */
polyPoints[xChange] =
e.evt.offsetX - vc_this.currentDrawingShape.getAttr("x");
polyPoints[yChange] =
e.evt.offsetY - vc_this.currentDrawingShape.getAttr("y");
vc_this.currentDrawingShape
.getChildren((element) => {
return element.getClassName() === "Line";
})[0]
.points(polyPoints);
});
return circle;
},
/**
*多边形
* @param points 多边形绘画的各个顶点,类型数组
*/
drawPloygon(points) {
const poly = new Konva.Line({
name: "poly",
points: points,
fill: "red",
stroke: "red",
strokeWidth: 1,
draggable: false,
opacity: 0.3,
lineCap: "round",
lineJoin: "round",
closed: true,
strokeScaleEnabled: false,
});
this.currentDrawingShape = poly;
this.layer.add(poly);
this.layer.draw();
var vc_this = this;
poly.on("mouseenter", () => {
vc_this.stage.container().style.cursor = "move";
});
poly.on("mouseleave", () => {
vc_this.stage.container().style.cursor = "crosshair";
});
poly.on("mousedown", () => {
//如果不是正在绘画图形时,可以显示顶点
if (!vc_this.drawing) {
vc_this.stage.container().style.cursor = "move";
console.log("mousedown");
//设置现在绘画节点的对象为该多边形和顶点的组
vc_this.currentDrawingShape = poly.getParent();
//先隐藏全部顶点
vc_this.stage.find("Circle").forEach((element) => {
element.hide();
//解绑第一个红色顶点的事件
element.off("mousedown");
});
//显示现在操作多边形的原来的顶点
vc_this.currentDrawingShape
.getChildren((element) => {
return element.getClassName() === "Circle";
})
.forEach((element) => {
element.show();
element.setAttr("draggable", false);
});
// 如果要让顶点和多边形一起拖拽,必须设置,多边形不能被拖拽
poly.setAttr("draggable", false);
poly.getParent().setAttr("draggable", false);
//使所有顶点在顶层显示
vc_this.stage.find("Circle").forEach((element) => {
element.moveToTop();
});
vc_this.layer.draw();
} else {
//绘画时,鼠标移入多边形,设置组不可以拖动
vc_this.stage.container().style.cursor = "crosshair";
poly.getParent().setAttr("draggable", false);
}
});
poly.getParent().on("dragend", () => {
vc_this.stage.container().style.cursor = "crosshair";
poly.getParent().setAttr("draggable", false);
});
return poly;
},
/**
* 组件el-menu点击事件
* @param key 索引值
* @param keyPath
*/
handleSelect(key) {
//设置当前工具
this.currentTool = key
},
/**
* 在舞台上鼠标点下发生的事件
* @param currentTool 当前选择的工具
* @param e 传入的event对象
*/
stageMousedown(currentTool, e) {
switch (currentTool) {
case "poly":
//如果数组长度小于2,初始化多边形和顶点,是它们成为一组,否则什么都不做
if (this.polygonPoints.length < 2) {
var x = e.evt.offsetX,
y = e.evt.offsetY;
//拖拽组
var group = new Konva.Group({
x: 0,
y: 0,
name: "pointsAndPoly",
draggable: false,
});
//添加多边形的点
group.add(this.addPoint(e));
//绘画多边形
this.polygonPoints = [x, y];
group.add(this.drawPloygon(this.polygonPoints));
//使所有顶点在顶层显示
this.stage.find("Circle").forEach((element) => {
element.moveToTop();
});
this.layer.add(group);
this.stage.draw();
}
//多边形增加顶点
else {
x = e.evt.offsetX,
y = e.evt.offsetY;
//group继续添加多边形的点
this.currentDrawingShape.getParent().add(this.addPoint(e));
this.polygonPoints.push(x);
this.polygonPoints.push(y);
//绘画多边形
this.currentDrawingShape.setAttr("points", this.polygonPoints);
//使所有顶点在顶层显示
this.stage.find("Circle").forEach((element) => {
element.moveToTop();
});
this.stage.draw();
this.arr = this.polygonPoints
}
break;
default:
break;
}
this.drawing = true;
},
/**
* 鼠标在舞台上移动事件
* @param currentTool 当前选择的工具
* @param e 传入的event对象
*/
stageMousemove(currentTool, e) {
switch (currentTool) {
case "poly":
//多边形初始化后,如果数组长度大于2,鼠标移动时,实时更新下一个点
if (this.polygonPoints.length >= 2) {
var x = e.evt.offsetX,
y = e.evt.offsetY;
var tempPoints = this.polygonPoints.concat();
tempPoints.push(x);
tempPoints.push(y);
this.currentDrawingShape.setAttr("points", tempPoints);
}
break;
default:
break;
}
this.layer.draw();
},
/**
* 鼠标在舞台上移动事件
* @param currentTool 当前选择的工具
* @param e 传入的event对象
*/
stageMouseup(currentTool) {
switch (currentTool) {
default:
break;
}
this.layer.draw();
},
/**
* 增加多边形顶点
* @param e 传入的event对象
*/
addPoint(e) {
if (this.polygonPoints.length == 0) {
var vc_this = this;
//将第一个点标红,并显示
return this.drawCircle(e.evt.offsetX, e.evt.offsetY)
.setAttrs({
fill: "red",
})
.show()
.on("mousedown", () => {
//点击第一个红点,绘画多边形结束
//绘画多边形
this.currentDrawingShape.setAttr("points", this.polygonPoints);
//结束绘画多边形封闭
// this.currentDrawingShape.setAttr('closed', true);
vc_this.drawing = false;
vc_this.polygonPoints = [];
//隐藏所有顶点
vc_this.stage.find("Circle").forEach((element) => {
element.hide();
});
//所有顶点变为白色
vc_this.stage.find("Circle").forEach((element) => {
element.setAttrs({
fill: "#ffffff",
});
});
//把现在的绘画对象更改为点和多边形合成的组
this.currentDrawingShape = this.currentDrawingShape.getParent();
});
} else {
//绘画点并显示
return this.drawCircle(e.evt.offsetX, e.evt.offsetY).show();
}
},
},
}
</script>
<style>
.home{
width:65%;
margin: 0 auto;
}
#map {
background: #ddd;
overflow: hidden;
width: 640px;
height: 360px;
margin: 20px;
}
</style>