Fuyuu
1 year ago
16 changed files with 718 additions and 171 deletions
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<a-modal :visible="visible" :title="title" width="1080px" @cancel="handleCancel" destroyOnClose> |
|||
<a-row> |
|||
<div class="mpegPlayer" ref="mpegPlayer"></div> |
|||
</a-row> |
|||
<template #footer=""> |
|||
<a-button @click="handleCancel">关闭</a-button> |
|||
</template> |
|||
</a-modal> |
|||
</template> |
|||
<script lang="ts" setup> |
|||
import { ref, getCurrentInstance, onMounted, nextTick } from 'vue'; |
|||
const { proxy }: any = getCurrentInstance(); |
|||
const props = defineProps({ |
|||
title: { |
|||
type: String, |
|||
default: '查看监控', |
|||
}, |
|||
visible: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
// rtsp视频流地址 |
|||
videoUrl: { |
|||
type: String, |
|||
default: 'rtsp://admin:hk123456@192.168.1.71:554/', |
|||
}, |
|||
}); |
|||
|
|||
/* 播放rtsp视频相关 */ |
|||
import MpegPlayer from 'jsmpeg-player'; |
|||
const { ipcRenderer } = require('electron'); |
|||
// DOM承载实例 |
|||
const mpegPlayer = ref(); |
|||
// 消息 |
|||
const msg = ref(''); |
|||
// 播放容器 |
|||
let player: any = ref(null); |
|||
// 开启播放 |
|||
const open = () => { |
|||
const res = ipcRenderer.sendSync('openRtsp', props.videoUrl); |
|||
if (res.code === 200) { |
|||
player.value = new MpegPlayer.VideoElement(mpegPlayer.value, res.ws); |
|||
} |
|||
msg.value = res.msg; |
|||
}; |
|||
// 关闭播放 |
|||
const close = () => { |
|||
const res = ipcRenderer.sendSync('closeRtsp', props.videoUrl); |
|||
msg.value = res.msg; |
|||
}; |
|||
|
|||
function handleCancel() { |
|||
nextTick(() => { |
|||
// 关闭窗口 |
|||
proxy.$parent.videoVisible = false; |
|||
// 关闭播放 |
|||
close(); |
|||
}); |
|||
} |
|||
|
|||
onMounted(() => { |
|||
open(); |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.mpegPlayer { |
|||
width: 100%; |
|||
height: 580px; |
|||
background: #000; |
|||
} |
|||
</style> |
@ -0,0 +1,360 @@ |
|||
<!-- |
|||
标注组件1.0 |
|||
|
|||
目录位置:AI智能化区域监控 -> 视频智能监控范围 -> 标注区域 |
|||
功能概述:引入轻量级图片2d标注组件canvas-select,通过添加透明图层,与rtsp视频流结合,实现视频监控区域标注 |
|||
--> |
|||
<template> |
|||
<a-modal |
|||
:keyboard="false" |
|||
:maskClosable="false" |
|||
:mask="false" |
|||
:forceRender="true" |
|||
:visible="visible" |
|||
:title="title" |
|||
width="1080px" |
|||
@cancel="handleCancel" |
|||
destroyOnClose |
|||
> |
|||
<a-row class="video_area"> |
|||
<canvas ref="canvas" class="container"></canvas> |
|||
<div class="mpegPlayer" ref="mpegPlayer"></div> |
|||
</a-row> |
|||
<a-row class="operate_area"> |
|||
<div class="area_box"> |
|||
<a-space :size="18"> |
|||
<a-button shape="round" size="middle" @click="change(0)"> <ApiOutlined /> 退出创建</a-button> |
|||
<a-button type="primary" shape="circle" size="large" @click="change(1)" title="创建矩形"> |
|||
<img class="icon_style" src="./assets/rect_icon.png" /> |
|||
</a-button> |
|||
<a-button type="primary" shape="circle" size="large" @click="change(2)" title="创建多边形"> |
|||
<img class="icon_style" src="./assets/polo_icon.png" /> |
|||
</a-button> |
|||
<a-button type="primary" shape="circle" size="large" @click="change(3)" title="创建标记点"> |
|||
<img class="icon_style" src="./assets/point_icon.png" /> |
|||
</a-button> |
|||
<a-button type="primary" shape="circle" size="large" @click="change(4)" title="创建线"> |
|||
<img class="icon_style" src="./assets/line_icon.png" /> |
|||
</a-button> |
|||
<a-button type="primary" shape="circle" size="large" @click="change(5)" title="创建圆"> |
|||
<img class="icon_style" src="./assets/circle_icon.png" /> |
|||
</a-button> |
|||
<a-button type="primary" shape="round" danger size="middle" @click="clear()"> <FormatPainterOutlined />清除标注 </a-button> |
|||
</a-space> |
|||
</div> |
|||
</a-row> |
|||
<template #footer=""> |
|||
<a-popconfirm title="是否保存标注?" ok-text="确认" cancel-text="取消" @confirm="handleOk()" @cancel="handleCancel"> |
|||
<a-button type="primary">保存</a-button> |
|||
</a-popconfirm> |
|||
</template> |
|||
</a-modal> |
|||
</template> |
|||
<script lang="ts" setup> |
|||
import { ref, getCurrentInstance, onMounted, onBeforeUnmount, nextTick } from 'vue'; |
|||
import { ApiOutlined, FormatPainterOutlined } from '@ant-design/icons-vue'; |
|||
const { proxy }: any = getCurrentInstance(); |
|||
const props = defineProps({ |
|||
title: { |
|||
type: String, |
|||
default: '区域标注', |
|||
}, |
|||
visible: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
// rtsp视频流地址 |
|||
videoUrl: { |
|||
type: String, |
|||
default: 'rtsp://admin:hk123456@192.168.1.71:554/', |
|||
}, |
|||
// 绘制数据——回显 |
|||
labelData: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
// 点位数据——传递 |
|||
locationData: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
}); |
|||
|
|||
const emit = defineEmits(['update:label-data', 'update:location-data']); |
|||
|
|||
/* 播放rtsp视频相关 */ |
|||
import MpegPlayer from 'jsmpeg-player'; |
|||
const { ipcRenderer } = require('electron'); |
|||
// DOM承载实例 |
|||
const mpegPlayer = ref(); |
|||
// 消息 |
|||
const msg = ref(''); |
|||
// 播放容器 |
|||
let player: any = ref(null); |
|||
// 开启播放 |
|||
const open = () => { |
|||
const res = ipcRenderer.sendSync('openRtsp', props.videoUrl); |
|||
if (res.code === 200) { |
|||
player.value = new MpegPlayer.VideoElement(mpegPlayer.value, res.ws); |
|||
} |
|||
msg.value = res.msg; |
|||
}; |
|||
// 关闭播放 |
|||
const close = () => { |
|||
const res = ipcRenderer.sendSync('closeRtsp', props.videoUrl); |
|||
msg.value = res.msg; |
|||
}; |
|||
|
|||
/* 标注区域相关 */ |
|||
// 导入2d绘图插件canvas-Select |
|||
import CanvasSelect from 'canvas-select'; |
|||
// canvas实例 |
|||
const canvas = ref(null); |
|||
// 导入透明静态图片 |
|||
const hideUrl = new URL('./assets/hide.png', import.meta.url).href; |
|||
// 标注实例 |
|||
let instance: any = ref(null); |
|||
// 图形标注列表 |
|||
const option: any = ref([]); |
|||
|
|||
// 选择绘制工具 |
|||
function change(num: any) { |
|||
instance.createType = num; |
|||
} |
|||
|
|||
// 清楚标注数据 |
|||
function clear() { |
|||
instance.setData([]); |
|||
} |
|||
|
|||
// 保存数据 |
|||
function save() { |
|||
/* 保存绘制数据,用于回显 */ |
|||
// 清除所有激活状态 |
|||
instance.dataset.forEach((item) => { |
|||
item.active = false; |
|||
}); |
|||
// 更新标注数据到表单 |
|||
emit('update:label-data', JSON.stringify(instance.dataset)); |
|||
/* 保存区域界限节点数据,用于后端分析 */ |
|||
// 二次处理 |
|||
const newData = instance.dataset.map((item) => { |
|||
let typeString; |
|||
switch (item.type) { |
|||
case 1: |
|||
typeString = 'rect'; |
|||
break; |
|||
case 2: |
|||
typeString = 'polygon'; |
|||
break; |
|||
case 3: |
|||
typeString = 'dot'; |
|||
break; |
|||
case 4: |
|||
typeString = 'line'; |
|||
break; |
|||
case 5: |
|||
typeString = 'circle'; |
|||
break; |
|||
default: |
|||
typeString = ''; |
|||
} |
|||
|
|||
const newShape: any = { |
|||
type: typeString, |
|||
coor: item.coor, |
|||
}; |
|||
|
|||
if (item.type === 5) { |
|||
// 如果type为5(circle),则添加radius属性 |
|||
newShape.radius = item.radius; |
|||
} |
|||
|
|||
return newShape; |
|||
}); |
|||
// 更新节点数据到表单 |
|||
emit('update:location-data', JSON.stringify(newData)); |
|||
} |
|||
// 删除选中图形 backspace / 回退绘制节点 esc |
|||
function changeSelect(event) { |
|||
if (event.keyCode == 8) { |
|||
// 删除选中图形 |
|||
instance.dataset = instance.dataset.filter((item) => !item.active); |
|||
// 更新视图 |
|||
instance.update(); |
|||
} else if (event.keyCode == 27) { |
|||
// 是否节点已删除完毕 |
|||
let delete_status = 0; |
|||
// 回退绘制节点 |
|||
instance.dataset.forEach((item) => { |
|||
if (item.active === true && item.creating === true) { |
|||
// 矩形 |
|||
if (item.type == 1) { |
|||
if (item.coor.length > 1) { |
|||
// 回退 |
|||
item.coor = []; |
|||
// 删除完毕 |
|||
delete_status = 1; |
|||
} |
|||
} |
|||
// 圆形 |
|||
else if (item.type == 5) { |
|||
if (item.coor.length > 1) { |
|||
// 回退 |
|||
item.coor = []; |
|||
item.radius = 0; |
|||
// 删除完毕 |
|||
delete_status = 1; |
|||
} |
|||
} |
|||
// 其他图形 |
|||
else { |
|||
if (item.coor.length > 1) { |
|||
// 回退上一个节点 |
|||
item.coor.pop(); |
|||
} else { |
|||
// 删除完毕 |
|||
delete_status = 1; |
|||
} |
|||
} |
|||
// 更新视图 |
|||
instance.update(); |
|||
} |
|||
}); |
|||
if (delete_status === 1) { |
|||
// 删除残余数据 |
|||
instance.dataset = instance.dataset.filter((item) => !item.active); |
|||
// 更新视图 |
|||
instance.update(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
onMounted(() => { |
|||
// 添加键盘事件监听 |
|||
document.addEventListener('keydown', changeSelect); |
|||
//打开窗口就打开监控 |
|||
open(); |
|||
// 载入绘图数据 |
|||
if (props.labelData == '') { |
|||
option.value = []; |
|||
} else { |
|||
option.value = JSON.parse(props.labelData); |
|||
} |
|||
// 创建实例 |
|||
instance = new CanvasSelect('.container', hideUrl); |
|||
// 标签最大长度 |
|||
instance.labelMaxLen = 10; |
|||
// 禁用滚动缩放 |
|||
instance.scrollZoom = false; |
|||
// 图形数据赋值到实例 |
|||
instance.setData(option.value); |
|||
// 形状边线宽度 |
|||
instance.lineWidth = 2; |
|||
// 标签字体 |
|||
instance.labelFont = '12px Arial'; |
|||
// 标签填充颜色 |
|||
instance.labelFillStyle = '#fa4545'; |
|||
// 标签文字颜色 |
|||
instance.textFillStyle = '#fff'; |
|||
// 控制点半径 |
|||
instance.ctrlRadius = 4; |
|||
// 图片加载完成 |
|||
instance.on('load', (src: any) => { |
|||
console.log('image load', src); |
|||
}); |
|||
|
|||
// 添加 |
|||
instance.on('add', (info: any) => { |
|||
// 添加默认标签 |
|||
switch (instance.createType) { |
|||
case 1: |
|||
info.label = '矩形防区'; |
|||
break; |
|||
case 2: |
|||
info.label = '多边形防区'; |
|||
break; |
|||
case 3: |
|||
info.label = '标记点位'; |
|||
break; |
|||
case 4: |
|||
info.label = '报警界线'; |
|||
break; |
|||
case 5: |
|||
info.label = '圆形防区'; |
|||
break; |
|||
default: |
|||
info.label = '未定义'; |
|||
} |
|||
// 更新画布 |
|||
instance.update(); |
|||
}); |
|||
// 删除 - 弃用内置删除方法 |
|||
// instance.on('delete', (info: any) => { |
|||
// console.log('删除', info); |
|||
// }); |
|||
// 选中 |
|||
instance.on('select', (shape: any) => { |
|||
console.log('选中', shape); |
|||
}); |
|||
// 更新 |
|||
instance.on('updated', (result: any) => { |
|||
console.log('更新', result); |
|||
}); |
|||
}); |
|||
// 在组件卸载时移除事件监听 |
|||
// 考虑到你想要的是在页面卸载前移除事件监听,因此这里使用了`beforeUnmount` |
|||
// 如果你希望在页面卸载后再移除事件监听,可以使用`onUnmounted` |
|||
onBeforeUnmount(() => { |
|||
document.removeEventListener('keydown', changeSelect); |
|||
}); |
|||
// 确定 |
|||
async function handleOk() { |
|||
// 保存提交 |
|||
save(); |
|||
// 关闭窗口 |
|||
handleCancel(); |
|||
} |
|||
// 取消 |
|||
function handleCancel() { |
|||
nextTick(() => { |
|||
// 关闭窗口 |
|||
proxy.$parent.videoVisible = false; |
|||
// 关闭播放 |
|||
close(); |
|||
}); |
|||
} |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.video_area { |
|||
width: 100%; |
|||
height: 580px; |
|||
margin: 0 auto; |
|||
position: relative; |
|||
.container { |
|||
width: 1080px; |
|||
height: 580px; |
|||
position: absolute; |
|||
z-index: 9999; |
|||
left: 0; |
|||
top: 0; |
|||
} |
|||
.mpegPlayer { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: #000; |
|||
} |
|||
} |
|||
.operate_area { |
|||
width: 100%; |
|||
margin: 10px auto; |
|||
.area_box { |
|||
margin: 0 auto; |
|||
} |
|||
.icon_style { |
|||
width: 24px; |
|||
height: 24px; |
|||
margin: 0 auto; |
|||
} |
|||
} |
|||
</style> |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
Loading…
Reference in new issue