利用canvas 实现图片的标注,把标注像素点传入到后端

news2024/9/22 21:26:47

背景:我们有一个摄像的产品,拍照传统的水表盘面,我们需要框选水表读数,标注点传到后端,后端根据标注点自动去截取摄像表拍摄回来的图片,然后拿到大模型里面进行训练。由于同一只表拍摄的画面都是一样的,所以按此方法减少了人工标注的繁琐工作

可关注,参考另外一篇文章:利用fabricjs 实现图片的标注,把标注像素点传入到后端

解锁前端难题:亲手实现一个图片标注工具

《T 恤图案编辑器》
《T 恤图案编辑器》-源码
实现一个轻量 fabric.js 系列一(摸透 canvas)

遗留问题:
1、矩形框旋转后,鼠标悬浮在缩放标注点的位置上,鼠标的样式无法旋转角度
2、矩形框旋转后,拖动缩放的标准变了
备注:经测试,不管怎么变化,传入到后端的像素点是对的

一、效果图

请添加图片描述

二、问题分解

三、源代码

<template>
  <div
    :style="{
      width: canvasProp.width + 'px',
      height: canvasProp.height + 'px',
      border: '1px solid #ccc'
    }"
  >
    <canvas
      ref="canvas"
      :width="canvasProp.width"
      :height="canvasProp.height"
      @mousedown="onMouseDown"
      @mousemove="onMouseMove"
      @mouseup="onMouseUp"
      :style="{
        width: canvasProp.width + 'px',
        height: canvasProp.height + 'px'
      }"
    ></canvas>
    <div @click="saveData">保存数据</div>
    <div @click="zoomBig">放大</div>
    <div @click="zoomSmall">缩小</div>
  </div>
</template>
  
  <script>
export default {
  name: "images-tags",
  props: {
    // 矩形标注的数据
    tagsData: {
      type: Array,
      default: () => {
        return [
          {
            label: "基表数据",
            color: "#0000ff",
            type: "rectangle",
            width: 150,
            height: 50,
            rotate: 0,
            isInit: true,
            startX: 185,
            startY: 235
          }
        ];
      }
    },
    // 图片路径
    images: {
      type: String,
      default: "/img/yejing1.jpg"
    }
  },
  data() {
    return {
      ctx: null,
      cursorClass: "",
      initCenterX: 0,
      initCenterY: 0,
      rotateImages: null, //旋转图标是否加载
      bgImage: null, //背景图是否加载
      canvasProp: {
        width: 0, // canvas的宽度
        height: 0, // canvas的高度
        scale: 1, // canvas的缩放比例
        scaleX: 0,
        scaleY: 0,
        translateX: 0,
        translateY: 0
      },
      selectedTag: null, // 当前选中的矩形框
      isResizing: false,
      isDragging: false,
      isRotating: false,
      resizeHandle: null,
      dragOffsetX: 0,
      dragOffsetY: 0,
      mouseDownX: 0,
      mouseDownY: 0,
      initialRotation: 0,
      isCanvasDraging: false
    };
  },
  mounted() {
    this.loadImageAndSetCanvas();
    window.addEventListener("keydown", this.handleKeyDown);
    window.addEventListener("wheel", this.onWheel, { passive: false });

    console.log("保存的数据===", this.tagsData);
  },
  beforeDestroy() {
    window.removeEventListener("keydown", this.handleKeyDown);
    window.removeEventListener("wheel", this.onWheel);
  },
  methods: {
    zoomBig() {
      this.zoom(true, this.initCenterX, this.initCenterY);
    },
    zoomSmall() {
      this.zoom(false, this.initCenterX, this.initCenterY);
    },
    onWheel(event) {
      if (event.ctrlKey) {
        // detect pinch
        event.preventDefault(); // prevent zoom
        this.zoom(event.deltaY < 0, event.offsetX, event.offsetY);
      }
    },
    zoom(iszoomBig, zoomCenterX, zoomCenterY) {
      if (iszoomBig) {
        console.log("Pinching 放大");
        if (this.canvasProp.scale < 3) {
          this.canvasProp.scaleX = zoomCenterX;
          this.canvasProp.scaleY = zoomCenterY;
          this.canvasProp.scale = Math.min(this.canvasProp.scale + 0.1, 3);
        }
        this.drawTags();
      } else {
        if (this.canvasProp.scale > 1) {
          this.canvasProp.scaleX = zoomCenterX;
          this.canvasProp.scaleY = zoomCenterY;
          this.canvasProp.scale = Math.max(this.canvasProp.scale - 0.1, 1);
          this.drawTags();
        }
      }
    },
    computexy(x, y) {
      let { scaleX, scale, scaleY, translateX, translateY } = this.canvasProp;
      const xy = {
        // x: x / scale - translateX,
        // y: y / scale - translateY,
        offsetX: (x - scaleX * (1 - scale) - translateX * scale) / scale,
        offsetY: (y - scaleY * (1 - scale) - translateY * scale) / scale
      };
      return xy;
    },
    computewh(width, height) {
      return {
        width: width / scale,
        height: height / scale
      };
    },
    handleKeyDown(event) {
      console.log("event.key", event.key);
      const step = 10; // 每次移动的步长
      switch (event.key) {
        case "ArrowUp":
          this.canvasProp.translateY -= step;
          break;
        case "ArrowDown":
          this.canvasProp.translateY += step;
          break;
        case "ArrowLeft":
          this.canvasProp.translateX -= step;
          break;
        case "ArrowRight":
          this.canvasProp.translateX += step;
          break;
      }
      this.drawTags(); // 重新绘制画布
    },
    saveData() {
      console.log("保存的数据", this.tagsData);
      let pointData = this.getPointData();
      console.log("pointData", pointData);
      this.setPointData(pointData);
      // this.$emit("saveData",this.setPointData(pointData));
    },
    getPointData() {
      const result = this.tagsData.map(tag => {
        const { startX, startY, width, height, rotate } = tag;
        const centerX = startX + width / 2;
        const centerY = startY + height / 2;

        const points = [
          { x: startX, y: startY }, // Top-left
          { x: startX + width, y: startY }, // Top-right
          { x: startX + width, y: startY + height }, // Bottom-right
          { x: startX, y: startY + height } // Bottom-left
        ];

        const rotatedPoints = points.map(point => {
          const dx = point.x - centerX;
          const dy = point.y - centerY;
          const rotatedX =
            centerX +
            dx * Math.cos((rotate * Math.PI) / 180) -
            dy * Math.sin((rotate * Math.PI) / 180);
          const rotatedY =
            centerY +
            dy * Math.cos((rotate * Math.PI) / 180) +
            dx * Math.sin((rotate * Math.PI) / 180);
          return [Math.round(rotatedX), Math.round(rotatedY)];
        });

        return rotatedPoints;
      });
      return result;
    },
    setPointData(result) {
      const newTagData = result.map(points => {
        const [p1, p2, p3, p4] = points;

        const centerX = (p1[0] + p3[0]) / 2;
        const centerY = (p1[1] + p3[1]) / 2;

        const width = Math.sqrt(
          Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2)
        );
        const height = Math.sqrt(
          Math.pow(p4[0] - p1[0], 2) + Math.pow(p4[1] - p1[1], 2)
        );

        const rotate =
          Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * (180 / Math.PI);

        return {
          label: "新矩形", // 可以根据需要更改标签
          color: "#0000ff", // 可以根据需要更改颜色
          type: "rectangle",
          startX: centerX - width / 2,
          startY: centerY - height / 2,
          width: width,
          height: height,
          rotate: rotate,
          isInit: false
        };
      });
      console.log("newTagData", newTagData);

      return newTagData;

      //this.tagsData = newTagData;
      //this.drawTags();
    },
    loadImageAndSetCanvas() {
      const img = new Image();
      img.src = this.images;
      img.onload = () => {
        this.bgImage = img;
        this.canvasProp.width = img.width;
        this.canvasProp.height = img.height;
        this.initCenterX = img.width / 2;
        this.initCenterY = img.height / 2;
        this.ctx = this.$refs.canvas.getContext("2d");
        this.$nextTick(() => {
          this.drawTags();
        });
      };
    },
    drawTags() {
      this.ctx.clearRect(0, 0, this.canvasProp.width, this.canvasProp.height);
      this.ctx.save();
      if (this.bgImage) {
        //画布缩放
        this.ctx.translate(this.canvasProp.scaleX, this.canvasProp.scaleY);
        this.ctx.scale(this.canvasProp.scale, this.canvasProp.scale);
        this.ctx.translate(-this.canvasProp.scaleX, -this.canvasProp.scaleY);
        //画布平移
        this.ctx.translate(
          this.canvasProp.translateX,
          this.canvasProp.translateY
        );

        this.ctx.drawImage(
          this.bgImage,
          0,
          0,
          this.bgImage.width,
          this.bgImage.height
        );

        this.tagsData.forEach(tag => {
          if (tag.type === "rectangle") {
            this.drawRectangle(tag);
          }
        });
      }
      this.ctx.restore();
    },
    rotateExec(tag) {
      let { startX, startY, width, height, rotate } = tag;
      this.ctx.translate(startX + width / 2, startY + height / 2);
      this.ctx.rotate((rotate * Math.PI) / 180);
      this.ctx.translate(-(startX + width / 2), -(startY + height / 2));
    },
    //手动添加输入框的时候
    drawRectangle(tag) {
      const { label, color, width, height, rotate, isInit } = tag;
      if (isInit) {
        tag.startX = this.initCenterX - width / 2;
        tag.startY = this.initCenterY - height / 2;
      }
      // 旋转矩形框,平移-旋转-平移到原来
      this.rotateExec(tag);
      this.ctx.save();
      // Draw the rectangle
      this.ctx.beginPath();
      this.ctx.rect(tag.startX, tag.startY, width, height);
      this.ctx.fillStyle = this.hexToRgba(color, 0.2);
      this.ctx.fill();
      this.ctx.lineWidth = 2;
      this.ctx.strokeStyle = color;
      this.ctx.stroke();
      //旋转矩形框

      // Draw the label text
      this.ctx.font = "14px Arial";
      this.ctx.textAlign = "center";
      this.ctx.textBaseline = "middle";
      let textX = tag.startX + width / 2;
      let textY = tag.startY + height / 2;
      let displayText = label;
      if (this.ctx.measureText(label).width > width) {
        displayText = this.truncateText(label, width);
      }
      this.ctx.fillStyle = color;
      this.ctx.strokeStyle = "white";
      this.ctx.lineWidth = 1;
      this.ctx.strokeText(displayText, textX, textY);
      this.ctx.fillText(displayText, textX, textY);

      this.drawResizeHandles(tag);
      this.drawRotateHandle(tag);
      this.ctx.restore();
      tag.isInit = false;
    },
    drawResizeHandles(tag) {
      const { startX, startY, width, height, color, rotate } = tag;
      const handles = [
        { x: startX, y: startY },
        { x: startX + width / 2, y: startY },
        { x: startX + width, y: startY },
        { x: startX, y: startY + height / 2 },
        { x: startX + width, y: startY + height / 2 },
        { x: startX, y: startY + height },
        { x: startX + width / 2, y: startY + height },
        { x: startX + width, y: startY + height }
      ];
      this.ctx.save();
      //this.rotateExec(tag);
      handles.forEach(handle => {
        this.ctx.beginPath();
        this.ctx.rect(handle.x - 2.5, handle.y - 2.5, 5, 5);
        this.ctx.fillStyle = "white";
        this.ctx.fill();
        this.ctx.lineWidth = 1;
        this.ctx.strokeStyle = color;
        this.ctx.stroke();
        //添加鼠标悬浮事件,如果鼠标悬浮在矩形框上,则鼠标样式显示为resize样式,否则显示为默认样式
      });
      this.ctx.restore();
    },
    drawRotateHandle(tag) {
      const { startX, startY, width, height, color, rotate } = tag;
      const handleX = startX + width;
      const handleY = startY - 12 - 5;
      this.ctx.save();
      // this.rotateExec(tag);
      this.ctx.beginPath();
      if (!this.rotateImages) {
        console.log("记载旋1转图片");
        var img = new Image();
        img.src = "/img/tagRotate.png";
        img.onload = () => {
          this.rotateImages = img;
          this.ctx.drawImage(img, handleX, handleY, 24, 24);
          this.ctx.restore();
        };
      } else {
        this.ctx.drawImage(this.rotateImages, handleX, handleY, 24, 24);
        this.ctx.restore();
      }
    },
    truncateText(text, maxWidth) {
      const ellipsis = "...";
      let truncated = text;
      while (this.ctx.measureText(truncated + ellipsis).width > maxWidth) {
        truncated = truncated.slice(0, -1);
      }
      return truncated + ellipsis;
    },
    hexToRgba(hex, alpha) {
      const bigint = parseInt(hex.replace("#", ""), 16);
      const r = (bigint >> 16) & 255;
      const g = (bigint >> 8) & 255;
      const b = bigint & 255;
      return `rgba(${r},${g},${b},${alpha})`;
    },
    onMouseDown(e) {
      const { offsetX, offsetY } = this.computexy(e.offsetX, e.offsetY);
      this.mouseDownX = offsetX;
      this.mouseDownY = offsetY;
      this.tagsData.forEach(tag => {
        const handle = this.getHandleUnderMouse(tag, offsetX, offsetY);
        if (handle) {
          this.isResizing = true; //缩放
          this.resizeHandle = handle;
          this.selectedTag = tag;
          return;
        }
        const rotateHandle = this.getRotateHandleUnderMouse(
          tag,
          offsetX,
          offsetY
        );
        if (rotateHandle) {
          this.isRotating = true; //旋转
          this.selectedTag = tag;
          this.initialRotation = this.selectedTag.rotate; // 保存初始旋转角度
          return;
        }
        if (this.isMouseInsideRectangle(tag, offsetX, offsetY)) {
          this.isDragging = true;
          this.selectedTag = tag;
          this.dragOffsetX = offsetX - tag.startX;
          this.dragOffsetY = offsetY - tag.startY;
        }
      });

      // if (!this.isDragging && !this.isResizing && !this.isRotating) {
      //   console.log("拖动canvas大小");
      //   this.$refs.canvas.style.cursor = "hand";
      //   this.isCanvasDraging = true;
      // }
    },
    onMouseUp() {
      this.isDragging = false;
      this.isResizing = false;
      this.isRotating = false;
      this.selectedTag = null;
      this.resizeHandle = null;
      this.isCanvasDraging = false;
    },
    onMouseMove(e) {
      // console.log("鼠标移动事件", e);
      const { offsetX, offsetY } = this.computexy(e.offsetX, e.offsetY);

      // if (this.isCanvasDraging) {

      //   this.canvasProp.translateX -= offsetX - this.mouseDownX;
      //   this.canvasProp.translateY -= offsetY - this.mouseDownY;
      //   this.drawTags();
      //   return;
      // }
      if (this.isDragging && this.selectedTag) {
        //矩形框拖动
        this.selectedTag.startX = offsetX - this.dragOffsetX;
        this.selectedTag.startY = offsetY - this.dragOffsetY;
        this.drawTags();
      } else if (this.isResizing && this.selectedTag) {
        //矩形框缩放
        const handle = this.resizeHandle;
        switch (handle.position) {
          case "top-left":
            this.selectedTag.width += this.selectedTag.startX - offsetX;
            this.selectedTag.height += this.selectedTag.startY - offsetY;
            this.selectedTag.startX = offsetX;
            this.selectedTag.startY = offsetY;
            break;
          case "top":
            this.selectedTag.height += this.selectedTag.startY - offsetY;
            this.selectedTag.startY = offsetY;
            break;
          case "top-right":
            this.selectedTag.width = offsetX - this.selectedTag.startX;
            this.selectedTag.height += this.selectedTag.startY - offsetY;
            this.selectedTag.startY = offsetY;
            break;
          case "left":
            this.selectedTag.width += this.selectedTag.startX - offsetX;
            this.selectedTag.startX = offsetX;
            break;
          case "right":
            this.selectedTag.width = offsetX - this.selectedTag.startX;
            break;
          case "bottom-left":
            this.selectedTag.width += this.selectedTag.startX - offsetX;
            this.selectedTag.height = offsetY - this.selectedTag.startY;
            this.selectedTag.startX = offsetX;
            break;
          case "bottom":
            this.selectedTag.height = offsetY - this.selectedTag.startY;
            break;
          case "bottom-right":
            this.selectedTag.width = offsetX - this.selectedTag.startX;
            this.selectedTag.height = offsetY - this.selectedTag.startY;
            break;
        }
        this.drawTags();
      } else if (this.isRotating && this.selectedTag) {
        //矩形旋转
        const centerX = this.selectedTag.startX + this.selectedTag.width / 2;
        const centerY = this.selectedTag.startY + this.selectedTag.height / 2;

        const initDeg = Math.atan2(
          this.mouseDownY - centerY,
          this.mouseDownX - centerX
        );
        const currentDeg = Math.atan2(offsetY - centerY, offsetX - centerX);
        // this.selectedTag.rotate = ((currentDeg - initDeg) * 180) / Math.PI;
        const rotationChange = ((currentDeg - initDeg) * 180) / Math.PI;
        this.selectedTag.rotate = this.initialRotation + rotationChange; // 根据初始旋转角度调整
        this.drawTags();
      } else {
        let cursorSet = false;

        this.tagsData.some(tag => {
          const handle = this.getHandleUnderMouse(tag, offsetX, offsetY);
          if (handle) {
            let cursor = this.getCursorStyle(handle);
            this.$refs.canvas.style.cursor = cursor;
            cursorSet = true;
            return true;
          }
          const rotateHandle = this.getRotateHandleUnderMouse(
            tag,
            offsetX,
            offsetY
          );
          if (rotateHandle) {
            this.$refs.canvas.style.cursor = "crosshair";
            cursorSet = true;
            return true;
          }
          if (this.isMouseInsideRectangle(tag, offsetX, offsetY)) {
            this.$refs.canvas.style.cursor = "move";
            cursorSet = true;
            return true;
          }
          return false;
        });

        if (!cursorSet) {
          this.$refs.canvas.style.cursor = "default";
        }
      }
    },
    getCursorCustomStyle(handle) {
      if (handle.position == "left" || handle.position == "right") {
        return `h-cursor`;
      } else if (handle.position === "top" || handle.position == "bottom") {
        return `s-cursor`;
      } else if (
        handle.position === "top-left" ||
        handle.position == "top-right"
      ) {
        return `lx-cursor`;
      } else if (
        handle.position === "bottom-left" ||
        handle.position == "bottom-right"
      ) {
        return `-cursor`;
      }
    },
    getCursorStyle(handle) {
      if (handle.position == "left") {
        return `w-resize`;
      } else if (handle.position === "top") {
        return `n-resize`;
      } else if (handle.position === "top-left") {
        return `nw-resize`;
      } else if (handle.position === "top-right") {
        return `ne-resize`;
      } else if (handle.position === "right") {
        return `e-resize`;
      } else if (handle.position === "bottom") {
        return `s-resize`;
      } else if (handle.position == "bottom-left") {
        return `sw-resize`;
      } else if (handle.position === "bottom-right") {
        return `se-resize`;
      }
    },

    getHandleUnderMouse(tag, x, y) {
      const handles = [
        {
          x: tag.startX,
          y: tag.startY,
          position: "top-left"
        },
        {
          x: tag.startX + tag.width / 2,
          y: tag.startY,
          position: "top"
        },
        {
          x: tag.startX + tag.width,
          y: tag.startY,
          position: "top-right"
        },
        {
          x: tag.startX,
          y: tag.startY + tag.height / 2,
          position: "left"
        },
        {
          x: tag.startX + tag.width,
          y: tag.startY + tag.height / 2,
          position: "right"
        },
        {
          x: tag.startX,
          y: tag.startY + tag.height,
          position: "bottom-left"
        },
        {
          x: tag.startX + tag.width / 2,
          y: tag.startY + tag.height,
          position: "bottom"
        },
        {
          x: tag.startX + tag.width,
          y: tag.startY + tag.height,
          position: "bottom-right"
        }
      ];
      return handles.find(handle => {
        let { rotatedX, rotatedY } = this.rotateAfterPoint(tag, x, y);
        return this.isMouseOverHandle(handle, rotatedX, rotatedY);
      });
    },
    isMouseOverHandle(handle, x, y) {
      return (
        x >= handle.x - 2.5 &&
        x <= handle.x + 2.5 &&
        y >= handle.y - 2.5 &&
        y <= handle.y + 2.5
      );
    },
    getRotateHandleUnderMouse(tag, x, y) {
      let { rotatedX, rotatedY } = this.rotateAfterPoint(tag, x, y);

      const handleX = tag.startX + tag.width;
      const handleY = tag.startY - 12 - 5;
      if (
        rotatedX > handleX &&
        rotatedX <= handleX + 24 &&
        rotatedY > handleY &&
        rotatedY <= handleY + 24
      ) {
        return true;
      } else {
        return false;
      }
    },
    isMouseInsideRectangle(tag, x, y) {
      const { startX, startY, width, height, rotate } = tag;
      this.ctx.save();
      //this.rotateExec(tag);
      let { rotatedX, rotatedY } = this.rotateAfterPoint(tag, x, y);
      const isInside =
        rotatedX >= startX &&
        rotatedX <= startX + width &&
        rotatedY >= startY &&
        rotatedY <= startY + height;
      this.ctx.restore();
      return isInside;
    },
    //解决这个问题有两个思路,一个是将旋转后矩形的四个点坐标计算出来,这种方法比较麻烦。另一个思路是逆向的,将要判断的点,以矩形的中点为中心,做逆向旋转,计算出其在 canvas 中的坐标,这个坐标,可以继续参与我们之前点在矩形内的计算
    rotateAfterPoint(tag, x, y) {
      const { startX, startY, width, height, rotate } = tag;
      const centerX = startX + width / 2;
      const centerY = startY + height / 2;

      let dx = x - centerX;
      let dy = y - centerY;
      // // 将鼠标点旋转回矩形未旋转前的坐标
      let rotatedX =
        dx * Math.cos((-rotate * Math.PI) / 180) -
        dy * Math.sin((-rotate * Math.PI) / 180) +
        centerX;
      let rotatedY =
        dy * Math.cos((-rotate * Math.PI) / 180) +
        dx * Math.sin((-rotate * Math.PI) / 180) +
        centerY;

      return { rotatedX: rotatedX, rotatedY: rotatedY };
    }
  }
};
</script>
  
  <style scoped>
.h-cursor {
  cursor: url("/img/h-custor"), auto !important;
}
.s-cursor {
  cursor: url("/img/s-custor"), auto !important;
}
.lx-cursor {
  cursor: url("/img/lx-custor"), auto !important;
}
.yx-cursor {
  cursor: url("/img/yx-custor"), auto !important;
}
.rotate-cursor {
  cursor: url("/img/yx-custor"), auto !important;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1972214.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言之指针函数与函数指针

目录 1 前言2 函数指针与指针函数理解与区分函数指针指针函数 3 函数指针与指针函数常见用法函数指针指针函数 4 总结 1 前言 项目中时常遇到指正函数与函数指正的使用&#xff0c;时间一长容易出现概念混淆。 2 函数指针与指针函数理解与区分 函数指针 原型&#xff1a;返回…

React 项目中如何使用 easyPlayer-pro.js

目录 背景EasyPlayer.js H5播放器简单介绍EasyPlayer.js 简介EasyPlayer.js 功能说明&#xff1a;配置属性事件回调方法 下载 EasyPlayer.js引入使用重写webpack问题处理证清白最后 背景 项目中要使用 easyplayer-pro.js 播放视频&#xff0c;查了下资料&#xff0c;网上基本都…

Axure在数据可视化原型设计中的革新力量

在数据洪流与信息爆炸的当下&#xff0c;产品设计不再局限于界面的美观与功能的堆砌&#xff0c;而是更多地聚焦于如何高效地呈现与解读数据。Axure RP&#xff0c;作为原型设计领域的璀璨明星&#xff0c;正以其独特的魅力&#xff0c;引领着数据可视化原型设计的新风尚。本文…

【云原生】数据库忘记密码怎么办?

相信很多人都会遇到在虚拟机中忘记数据库密码的情况&#xff0c;想必大家都很苦恼&#xff0c;所以今天给大家来讲讲数据库忘记密码了如何修改密码再登录数据库&#xff01;&#xff01;&#xff01; 1、关闭数据库服务 systemctl stop mariadb 2、执行MySQL 服务器在启动时跳…

【LeetCode】146.LRU页面置换

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

C++ string类(你想要的这里都有)

1. string类概述 C语言中&#xff0c;字符串是以“\0”结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要用户管理&#…

Javaweb项目|ssm基于web的健身中心管理系统的的设计与实现jsp

收藏点赞不迷路 关注作者有好处 文末获取源码 一、系统展示 二、万字文档展示 基于ssm基于web的健身中心管理系统的的设计与实现jsp 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringSpringMVCMyBatisVue 工具&#xff1a;IDEA/Ecilpse、Navicat、…

HTTP常见的请求方法、响应状态码、接口规范介绍

常见的请求方法 GET&#xff08;查询&#xff0c;从服务器获取资源&#xff09;POST&#xff08;新增&#xff0c;在服务器创建资源&#xff09;PUT&#xff08;修改&#xff0c;在服务器更新资源&#xff09;DELETE&#xff08;删除&#xff0c;从服务器删除资源&#xff09;…

跟《经济学人》学英文:2024年07月27日这期 AI firms will soon exhaust most of the internet’s data

AI firms will soon exhaust most of the internet’s data Can they create more? 原文&#xff1a; In 2006 fei-fei li, then at the University of Illinois, now at Stanford University, saw how mining the internet might help to transform AI research. Linguis…

商场购物中心营销怎么玩?附230个参考案例

随着消费市场的不断演变&#xff0c;商场购物中心正面临着前所未有的竞争压力。如何在众多竞争对手中脱颖而出&#xff0c;吸引消费者的目光&#xff1f; 今天道叔将探讨商场购物中心营销的新玩法&#xff0c;帮助您在激烈的市场竞争中占据一席之地。 码字不易&#xff0c;如…

第三方库认识- Mysql 数据库 API 认识

文章目录 一、msyql数据库API接口1.初始化mysql_init()——mysql_init2.链接数据库mysql_real_connect——mysql_real_connect3.设置当前客户端的字符集——mysql_set_character_set4.选择操作的数据库——mysql_select_db5.执行sql语句——mysql_query6.保存查询结果到本地——…

配置frp实现内网穿透(.toml配置文件)

简介 frp 是一款高性能的反向代理应用&#xff0c;专注于内网穿透。它支持多种协议&#xff0c;包括 TCP、UDP、HTTP、HTTPS 等&#xff0c;并且具备 P2P 通信功能。使用 frp&#xff0c;您可以安全、便捷地将内网服务暴露到公网&#xff0c;通过拥有公网 IP 的节点进行中转。…

软件测试---禅道

一、禅道简介 二、安装 三、新手引导 &#xff08;1&#xff09;在系统创建一个新的用户帐号&#xff1a; &#xff08;2&#xff09;在系统创建一个新的项目集&#xff1a; &#xff08;3&#xff09;在系统创建一个新的产品&#xff1a; &#xff08;4&#xff09;在系统创…

【AI学习】[2024北京智源大会]具身智能:面向通用机器人的具身多模态大模型系统

面向通用机器人的具身多模态大模型系统 王 鹤 | 北京大学助理教授&#xff0c;智源学者 边听边做一些记录 一、通用机器人的概念和发展趋势&#xff0c;以及实现通用机器人的基石层、大脑和小脑模型等方面的思考和探索。 主要观点&#xff1a;人形机器人&#xff0c;是未来…

基于SpringBoot+Vue的校园便利平台(带1w+文档)

基于SpringBootVue的校园便利平台(带1w文档) 基于SpringBootVue的校园便利平台(带1w文档) 本平台采用B/S架构、采用的数据库是MySQL&#xff0c;使用JAVA技术开发。该平台的开发方式无论在国内还是国外都比较常见&#xff0c;而且开发完成后使用普遍&#xff0c;可以给平台用户…

多址技术(FDMA,TDMA,CDMA,帧,时隙)(通俗易懂)

多址技术是一种区分用户的技术。 举个例子&#xff0c;一个基站发出信息&#xff0c;如何确定是发给谁的&#xff1f; 这个技术就是解决这个问题的。 多址技术常见的有三种&#xff1a; 频分多址&#xff08;FDMA&#xff09;、时分多址&#xff08;TDMA&#xff09;、码分…

程序员学长 | 快速学习一个算法,UNet

本文来源公众号“程序员学长”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;快速学习一个算法&#xff0c;UNet 今天给大家分享一个超强的算法模型&#xff0c;UNet UNet 是一种卷积神经网络架构&#xff0c;最初由 Olaf Ronne…

快速排序(下)

快速排序&#xff08;下&#xff09; 前言 在上一篇文章中我们了解了快速排序算法&#xff0c;但那是Hoare的版本&#xff0c;其实还有别的版本&#xff1a;一种是挖坑法&#xff0c;它们的区别主要在于如何找基准值。霍尔的版本思路难理解但代码好理解&#xff0c;挖坑法则是…

Java新特性(二) Stream与Optional详解

Java8新特性&#xff08;二&#xff09; Stream与Optional详解 一. Stream流 1. Stream概述 1.1 基本概念 Stream&#xff08;java.util.stream&#xff09; 是Java 8中新增的一种抽象流式接口&#xff0c;主要用于配合Lambda表达式提高批量数据的计算和处理效率。Stream不是…

【前端】中后台框架 添加其他布局的探索

文章目录 前言需求整理第一步&#xff1a;实现可切换布局第二步&#xff1a;配置页面顶部的路由&#xff08;一级路由&#xff09;第三部&#xff1a;配置左侧二级和二级以上的路由第四部&#xff1a;给侧边栏加一个动画第五部&#xff1a;刷新页面之后顶部路由、左侧路由的回显…