|
@@ -1,6 +1,6 @@
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
import axios from 'axios'
|
|
import axios from 'axios'
|
|
-import { v4 as uuidv4 } from 'uuid';
|
|
|
|
|
|
+import { v4 as uuidv4 } from 'uuid'
|
|
import Viewer from 'viewerjs'
|
|
import Viewer from 'viewerjs'
|
|
import 'viewerjs/dist/viewer.css'
|
|
import 'viewerjs/dist/viewer.css'
|
|
|
|
|
|
@@ -8,9 +8,9 @@ const positiveDirectionPOptions = ref([{ label: '+', value: '+' }, { label: '-',
|
|
const positiveDirectionTOptions = ref([{ label: '+', value: '+' }, { label: '-', value: '-' }])
|
|
const positiveDirectionTOptions = ref([{ label: '+', value: '+' }, { label: '-', value: '-' }])
|
|
|
|
|
|
const system = useSystemStore()
|
|
const system = useSystemStore()
|
|
-const currentBallCamera = ref('')
|
|
|
|
|
|
|
|
const addDialogV = ref(false)
|
|
const addDialogV = ref(false)
|
|
|
|
+const updDialogV = ref(false)
|
|
|
|
|
|
const tools = ref([
|
|
const tools = ref([
|
|
{},
|
|
{},
|
|
@@ -40,9 +40,10 @@ const tools = ref([
|
|
{},
|
|
{},
|
|
])
|
|
])
|
|
|
|
|
|
|
|
+const isDisabled = ref(true)
|
|
const step = ref(7)
|
|
const step = ref(7)
|
|
|
|
|
|
-const imgSrc = ref('/assets/image/test.jpg')
|
|
|
|
|
|
+const imgSrc = ref('')
|
|
const imgSrcLoading = ref(true)
|
|
const imgSrcLoading = ref(true)
|
|
let imgElement: HTMLImageElement | null = null
|
|
let imgElement: HTMLImageElement | null = null
|
|
const naturalWH = {
|
|
const naturalWH = {
|
|
@@ -66,96 +67,100 @@ const currentMark = ref<MarkItemType>({
|
|
color: '#000',
|
|
color: '#000',
|
|
})
|
|
})
|
|
|
|
|
|
-// opencv ==============start
|
|
|
|
-async function buildMatrix() {
|
|
|
|
- let srcPoints: any = [];
|
|
|
|
- let dstPoints: any = [];
|
|
|
|
-
|
|
|
|
- // 遍历点集收集源点和目标点
|
|
|
|
- // Object.values(_imgController.p).forEach(v => {
|
|
|
|
- // if (v.p !== undefined && v.t !== undefined) {
|
|
|
|
- // srcPoints.push([v.x, v.y]);
|
|
|
|
- // dstPoints.push([v.p, v.t]);
|
|
|
|
- // }
|
|
|
|
- // });
|
|
|
|
- PTXYMap.forEach(function (v: any, key) {
|
|
|
|
- if (v.P !== undefined && v.T !== undefined) {
|
|
|
|
- srcPoints.push([v.X, v.Y]);
|
|
|
|
- dstPoints.push([v.P, v.T]);
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- let matrix = null;
|
|
|
|
-
|
|
|
|
- if (srcPoints.length >= 4) {
|
|
|
|
- // 将点集转换为OpenCV矩阵格式
|
|
|
|
- const srcMat = pointsToMat(srcPoints);
|
|
|
|
- const dstMat = pointsToMat(dstPoints);
|
|
|
|
|
|
+const PTXYMap = new Map<string, { P: number, T: number, X: number, Y: number }>()
|
|
|
|
|
|
- // 计算单应性矩阵
|
|
|
|
- matrix = findHomography(srcMat, dstMat);
|
|
|
|
- console.log(matrix);
|
|
|
|
|
|
+// opencv ==============start
|
|
|
|
+function calculateInverseMatrix(Matrix: number[]) {
|
|
|
|
+ // 检查矩阵是否有效
|
|
|
|
+ if (Matrix.length !== 9) {
|
|
|
|
+ return []
|
|
|
|
+ }
|
|
|
|
|
|
- // 清理临时矩阵(OpenCV.js内存管理)
|
|
|
|
- srcMat.delete();
|
|
|
|
- dstMat.delete();
|
|
|
|
|
|
+ // 提取矩阵元素
|
|
|
|
+ const a = Matrix[0]
|
|
|
|
+ const b = Matrix[1]
|
|
|
|
+ const c = Matrix[2]
|
|
|
|
+ const d = Matrix[3]
|
|
|
|
+ const e = Matrix[4]
|
|
|
|
+ const f = Matrix[5]
|
|
|
|
+ const g = Matrix[6]
|
|
|
|
+ const h = Matrix[7]
|
|
|
|
+ const i = Matrix[8]
|
|
|
|
+
|
|
|
|
+ // 计算行列式
|
|
|
|
+ const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
|
|
|
|
+
|
|
|
|
+ // 检查矩阵是否可逆
|
|
|
|
+ if (det === 0) {
|
|
|
|
+ return []
|
|
}
|
|
}
|
|
|
|
|
|
- // 应用矩阵到所有点
|
|
|
|
- PTXYMap.forEach(function (v: any, key) {
|
|
|
|
- if (v.P !== undefined && v.T !== undefined) {
|
|
|
|
- matrix2Point(v, matrix);
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
|
|
+ // 计算行列式的倒数
|
|
|
|
+ const invDet = 1.0 / det
|
|
|
|
+
|
|
|
|
+ return [
|
|
|
|
+ (e * i - f * h) * invDet,
|
|
|
|
+ (c * h - b * i) * invDet,
|
|
|
|
+ (b * f - c * e) * invDet,
|
|
|
|
+ (f * g - d * i) * invDet,
|
|
|
|
+ (a * i - c * g) * invDet,
|
|
|
|
+ (c * d - a * f) * invDet,
|
|
|
|
+ (d * h - e * g) * invDet,
|
|
|
|
+ (b * g - a * h) * invDet,
|
|
|
|
+ (a * e - b * d) * invDet,
|
|
|
|
+ ]
|
|
}
|
|
}
|
|
|
|
|
|
-// 将点数组转换为OpenCV矩阵(CV_64FC2类型)
|
|
|
|
-function pointsToMat(points: any) {
|
|
|
|
- const data = new Float64Array(points.length * 2);
|
|
|
|
- points.forEach(([x, y]:[any, any], i: number) => {
|
|
|
|
- data[i * 2] = x;
|
|
|
|
- data[i * 2 + 1] = y;
|
|
|
|
- });
|
|
|
|
- return window.cv.matFromArray(points.length, 1, window.cv.CV_64FC2, data);
|
|
|
|
-}
|
|
|
|
|
|
+function WarpingPtByHomography(matrix: number[], X: number, Y: number) {
|
|
|
|
+ let x, y
|
|
|
|
+ x = matrix[0] * X + matrix[1] * Y + 1.0 * matrix[2]
|
|
|
|
+ y = matrix[3] * X + matrix[4] * Y + 1.0 * matrix[5]
|
|
|
|
+ const z = matrix[6] * X + matrix[7] * Y + 1.0 * matrix[8]
|
|
|
|
|
|
-// 计算单应性矩阵
|
|
|
|
-function findHomography(srcPoints:any, dstPoints:any) {
|
|
|
|
- const homography = new window.cv.Mat();
|
|
|
|
- // 使用基本参数版本:src, dst, method (0), ransacReprojThreshold (3)
|
|
|
|
- window.cv.findHomography(srcPoints, dstPoints, homography, 0, 3);
|
|
|
|
- return homography;
|
|
|
|
|
|
+ x /= z
|
|
|
|
+ y /= z
|
|
|
|
+ return { X: x, Y: y }
|
|
}
|
|
}
|
|
|
|
|
|
-// 应用矩阵变换到单个点
|
|
|
|
-function matrix2Point(v:any, matrix:any) {
|
|
|
|
- if (!matrix || matrix.empty()) return;
|
|
|
|
|
|
+async function buildMatrix() {
|
|
|
|
+ if (PTXYMap.size < 4) {
|
|
|
|
+ system.msg('映射矩阵最少四个')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
|
|
- // 创建输入矩阵(齐次坐标)
|
|
|
|
- const src = window.cv.matFromArray(3, 1, window.cv.CV_64FC1, [v.X, v.Y, 1]);
|
|
|
|
- const dst = new window.cv.Mat();
|
|
|
|
|
|
+ const PT: any[] = []; const XY: any[] = []
|
|
|
|
+ PTXYMap.forEach((v: any) => {
|
|
|
|
+ PT.push(v.P, v.T)
|
|
|
|
+ XY.push(v.X, v.Y)
|
|
|
|
+ })
|
|
|
|
+ // eslint-disable-next-line new-cap
|
|
|
|
+ const srcPointMat = new window.cv.matFromArray(4, 1, window.cv.CV_64FC2, XY)
|
|
|
|
+ // eslint-disable-next-line new-cap
|
|
|
|
+ const dtsPointMat = new window.cv.matFromArray(4, 1, window.cv.CV_64FC2, PT)
|
|
|
|
|
|
- // 执行矩阵变换
|
|
|
|
- window.cv.gemm(matrix, src, 1, null, 0, dst);
|
|
|
|
|
|
+ // 计算单应性矩阵
|
|
|
|
+ const outMatrix = window.cv.findHomography(srcPointMat, dtsPointMat)
|
|
|
|
+ window.outMatrix = outMatrix.data64F
|
|
|
|
|
|
- // 转换为笛卡尔坐标
|
|
|
|
- const w = dst.data64F[2];
|
|
|
|
- if (Math.abs(w) > Number.EPSILON) {
|
|
|
|
- v.p_transformed = dst.data64F[0] / w;
|
|
|
|
- v.t_transformed = dst.data64F[1] / w;
|
|
|
|
- }
|
|
|
|
|
|
+ // 清理临时矩阵(OpenCV.js内存管理)
|
|
|
|
+ srcPointMat.delete()
|
|
|
|
+ dtsPointMat.delete()
|
|
|
|
|
|
- // 清理临时矩阵
|
|
|
|
- src.delete();
|
|
|
|
- dst.delete();
|
|
|
|
|
|
+ // 应用矩阵到所有点
|
|
|
|
+ PTXYMap.forEach((v: any) => {
|
|
|
|
+ if (v.P !== undefined && v.T !== undefined) {
|
|
|
|
+ const out = WarpingPtByHomography(outMatrix.data64F, v.X, v.Y)
|
|
|
|
+ v.P = out.X
|
|
|
|
+ v.T = out.Y
|
|
|
|
+ }
|
|
|
|
+ })
|
|
}
|
|
}
|
|
|
|
|
|
// opencv =============end
|
|
// opencv =============end
|
|
|
|
|
|
-
|
|
|
|
function markClick(item: MarkItemType) {
|
|
function markClick(item: MarkItemType) {
|
|
- console.log(item);
|
|
|
|
-
|
|
|
|
|
|
+ if (!isDisabled.value)
|
|
|
|
+ return system.msg('正在装载,请稍后...')
|
|
currentMark.value = item
|
|
currentMark.value = item
|
|
colorReset(item.id)
|
|
colorReset(item.id)
|
|
}
|
|
}
|
|
@@ -185,6 +190,7 @@ function colorReset(id: string) {
|
|
function del(id: string) {
|
|
function del(id: string) {
|
|
if (!id)
|
|
if (!id)
|
|
return
|
|
return
|
|
|
|
+ PTXYMap.delete(id)
|
|
mark.value = mark.value.filter(v => v.id !== id)
|
|
mark.value = mark.value.filter(v => v.id !== id)
|
|
markClone.value = markClone.value.filter(v => v.id !== id)
|
|
markClone.value = markClone.value.filter(v => v.id !== id)
|
|
mark.value.length && (currentMark.value = mark.value[mark.value.length - 1])
|
|
mark.value.length && (currentMark.value = mark.value[mark.value.length - 1])
|
|
@@ -202,6 +208,7 @@ function clear() {
|
|
y: 0.0,
|
|
y: 0.0,
|
|
color: '#000',
|
|
color: '#000',
|
|
}
|
|
}
|
|
|
|
+ PTXYMap.clear()
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -244,13 +251,10 @@ function dbClick(e: MouseEvent) {
|
|
mark.value.push(markV)
|
|
mark.value.push(markV)
|
|
markClone.value.push(markV)
|
|
markClone.value.push(markV)
|
|
currentMark.value = mark.value[mark.value.length - 1]
|
|
currentMark.value = mark.value[mark.value.length - 1]
|
|
|
|
+ PTXYMap.set(currentMark.value.id, { P: 0.0, T: 0.0, X: currentMark.value.x, Y: currentMark.value.y })
|
|
markPointOnImageWithMargin(imgElement as HTMLImageElement)
|
|
markPointOnImageWithMargin(imgElement as HTMLImageElement)
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
-const PTXYMap = new Map<string, { P: number, T: number, X: number, Y: number }>()
|
|
|
|
-
|
|
|
|
-
|
|
|
|
function getPTXY(id: string, x: number, y: number) {
|
|
function getPTXY(id: string, x: number, y: number) {
|
|
if (!system.globalConfig.serverUrl) {
|
|
if (!system.globalConfig.serverUrl) {
|
|
system.msg('请先设置服务器地址')
|
|
system.msg('请先设置服务器地址')
|
|
@@ -264,28 +268,30 @@ function getPTXY(id: string, x: number, y: number) {
|
|
system.msg('请选择球机')
|
|
system.msg('请选择球机')
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- axios.get(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then(res => {
|
|
|
|
|
|
+ system.msg('正在装载,请稍后...')
|
|
|
|
+ isDisabled.value = false
|
|
|
|
+ axios.get(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then((res) => {
|
|
PTXYMap.set(currentMark.value.id, { P: res.data.data.P, T: res.data.data.T, X: x, Y: y })
|
|
PTXYMap.set(currentMark.value.id, { P: res.data.data.P, T: res.data.data.T, X: x, Y: y })
|
|
mark.value.forEach((v, i) => {
|
|
mark.value.forEach((v, i) => {
|
|
if (v.id === currentMark.value.id) {
|
|
if (v.id === currentMark.value.id) {
|
|
- const p = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, res.data.data.P, system.positiveConfig.P_Direction, "")
|
|
|
|
- const t = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, res.data.data.T, system.positiveConfig.T_Direction, "")
|
|
|
|
|
|
+ const p = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, res.data.data.P, system.positiveConfig.P_Direction, '')
|
|
|
|
+ const t = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, res.data.data.T, system.positiveConfig.T_Direction, '')
|
|
v.p = p
|
|
v.p = p
|
|
v.t = t
|
|
v.t = t
|
|
markClone.value[i].p = p
|
|
markClone.value[i].p = p
|
|
markClone.value[i].t = t
|
|
markClone.value[i].t = t
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
+ isDisabled.value = true
|
|
|
|
+ system.msg('装载完成')
|
|
})
|
|
})
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-const loadOld = () => {
|
|
|
|
- if (!system.ballCameraInfo) return
|
|
|
|
- console.log(system.ballCameraInfo);
|
|
|
|
|
|
+function loadOld() {
|
|
|
|
+ if (!system.ballCameraInfo)
|
|
|
|
+ return
|
|
PTXYMap.clear()
|
|
PTXYMap.clear()
|
|
const ps: any = system.ballCameraInfo.Matrix.PointSet
|
|
const ps: any = system.ballCameraInfo.Matrix.PointSet
|
|
- console.log(ps);
|
|
|
|
|
|
|
|
for (const key in ps) {
|
|
for (const key in ps) {
|
|
PTXYMap.set(key, {
|
|
PTXYMap.set(key, {
|
|
@@ -304,33 +310,96 @@ const loadOld = () => {
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (!mark.value.length)
|
|
|
|
+ return
|
|
markClone.value = JSON.parse(JSON.stringify(mark.value))
|
|
markClone.value = JSON.parse(JSON.stringify(mark.value))
|
|
colorReset(mark.value[mark.value.length - 1].id)
|
|
colorReset(mark.value[mark.value.length - 1].id)
|
|
currentMark.value = mark.value[mark.value.length - 1]
|
|
currentMark.value = mark.value[mark.value.length - 1]
|
|
markPointOnImageWithMargin(imgElement as HTMLImageElement)
|
|
markPointOnImageWithMargin(imgElement as HTMLImageElement)
|
|
}
|
|
}
|
|
|
|
|
|
-const jumpToPT = (p: number, t: number) => {
|
|
|
|
|
|
+function jumpToPT(p: number, t: number) {
|
|
if (!system.globalConfig.serverUrl) {
|
|
if (!system.globalConfig.serverUrl) {
|
|
system.msg('请先设置服务器地址')
|
|
system.msg('请先设置服务器地址')
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- const pV = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, p, system.positiveConfig.P_Direction, "inv")
|
|
|
|
- const tV = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, t, system.positiveConfig.T_Direction, "inv")
|
|
|
|
- axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzSet?CameraId=${currentBallCamera.value}&Action=1`, { P: pV, T: tV, Z: 0 }).then(res => {
|
|
|
|
- console.log(res);
|
|
|
|
|
|
+ const pV = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, p, system.positiveConfig.P_Direction, 'inv')
|
|
|
|
+ const tV = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, t, system.positiveConfig.T_Direction, 'inv')
|
|
|
|
+ axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzSet?CameraId=${system.currentBallCamera}&Action=1`, { P: pV, T: tV, Z: 0 }).then((res) => {
|
|
|
|
+ console.log(res)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+function ptByMatrix() {
|
|
|
|
+ if (!system.globalConfig.serverUrl) {
|
|
|
|
+ system.msg('请先设置服务器地址')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ const out: any = WarpingPtByHomography(window.outMatrix, currentMark.value.x, currentMark.value.y)
|
|
|
|
+ PTXYMap.set(currentMark.value.id, { P: out.P, T: out.T, X: currentMark.value.x, Y: currentMark.value.y })
|
|
|
|
+ mark.value.forEach((v, i) => {
|
|
|
|
+ if (v.id === currentMark.value.id) {
|
|
|
|
+ v.p = out.P
|
|
|
|
+ v.t = out.T
|
|
|
|
+ markClone.value[i].p = out.P
|
|
|
|
+ markClone.value[i].t = out.T
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
|
|
|
|
+function updateMatrix(callback?: () => void) {
|
|
|
|
+ if (!system.globalConfig.serverUrl) {
|
|
|
|
+ system.msg('请先设置服务器地址')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!system.currentBallCamera) {
|
|
|
|
+ system.msg('请选择球机')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ const pointSet: any = {}
|
|
|
|
+ PTXYMap.forEach((v, k) => {
|
|
|
|
+ pointSet[k] = {
|
|
|
|
+ P: v.P,
|
|
|
|
+ T: v.T,
|
|
|
|
+ X: v.X,
|
|
|
|
+ Y: v.Y,
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ const data = {
|
|
|
|
+ P_Start: system.positiveConfig.P_Start,
|
|
|
|
+ P_Max: system.positiveConfig.P_Max,
|
|
|
|
+ p_Positive_Direction: system.positiveConfig.P_Direction,
|
|
|
|
+ T_Start: system.positiveConfig.T_Start,
|
|
|
|
+ T_Max: system.positiveConfig.T_Max,
|
|
|
|
+ T_Positive_Direction: system.positiveConfig.T_Direction,
|
|
|
|
+ Matrix: window.outMatrix || system.ballCameraInfo?.Matrix.Matrix || [],
|
|
|
|
+ InvMatrix: system.ballCameraInfo?.Matrix.InvMatrix || [],
|
|
|
|
+ PointSet: JSON.stringify(pointSet) === '{}' ? system.ballCameraInfo?.Matrix.PointSet : pointSet,
|
|
|
|
+ }
|
|
|
|
+ data.Matrix = Array.isArray(data.Matrix) ? data.Matrix : Array.from(data.Matrix)
|
|
|
|
+ axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Matrix?CameraId=${system.currentBallCamera}`, data).then(() => {
|
|
|
|
+ callback && callback()
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
|
|
-let viewer: Viewer
|
|
|
|
-
|
|
|
|
-watch(() => system.drawer, () => {
|
|
|
|
- console.log(viewer)
|
|
|
|
-})
|
|
|
|
|
|
+function saveBtn() {
|
|
|
|
+ if (!window.outMatrix) {
|
|
|
|
+ system.msg('请先生成映射矩阵')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ const invMatrix = calculateInverseMatrix(window.outMatrix)
|
|
|
|
+ system.ballCameraInfo!.Matrix.InvMatrix = invMatrix
|
|
|
|
+ updateMatrix(() => {
|
|
|
|
+ system.msg('保存成功')
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
|
|
|
|
+let viewer: Viewer | null = null
|
|
function initViewer() {
|
|
function initViewer() {
|
|
|
|
+ if (viewer) {
|
|
|
|
+ viewer.destroy()
|
|
|
|
+ viewer = null
|
|
|
|
+ }
|
|
const updloadImg = document.getElementById('uploadImage') as HTMLImageElement
|
|
const updloadImg = document.getElementById('uploadImage') as HTMLImageElement
|
|
viewer = new Viewer(updloadImg, {
|
|
viewer = new Viewer(updloadImg, {
|
|
inline: true,
|
|
inline: true,
|
|
@@ -367,42 +436,68 @@ function initViewer() {
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-onMounted(() => {
|
|
|
|
- initViewer()
|
|
|
|
|
|
+watch(() => system.globalConfig.imgUrl, (file) => {
|
|
|
|
+ const url = file && URL.createObjectURL(file)
|
|
|
|
+ if (url) {
|
|
|
|
+ imgSrc.value = url
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ initViewer()
|
|
|
|
+ })
|
|
|
|
+ }
|
|
})
|
|
})
|
|
|
|
+
|
|
|
|
+watch(() => system.positiveConfig, () => {
|
|
|
|
+ updateMatrix()
|
|
|
|
+}, { deep: true })
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<template>
|
|
<template>
|
|
<v-row>
|
|
<v-row>
|
|
<v-col class="relative" cols="12" md="6">
|
|
<v-col class="relative" cols="12" md="6">
|
|
<div class="flex-1">
|
|
<div class="flex-1">
|
|
- <v-select v-if="system.type?.Baseline === 'BoGuan'" v-model="system.currentDevice" :items="system.deviceList"
|
|
|
|
- item-title="name" item-value="id" label="选择设备" @update:model-value="system.getBallCameraList" />
|
|
|
|
- <v-select v-model="currentBallCamera" :items="system.ballCameraList" label="球机列表" item-title="name"
|
|
|
|
- item-value="id" @update:model-value="system.getBallCameraInfo">
|
|
|
|
|
|
+ <v-select
|
|
|
|
+ v-if="system.type?.Baseline === 'BoGuan'" v-model="system.currentDevice" :items="system.deviceList"
|
|
|
|
+ item-title="name" item-value="id" label="选择设备" @update:model-value="system.getBallCameraList"
|
|
|
|
+ />
|
|
|
|
+ <v-select
|
|
|
|
+ v-model="system.currentBallCamera" :items="system.ballCameraList" label="球机列表" item-title="name"
|
|
|
|
+ item-value="id" @update:model-value="system.getBallCameraInfo"
|
|
|
|
+ >
|
|
<template #append>
|
|
<template #append>
|
|
- <add-ball v-model:dialog="addDialogV">
|
|
|
|
|
|
+ <add-ball v-model:dialog="addDialogV" flag="add">
|
|
|
|
+ <template #activator="{ props: activatorProps }">
|
|
|
|
+ <v-btn
|
|
|
|
+ v-tooltip:top="'添加球机'" size="large" rounded="0" variant="flat" icon="mdi-plus-thick"
|
|
|
|
+ v-bind="activatorProps"
|
|
|
|
+ />
|
|
|
|
+ </template>
|
|
|
|
+ </add-ball>
|
|
|
|
+ <add-ball v-model:dialog="updDialogV" flag="edit" :camera-id="system.currentBallCamera">
|
|
<template #activator="{ props: activatorProps }">
|
|
<template #activator="{ props: activatorProps }">
|
|
- <v-btn v-tooltip:top="'添加球机'" size="large" rounded="0" variant="flat" icon="mdi-plus-thick"
|
|
|
|
- v-bind="activatorProps" />
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ v-tooltip:top="'修改球机'" size="large" rounded="0" variant="flat" icon="mdi-pencil"
|
|
|
|
+ v-bind="activatorProps"
|
|
|
|
+ />
|
|
</template>
|
|
</template>
|
|
</add-ball>
|
|
</add-ball>
|
|
<v-dialog max-width="400">
|
|
<v-dialog max-width="400">
|
|
<template #activator="{ props: activatorProps }">
|
|
<template #activator="{ props: activatorProps }">
|
|
- <v-btn v-tooltip:top="'删除球机'" size="large" rounded="0" variant="flat" icon="mdi-trash-can"
|
|
|
|
- v-bind="activatorProps" />
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ v-tooltip:top="'删除球机'" size="large" rounded="0" variant="flat" icon="mdi-trash-can"
|
|
|
|
+ v-bind="activatorProps"
|
|
|
|
+ />
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<template #default="{ isActive }">
|
|
<template #default="{ isActive }">
|
|
<v-card title="警告" color="#f87171">
|
|
<v-card title="警告" color="#f87171">
|
|
<v-card-text>
|
|
<v-card-text>
|
|
- 确定删除吗?确定删除吗?
|
|
|
|
|
|
+ 确定删除当前球机吗?
|
|
</v-card-text>
|
|
</v-card-text>
|
|
|
|
|
|
<v-card-actions>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-spacer />
|
|
|
|
|
|
- <v-btn text="确定" border @click="isActive.value = false" />
|
|
|
|
|
|
+ <v-btn text="确定" border @click="() => { system.delBallCamera(system.currentBallCamera);isActive.value = false }" />
|
|
</v-card-actions>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-card>
|
|
</template>
|
|
</template>
|
|
@@ -412,62 +507,80 @@ onMounted(() => {
|
|
<v-row>
|
|
<v-row>
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-number-input v-model="system.positiveConfig.P_Start" control-variant="stacked" :precision="1"
|
|
|
|
- hide-details="auto" label="P 起始值" />
|
|
|
|
|
|
+ <v-number-input
|
|
|
|
+ v-model="system.positiveConfig.P_Start" control-variant="stacked" :precision="1"
|
|
|
|
+ hide-details="auto" label="P 起始值"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
|
|
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-number-input v-model="system.positiveConfig.T_Start" control-variant="stacked" :precision="1"
|
|
|
|
- hide-details="auto" label="T 起始值" />
|
|
|
|
|
|
+ <v-number-input
|
|
|
|
+ v-model="system.positiveConfig.T_Start" control-variant="stacked" :precision="1"
|
|
|
|
+ hide-details="auto" label="T 起始值"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
</v-row>
|
|
</v-row>
|
|
<v-row>
|
|
<v-row>
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-number-input v-model="system.positiveConfig.P_Max" control-variant="stacked" :precision="1"
|
|
|
|
- hide-details="auto" label="P 最大值" />
|
|
|
|
|
|
+ <v-number-input
|
|
|
|
+ v-model="system.positiveConfig.P_Max" control-variant="stacked" :precision="1"
|
|
|
|
+ hide-details="auto" label="P 最大值"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
|
|
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-number-input v-model="system.positiveConfig.T_Max" control-variant="stacked" :precision="1"
|
|
|
|
- hide-details="auto" label="T 最大值" />
|
|
|
|
|
|
+ <v-number-input
|
|
|
|
+ v-model="system.positiveConfig.T_Max" control-variant="stacked" :precision="1"
|
|
|
|
+ hide-details="auto" label="T 最大值"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
</v-row>
|
|
</v-row>
|
|
<v-row>
|
|
<v-row>
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-select v-model="system.positiveConfig.P_Direction" :items="positiveDirectionPOptions"
|
|
|
|
- item-title="label" item-value="value" label="P 正方向" />
|
|
|
|
|
|
+ <v-select
|
|
|
|
+ v-model="system.positiveConfig.P_Direction" :items="positiveDirectionPOptions"
|
|
|
|
+ item-title="label" item-value="value" label="P 正方向"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
<v-col>
|
|
<v-col>
|
|
<v-sheet>
|
|
<v-sheet>
|
|
- <v-select v-model="system.positiveConfig.T_Direction" :items="positiveDirectionTOptions"
|
|
|
|
- item-title="label" item-value="value" label="T 正方向" />
|
|
|
|
|
|
+ <v-select
|
|
|
|
+ v-model="system.positiveConfig.T_Direction" :items="positiveDirectionTOptions"
|
|
|
|
+ item-title="label" item-value="value" label="T 正方向"
|
|
|
|
+ />
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</v-col>
|
|
</v-col>
|
|
</v-row>
|
|
</v-row>
|
|
|
|
|
|
<!-- 视频播放 -->
|
|
<!-- 视频播放 -->
|
|
<div class="p-1">
|
|
<div class="p-1">
|
|
- <v-card :loading="system.canvasVideoLoading" title="画面预览"
|
|
|
|
- :subtitle="`当前P ${system.PT.p},当前T ${system.PT.t},当前标定点位的个数0`" text="">
|
|
|
|
|
|
+ <v-card
|
|
|
|
+ :loading="system.canvasVideoLoading" title="画面预览"
|
|
|
|
+ :subtitle="`当前P ${system.PT.p},当前T ${system.PT.t},当前标定点位的个数0`" text=""
|
|
|
|
+ >
|
|
<div class="relative aspect-ratio-video b-1 b-#ccc b-solid bg-black">
|
|
<div class="relative aspect-ratio-video b-1 b-#ccc b-solid bg-black">
|
|
- <v-icon icon="mdi-plus" color="#ef4444" class="absolute top-50% left-50% transform-translate--50%" :style="{
|
|
|
|
- textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
|
|
|
|
|
|
+ <v-icon
|
|
|
|
+ icon="mdi-plus" color="#ef4444" class="absolute left-50% top-50% transform-translate--50%" :style="{
|
|
|
|
+ textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
|
|
-1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
|
|
-1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
|
|
- }" />
|
|
|
|
|
|
+ }"
|
|
|
|
+ />
|
|
<canvas v-if="system.resetDom" class="canvas-video h-full w-full" />
|
|
<canvas v-if="system.resetDom" class="canvas-video h-full w-full" />
|
|
</div>
|
|
</div>
|
|
<v-card-actions>
|
|
<v-card-actions>
|
|
- <v-btn :disabled="system.canvasVideoLoading" block variant="tonal"
|
|
|
|
- @click="system.playBallCamera(currentBallCamera)">
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ :disabled="system.canvasVideoLoading" block variant="tonal"
|
|
|
|
+ @click="system.playBallCamera(system.currentBallCamera)"
|
|
|
|
+ >
|
|
播放
|
|
播放
|
|
</v-btn>
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card-actions>
|
|
@@ -477,9 +590,11 @@ onMounted(() => {
|
|
<div class="grid grid-cols-3 grid-rows-3">
|
|
<div class="grid grid-cols-3 grid-rows-3">
|
|
<div v-for="tool in tools" :key="tool.type">
|
|
<div v-for="tool in tools" :key="tool.type">
|
|
<div v-if="!tool.type" class="invisible" />
|
|
<div v-if="!tool.type" class="invisible" />
|
|
- <v-btn v-else width="60" height="40" variant="tonal" color="blue-darken-2"
|
|
|
|
- @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: tool.value })"
|
|
|
|
- @mouseup="system.ballStop(currentBallCamera, { Direction: tool.value })">
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ v-else width="60" height="40" variant="tonal" color="blue-darken-2"
|
|
|
|
+ @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: tool.value })"
|
|
|
|
+ @mouseup="system.ballStop(system.currentBallCamera, { Direction: tool.value })"
|
|
|
|
+ >
|
|
{{ tool.icon }}
|
|
{{ tool.icon }}
|
|
</v-btn>
|
|
</v-btn>
|
|
</div>
|
|
</div>
|
|
@@ -487,14 +602,18 @@ onMounted(() => {
|
|
<v-divider class="mx-2" vertical opacity="8" />
|
|
<v-divider class="mx-2" vertical opacity="8" />
|
|
<div class="flex-1">
|
|
<div class="flex-1">
|
|
<div class="flex justify-around">
|
|
<div class="flex justify-around">
|
|
- <v-btn variant="tonal" color="blue-darken-2"
|
|
|
|
- @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: 9 })"
|
|
|
|
- @mouseup="system.ballStop(currentBallCamera, { Direction: 9 })">
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ variant="tonal" color="blue-darken-2"
|
|
|
|
+ @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: 9 })"
|
|
|
|
+ @mouseup="system.ballStop(system.currentBallCamera, { Direction: 9 })"
|
|
|
|
+ >
|
|
🔍+
|
|
🔍+
|
|
</v-btn>
|
|
</v-btn>
|
|
- <v-btn variant="tonal" color="blue-darken-2"
|
|
|
|
- @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: 10 })"
|
|
|
|
- @mouseup="system.ballStop(currentBallCamera, { Direction: 10 })">
|
|
|
|
|
|
+ <v-btn
|
|
|
|
+ variant="tonal" color="blue-darken-2"
|
|
|
|
+ @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: 10 })"
|
|
|
|
+ @mouseup="system.ballStop(system.currentBallCamera, { Direction: 10 })"
|
|
|
|
+ >
|
|
🔎-
|
|
🔎-
|
|
</v-btn>
|
|
</v-btn>
|
|
</div>
|
|
</div>
|
|
@@ -520,13 +639,15 @@ onMounted(() => {
|
|
<v-skeleton-loader v-if="imgSrcLoading" height="100%" type="image" />
|
|
<v-skeleton-loader v-if="imgSrcLoading" height="100%" type="image" />
|
|
<img id="uploadImage" class="w-full" :src="imgSrc" alt="图片预览" @load="imgSrcLoad">
|
|
<img id="uploadImage" class="w-full" :src="imgSrc" alt="图片预览" @load="imgSrcLoad">
|
|
<v-empty-state v-if="!imgSrcLoading && !imgSrc" icon="mdi-image-broken-variant" title="暂无上传图片" />
|
|
<v-empty-state v-if="!imgSrcLoading && !imgSrc" icon="mdi-image-broken-variant" title="暂无上传图片" />
|
|
- <v-icon v-for="(item, index) in mark" :key="index" :style="{
|
|
|
|
- top: `${item.y}px`,
|
|
|
|
- left: `${item.x}px`,
|
|
|
|
- textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
|
|
|
|
|
|
+ <v-icon
|
|
|
|
+ v-for="(item, index) in mark" :key="index" :style="{
|
|
|
|
+ top: `${item.y}px`,
|
|
|
|
+ left: `${item.x}px`,
|
|
|
|
+ textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
|
|
-1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
|
|
-1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
|
|
- }" :color="item.color" icon="mdi-plus-thick" class="absolute bottom-0" variant="tonal"
|
|
|
|
- @click="markClick(item)" />
|
|
|
|
|
|
+ }" :color="item.color" icon="mdi-plus-thick" class="absolute bottom-0" variant="tonal"
|
|
|
|
+ @click="markClick(item)"
|
|
|
|
+ />
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col items-center">
|
|
<div class="flex flex-col items-center">
|
|
<v-chip class="ma-2" color="blue" label>
|
|
<v-chip class="ma-2" color="blue" label>
|
|
@@ -537,19 +658,19 @@ onMounted(() => {
|
|
<v-card-text>
|
|
<v-card-text>
|
|
<div class="flex flex-col items-center gap-2">
|
|
<div class="flex flex-col items-center gap-2">
|
|
<div>
|
|
<div>
|
|
- <v-btn color="blue">
|
|
|
|
|
|
+ <v-btn color="blue" @click="saveBtn">
|
|
保存
|
|
保存
|
|
</v-btn>
|
|
</v-btn>
|
|
</div>
|
|
</div>
|
|
<v-row justify="center">
|
|
<v-row justify="center">
|
|
<v-col>
|
|
<v-col>
|
|
- <v-btn width="100%" @click="getPTXY(currentBallCamera, currentMark.x, currentMark.y)">
|
|
|
|
|
|
+ <v-btn width="100%" @click="getPTXY(system.currentBallCamera, currentMark.x, currentMark.y)">
|
|
当前PT位置装载PT
|
|
当前PT位置装载PT
|
|
</v-btn>
|
|
</v-btn>
|
|
</v-col>
|
|
</v-col>
|
|
|
|
|
|
<v-col>
|
|
<v-col>
|
|
- <v-btn width="100%">
|
|
|
|
|
|
+ <v-btn width="100%" @click="ptByMatrix">
|
|
根据映射矩阵装载PT
|
|
根据映射矩阵装载PT
|
|
</v-btn>
|
|
</v-btn>
|
|
</v-col>
|
|
</v-col>
|