Browse Source

feat: 识别框绘制
1. 支持在识别图像内绘制识别框
2. 支持手动开关识别框的绘制
3. 将人脸的第一张图显示为大图, 第二张图显示为小图

kindring 1 year ago
parent
commit
356b0a9d6e

+ 142 - 0
web_src/src/components/canvasView.vue

@@ -0,0 +1,142 @@
+<script>
+// 使用canvas绘制图片
+// 顺编在图片上绘制矩形框
+export default {
+  name: "canvasView",
+  props: {
+    imageUrl: "",
+    aiItems: [],
+    drawRect: {
+      type: Boolean,
+      default: false
+    }
+  },
+  beforeMount() {
+    // 获取父元素宽高
+  },
+  mounted() {
+    this.canvas = this.$refs.canvas;
+    this.getParentWH();
+    this.loadImage();
+  },
+  watch: {
+    imageUrl() {
+      this.loadImage();
+    },
+    drawRect() {
+      if (this.img) {
+        this.imgLoaded(this.img)
+      }
+    }
+  },
+  data() {
+    return {
+      canvas: null,
+      ctx: null,
+      parentWidth: 0,
+      parentHeight: 0,
+      img: null,
+    }
+  },
+  methods: {
+    getParentWH() {
+      // 获取父元素的宽高, 用于限定图片显示区域
+      console.log('test')
+      let el = this.canvas;
+      console.log(el)
+      let parentEl = el.parentElement;
+      console.log(parentEl)
+      this.parentWidth = parentEl.clientWidth;
+      this.parentHeight = parentEl.clientHeight;
+      console.log(`width: ${this.parentWidth} height: ${this.parentHeight}`)
+    },
+    loadImage() {
+      let imageUrl = this.imageUrl;
+      let img = new Image();
+      img.onload = () => {
+        this.imgLoaded(img)
+      };
+      img.src = imageUrl;
+    },
+    imgLoaded(img) {
+      this.img = img;
+      this.ctx = this.canvas.getContext("2d");
+      let width = img.width;
+      let height = img.height;
+      console.log(`img width: ${width} height: ${height}`)
+      // 1 宽度超出父元素宽度 缩放父元素
+      let scale_w = this.parentWidth / width;
+      let scale_h = this.parentHeight / height;
+      // 获取缩放位置
+      let scale = Math.min(scale_w, scale_h);
+      console.log(`scale_w: ${scale_w} scale_h: ${scale_h}`)
+      // 尝试填满父元素宽度
+      if (scale_w * height < this.parentHeight) {
+        this.canvas.width = this.parentWidth;
+        this.canvas.height = scale_w * height;
+        scale = scale_w;
+      } else if (scale_h * width < this.parentWidth) {
+        this.canvas.width = scale_h * width;
+        this.canvas.height = this.parentHeight;
+        scale = scale_h;
+      }
+      // 缩放图片并绘制
+      this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
+      this.scale = scale;
+      if (this.drawRect) {
+        this.drawAiRect(scale);
+      }
+    },
+    drawAiRect(scale) {
+      let aiItems = this.aiItems;
+      console.log(aiItems)
+      // "alarmId": 8634,
+      //     "arithmetic": "1",
+      //     "info": "火焰",
+      //     "itemId": 8539,
+      //     "similarity": "0",
+      //     "trait": "fire",
+      //     "x1": "1051.0",
+      //     "x2": "1311.0",
+      //     "y1": "763.0",
+      //     "y2": "1114.0"
+      aiItems.forEach(item => {
+        console.log(item)
+        // 绘制矩形框, 将坐标转换成canvas坐标
+
+        let x = item.x1;
+        let y = item.y1;
+        x = x * scale;
+        y = y * scale;
+        let w = item.x2 * scale - x;
+        let h = item.y2 * scale - y;
+        this.ctx.strokeStyle = "red";
+        this.ctx.strokeRect(x, y, w, h);
+        // 尝试在上方绘制描述
+        let info_x = x;
+        let info_y = y;
+        // 绘制半透明矩形框
+        // this.ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
+        // this.ctx.fillRect(info_x, info_y, 80, 60);
+        // 绘制文字
+        this.ctx.fillStyle = "red";
+        this.ctx.font = "20px Arial";
+        let infoText = `${item.info}-${item.trait}(${item.similarity})`
+        this.ctx.fillText(infoText, info_x, info_y + 20);
+      })
+    }
+
+  },
+
+
+}
+</script>
+
+<template>
+  <canvas ref="canvas">
+  </canvas>
+</template>
+
+<style scoped>
+
+</style>

+ 65 - 11
web_src/src/components/mediaView.vue

@@ -15,12 +15,25 @@
     <div class="tab-box" style="width: 100%;">
       <div class="viewBox">
         <div class="view">
-          <img v-if="mediaImages[mediaInd]" :src="'/aiLib/'+mediaImages[mediaInd].url" alt="">
+          <canvas-view
+              :image-url="'/aiLib/'+mediaImages[mediaInd].url"
+              :ai-items="rowData.items"
+              :draw-rect="mediaInd === 0 && isDraw"
+          ></canvas-view>
+          <!--          绘制识别框-->
+
+          <!--          下载按钮 -->
           <div v-if="mediaImages[mediaInd]" class="toolBox">
             <a :href="'/aiLib/'+mediaImages[mediaInd].url">
               <el-button icon="el-icon-download" circle/>
             </a>
-
+          </div>
+          <div
+              v-if="mediaImages[mediaInd] && mediaInd === 0"
+              :class="`toolBox draw ${isDraw?'':'unDraw'}`"
+              @click="isDraw = !isDraw"
+          >
+            <svg-icon icon-class="draw"/>
           </div>
 
         </div>
@@ -75,17 +88,19 @@
 import devTool from "@/hfyUntil/devTool";
 import handle from "@/until/handle";
 import timeUntil from "@/until/time";
+import CanvasView from "@components/canvasView.vue";
 
 export default {
   name: "mediaView",
-  data(){
+  components: {CanvasView},
+  data() {
     return {
       isLoading: false,
       alarmId: "",
       rowData: {},
-      mediaImages: [
-      ],
+      mediaImages: [],
       mediaInd: 0,
+      isDraw: false
     }
   },
   mounted() {
@@ -177,16 +192,40 @@ export default {
   padding-left: 4px;
   padding-right: 5px;
 }
-.viewBox .view{
+
+.viewBox .view {
   height: calc(100% - 150px);
   position: relative;
 }
-.viewBox .view .toolBox{
+
+.viewBox .view .toolBox {
   position: absolute;
   bottom: 0;
   right: 0;
 }
-.viewBox .viewController{
+
+.viewBox .view .draw {
+  width: 35px;
+  height: 35px;
+  background-color: #fff;
+  color: #007CFF;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  position: absolute;
+  top: 5px;
+  left: 5px;
+  font-size: 1.5rem;
+}
+
+.viewBox .view .unDraw {
+  background-color: #9d9d9d;
+  color: #fff;
+}
+
+
+.viewBox .viewController {
   height: 150px;
   display: flex;
   flex-wrap: wrap;
@@ -194,7 +233,7 @@ export default {
   border-top: 1px solid lightgray;
 }
 
-.viewBox .viewController .mediaImage{
+.viewBox .viewController .mediaImage {
   width: 140px;
   height: 140px;
   border-radius: 0.125rem;
@@ -232,14 +271,29 @@ export default {
   align-items: center;
   box-sizing: border-box;
 }
-.infoRow .value{
+
+.infoRow .value {
   box-sizing: border-box;
   padding: 0 5px;
 }
-.infoRow .label{
+
+.infoRow .label {
   width: 80px;
   border-right: 1px solid lightgray;
 }
 
+.img-box {
+  position: relative;
+  width: auto;
+  height: auto;
+}
 
+.recl {
+  position: absolute;
+  top: 20px;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  background-color: rgba(0, 0, 0, 0.5);
+}
 </style>

+ 15 - 14
web_src/src/hfyUntil/devTool.js

@@ -26,21 +26,22 @@ function signalTransform(signalText,isNum = true){
 }
 
 function parseMediaPath(rawStr){
-  let fileNameList = [];
-  // 解析出前缀以及后缀
-  let baseUrl = rawStr.substring(0,rawStr.lastIndexOf("/"));
-  let fileNames = rawStr.substring(rawStr.lastIndexOf("/")+1);
-  let temp = [];
-  let split = "|";  // 指定分割字符
-  temp = fileNames.split(split); // 分割字符串
-  // 普通 for 循环
-  if(temp.length <= 1){
-    fileNameList.push(rawStr);
-  }else{
-    for (let i = 0;i<temp.length;i++){
-      fileNameList.push(baseUrl+"/"+temp[i]);
+    let fileNameList = [];
+    // 解析出前缀以及后缀
+    let baseUrl = rawStr.substring(0, rawStr.lastIndexOf("/"));
+    let fileNames = rawStr.substring(rawStr.lastIndexOf("/") + 1);
+    let temp = [];
+    let split = "|";  // 指定分割字符
+    temp = fileNames.split(split); // 分割字符串
+    // 普通 for 循环
+    if (temp.length <= 1) {
+        fileNameList.push(rawStr);
+    } else {
+        for (let i = temp.length - 1; i >= 0; i--) {
+            console.log(temp[i])
+            fileNameList.push(baseUrl + "/" + temp[i]);
+        }
     }
-  }
   return fileNameList;
 }
 

+ 7 - 0
web_src/src/icons/svg/draw.svg

@@ -0,0 +1,7 @@
+<svg t="1721122001533" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6123"
+     width="200" height="200">
+    <path d="M820.5 322.4c-10.1-10.1-23.6-15.7-37.9-15.7-14.3 0-27.8 5.6-37.9 15.7L402.2 664.9C391 676.1 384 691 382.7 706.8l-10.8 128.1c-1.4 16.2 4.4 32.1 15.9 43.6 10.4 10.4 24.4 16.1 38.9 16.1 1.5 0 3.1-0.1 4.7-0.2l128.2-10.8c15.8-1.3 30.7-8.2 41.9-19.4L944 521.7c20.9-20.9 20.9-54.9 0-75.8L820.5 322.4zM548.3 804.3l-94.2 7.9 7.9-94.1 215.8-215.8 86.2 86.2-215.7 215.8zM820.6 532l-86.2-86.2 48.2-48.2 86.2 86.2-48.2 48.2z"
+          p-id="6124"></path>
+    <path d="M313.2 851.4H192.4c-5.2-7.7-11.8-14.3-19.5-19.4V193c7.8-5.2 14.6-11.8 19.8-19.6h640.9c4.8 6.8 10.7 12.8 17.5 17.6v103.9c0 22.1 17.9 40 40 40s40-17.9 40-40v-104c18.1-12.7 30-33.6 30-57.4 0-38.7-31.3-70-70-70-23.7 0-44.7 11.8-57.3 29.9H191.1c-12.7-17.5-33.3-28.9-56.6-28.9-38.7 0-70 31.3-70 70 0 23.1 11.2 43.5 28.4 56.3v643.1c-17.4 12.7-28.6 33.3-28.6 56.5 0 38.7 31.3 70 70 70 23.3 0 44-11.4 56.7-29h122.2c22.1 0 40-17.9 40-40s-17.9-40-40-40z"
+          p-id="6125"></path>
+</svg>

+ 1 - 1
web_src/src/pages/index/main.js

@@ -18,7 +18,7 @@ import Fingerprint2 from 'fingerprintjs2';
 import VueClipboards from 'vue-clipboards';
 import Contextmenu from "vue-contextmenujs"
 import userService from "@/components/service/UserService"
-
+import '@/icons/registerSvg';
 
 // 生成唯一ID
 Fingerprint2.get(function (components) {