精华内容
下载资源
问答
  • jQuery下拉框菜单多选插件是一款点击弹出图片自定义选择区域编辑内容标注代码。
  • 博文链接:https://dbajun.iteye.com/blog/242763
  • 图片标注工具LabelImg的源码,这个工具可以用来制作自己深度学习的数据集
  • 在线h5图片标注源码

    2020-11-11 11:35:06
    精简的H5在线图片标注、图片背景画板,支持BASE64图片加载背景,支持使用不同的颜色画笔在线在图片上画自由线条,只有两个文件,一个HTM,一个JS,主要功能实现在JS中
  • 修改聚合样式,需要在js文件中找到../assets开头的图片链接,改为自己的本地链接。需要注意的是,链接不需要加.jpg或.png,需要那种格式,可以将后面变量改了就可以。js文件默认会在链接地址后面拼接0.jpg或0.png,...
  • LabelImg_win版用于图片标注,可方便创建自己的数据集,方便进行深度学习训练。这个是WINDOWS平台版本不需编译。
  • 在一些电商网站和家居网站上我们会看到这样的应用,一张图片中显示多种商品,点击每个商品可以弹出对应商品的简单介绍,包括价格等等,极大的提高了用户体验,今天我们通过实例给大家分享使用jQuery插件来实现这一...
  • 用于标注深度学习图片分割任务的工具,images_annotation_programme-master.zip,解压既可运行
  • 图片标注工具

    2019-04-11 11:28:05
    图片标注工具,支持windows下直接运行,压缩包内包括labelImg.exe程序和预定义的类别名称predefined_classes.txt文档。 使用说明:先在predefined_classes.txt内 按行输入所有标注的类别名称,然后运行labelImg.exe...
  • as3图片标注软件

    2017-11-13 23:05:42
    as3写的教育类软件,可以打开本地图片文件,并进行旋转、放大、在上面做出批注、划线、画圈等操作,特别适合于教师评讲试卷。
  • iPhotoDraw 是一款具备基础的照片编辑功能的工具,内置基本编辑功能,线条、文字、对话框或是箭头等等,而且在设置调整上,是相当的精细,所以是iPhotoDraw非常能够满足大众需要的一款软件。 iPhotoDraw让你一个图像...
  • jQuery图片标注提示插件是一款鼠标响应式图片描述提示标注特效。
  • iPhotoDraw-免费的图片处理软件,支持中文,主要用作图片标注及编辑,能在图片中方便地添加标注,例如文字对话框、箭头标记、测量线等图形。 更新日志 阴影和光晕效果 快照 锁定和解锁的形状 保存形状图片 组渲染...
  • jQuery制作图片标注提示信息插件。 演示地址:http://www.xwcms.net/js/tsk-fdc-dcc/88743.html
  • Labelme图片标注软件

    2021-10-12 17:38:00
    深度学习图片标注软件
  • labelImg图片标注工具

    2018-10-13 16:40:17
    图片标注工具LabelImg,有需要的朋友自己取用,解压运行即可。
  • jQuery下拉框菜单多选插件是一款点击弹出图片自定义选择区域编辑内容标注代码。
  • 经过几天的尝试和学习,封装了...jQuery图片标注组件(jquery.picsign) 在线演示:http://artlessbruin.gitee.io/picsign/ gitee:https://gitee.com/ArtlessBruin/PicSign 1. 组件依赖 jquery <script src=
  • LabelImg 图片标注工具 for Mac

    千次阅读 2018-05-24 15:31:08
    图片标注是物体检测等工作的基础,就是将图片中的物体,使用矩形框画出来,并且指定合适的标签。目前,比较常用的标注工具就是LabelImg,LabelImg提供可视化的画框操作界面,将图片的标注信息写入同名的XML文件中。 ...

    LabelImg

    图片标注(Image Annotation)是物体检测等工作的基础,就是使用矩形框标注出图片中的物体,同时指定合适的标签。目前,比较常用的标注工具就是LabelImg。LabelImg提供可视化的标注操作界面,将图片的标注信息存入同名的XML文件中。

    本文地址https://blog.csdn.net/caroline_wendy/article/details/80435225

    在LabelImg工程的主页,有一行对于操作系统的说明:

    macOS

    即目前的可执行文件,仅支持Windows和Linux,macOS需要从源码构建应用。那么,如何在Mac中构建LabelImg应用呢?

    已编译完成的LabelImg的下载地址,在Mac中,可以直接使用。

    GitHub

    LabelImg是一个GitHub的开源工程,除了源码之外,还有一些应用图标和链接库等文件,导致工程较大,下载较慢。

    其实,GitHub的下载链接是自带加速功能,由于国内的域名污染,在访问链接时,需要跨越较多的无效域名,导致下载较慢,参考。因此,找到GitHub的真实地址,直接访问,就可以加快下载速度。这个方法,也适用于访问其他较慢的国外下载链接。

    找到国外域名的真实IP地址,可以直接使用IPAddress,或访问lookup接口均可。

    IPAddress

    输入域名,即可查询真实的IP地址,例如查询github.com

    IP

    注意:查询的IP地址可能会不同,一般而言,直接使用国外的IP地址,比使用国内域名服务器的地址,能够获得更快下载速度。

    与GitHub相关的域名有两个,即:

    • github.com:真实IP 192.30.253.xxx;
    • github.global.ssl.fastly.net:真实IP 151.101.13.xxx;

    查询完成之后,将域名和域名真实IP,写入Mac的hosts文件中,由于hosts是系统文件,需要获取管理员权限sudo进行写入:

    sudo vi /etc/hosts

    在hosts的末尾添加:

    192.30.253.xxx  github.com
    151.101.13.xxx  github.global.ssl.fastly.net
    159.122.18.xxx  dl.bintray.com

    除GitHub的链接之外,也可以添加其他国外域名的IP,如dl.bintray.com等。

    Conda

    在Pip源中,Qt的相关包安装异常,因而,使用Conda源作为Python环境。Conda与Pip的功能类似,近似于Pip与Virtualenv的结合,用于隔离Python的系统环境。由于仅仅使用Conda的Python功能,直接安装Conda的Miniconda版本即可,选择Python 2.7版本。

    Miniconda

    Conda的Python版本是编译Conda功能所用的版本,与Conda所创建虚拟环境的Python版本无关,也就是说,Python 2.7版本的Conda也可以创建Python 3.6版本的虚拟环境。

    下载Miniconda的sh脚本,直接执行即可,安装目录位于~/miniconda2中,conda可执行文件位于miniconda2的bin文件夹下,其中就包含conda命令。由于Conda的系统环境设置问题,导致覆盖原有的Python命令路径,需要修改终端配置。

    终端

    终端shell是oh-my-zsh,即zsh终端。在zsh终端启动时,执行.zshrc脚本。因此,在.zshrc的末尾添加:

    source ~/.bash_profile

    即执行.bash_profile脚本。

    将定制的环境配置,添加至.bash_profile中,用于在zsh中执行:

    • 将定制的可执行文件夹~/bin放入系统路径PATH中;
    • 将Python路径PYTHONPATH指定为系统的python;
    • 修改Miniconda2配置,将默认Python路径位于miniconda2/bin路径之前,防止干扰;

    即:

    export PATH="$PATH:/Users/[name]/bin"
    export PYTHONPATH="/usr/local/bin/python"
    
    # added by Miniconda2 installer
    export PATH="$PATH:/Users/[name]/miniconda2/bin"

    ~/bin中,创建conda的软链接:

    ln -s conda /Users/[name]/miniconda2/bin/conda

    conda -> /Users/[name]/miniconda2/bin/conda

    最后,执行source ~/.bash_profile或重启终端,将命令导入至shell环境,直接输入conda即可运行命令,

    shell的调用流程:zsh -> .zshrc -> .bash_profile -> bin -> conda。

    其他shell命令的添加方法,与此类似。

    操作

    conda创建虚拟环境,如Python 3.x版本的py3,

    conda create -n py3 python=3
    source activate py3

    虚拟环境py3位于miniconda2/envs/中,如果重名,删除即可。

    安装Python包:

    conda install pyqt

    取消激活环境

    source deactivate

    conda的操作简单便捷,如果一些pip源的包没有收录至conda源,可以在Google中搜索conda的个人源。

    其他Conda命令,请参考

    加速

    conda源位于国外,速度较慢,可以切换为国内的清华镜像,执行以下设置即可。

    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
    conda config --set show_channel_urls yes

    conda的config文件位于~/.condarc中。

    LabelImg

    系统环境已经配置完成,GitHub和Conda均可高效使用,可以开始构建LabelImg项目了。

    在GitHub中下载labelImg工程:

    git clone https://github.com/tzutalin/labelImg.git

    创建conda的Python虚拟环境,下载依赖包,参考,环境是Qt5 + Python 2:

    conda create -n py2 python=2
    source activate py2
    conda install qt
    conda install pyqt
    conda install libxml2
    conda install lxml
    make qt5
    python labelImg.py

    其中的conda安装qt,也可以替换为brew安装

    conda install qt
    conda install pyqt
    或
    brew install qt
    brew install pyqt

    labelImg.py是程序入口,最终的启动效果:

    LabelImg

    错误梳理

    为什么不使用Qt4?

    因为Qt4与Mac系统的10.13(High Sierra)版本兼容性不好,可以编译成功,但是无法读取jpeg等类型的图片,所以只能选择Qt5进行编译。

    为什么安装和编译的是Qt5,却显示找不到Qt4?

    提示找不到Qt4的Bug,如下:

    Traceback (most recent call last):
      File "labelImg.py", line 1453, in <module>
        sys.exit(main())
      File "labelImg.py", line 1449, in main
        app, _win = get_main_app(sys.argv)
      File "labelImg.py", line 1442, in get_main_app
        argv[3] if len(argv) >= 4 else None)
      File "labelImg.py", line 98, in __init__
        self.settings.load()
      File "/Users/wang/exercises/labelImg/libs/settings.py", line 33, in load
        self.data = pickle.load(f)
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 1384, in load
        return Unpickler(file).load()
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 864, in load
        dispatch[key](self)
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 1139, in load_reduce
        value = func(*args)
    ImportError: No module named PyQt4.QtCore

    原因是,代码中导入包的异常处理,当无法加载Qt5时,就选择加载Qt4,这段逻辑简直无厘头,看似聪明,实则干扰调试,也不提示“无法找到 Qt5”的异常,让开发者误以为还要安装Qt4。

    try:
        from PyQt5.QtGui import *
        from PyQt5.QtCore import *
        from PyQt5.QtWidgets import *
    except ImportError:
        from PyQt4.QtGui import *
        from PyQt4.QtCore import *

    实际上,Qt5并未直接安装至系统环境中,而是安装在Python的site-packages中,即:

    /usr/local/lib/python2.7/site-packages/

    将这个包的文件夹,导入至Python路径即可

    export PYTHONPATH="$PYTHONPATH:/usr/local/lib/python2.7/site-packages/"

    为什么包含两份Qt的二进制文件?

    提示有两个Qt二进制文件的集合,如下:

    objc[32802]: Class RunLoopModeTracker is implemented in both xxx and yyy. One of the two will be used. Which one is undefined.
    QObject::moveToThread: Current thread (0x7fefa3512020) is not the object's thread (0x7fffb38b9380).
    Cannot move to target thread (0x7fefa3512020)
    
    You might be loading two sets of Qt binaries into the same process. Check that all plugins are compiled against the right Qt binaries. Export DYLD_PRINT_LIBRARIES=1 and check that only one set of binaries are being loaded.
    This application failed to start because it could not find or load the Qt platform plugin "cocoa"
    in "".
    
    Available platform plugins are: cocoa, minimal, offscreen.
    
    Reinstalling the application may fix this problem.

    原因是,在Mac系统中,使用brew安装一遍Qt,又使用conda安装一遍Qt,两个包重叠使用,卸载一个即可,优先卸载conda安装的Qt,执行以下操作:

    brew install qt
    brew install pyqt
    conda uninstall pyqt
    conda uninstall qt

    为什么提示Qt4和Qt5同时存在?

    提示同时使用PyQt4和PyQt5,如下:

    Traceback (most recent call last):
      File "labelImg.py", line 1453, in <module>
        sys.exit(main())
      File "labelImg.py", line 1449, in main
        app, _win = get_main_app(sys.argv)
      File "labelImg.py", line 1442, in get_main_app
        argv[3] if len(argv) >= 4 else None)
      File "labelImg.py", line 98, in __init__
        self.settings.load()
      File "/Users/wang/exercises/labelImg/libs/settings.py", line 33, in load
        self.data = pickle.load(f)
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 1384, in load
        return Unpickler(file).load()
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 864, in load
        dispatch[key](self)
      File "/Users/wang/miniconda2/envs/py2/lib/python2.7/pickle.py", line 1139, in load_reduce
        value = func(*args)
    RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap the QObject class

    根据错误位置定位至settings.py的加载pkl数据操作,原因是.labelImgSettings.pkl文件可能是用Qt4生成的,删除即可,应用则会重新生成。

    self.path = os.path.join(home, '.labelImgSettings.pkl')

    还有类似的PyQt4和PyQt5同时存在的问题:

    Traceback (most recent call last):
      File "labelImg.py", line 1453, in <module>
        sys.exit(main())
      File "labelImg.py", line 1449, in main
        app, _win = get_main_app(sys.argv)
      File "labelImg.py", line 1442, in get_main_app
        argv[3] if len(argv) >= 4 else None)
      File "labelImg.py", line 415, in __init__
        self.filePath = ustr(defaultFilename)
      File "/Users/wang/exercises/labelImg/libs/ustr.py", line 7, in ustr
        from PyQt4.QtCore import QString
    RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap the QObject class

    根据错误位置定位至ustr.py文件,原因是引用Qt4的包,直接注释或者使用Qt5重写这一段逻辑,即可。

    import sys
    
    def ustr(x):
        '''py2/py3 unicode helper'''
    
        if sys.version_info < (3, 0, 0):
            # from PyQt4.QtCore import QString
            if type(x) == str:
                return x.decode('utf-8')
            # if type(x) == QString:
            #     return unicode(x)
            return x
        else:
            return x  # py3

    LabelImg图片标注工具并不完美,不过已经可以使用,为标注工作提供了便捷。

    已编译完成的LabelImg的下载地址,在Mac中,可以直接使用。

    OK, that’s all! Enjoy it!

    展开全文
  • tkinter是Python下面向tk的图形界面接口库,可以方便地进行图形界面设计和交互操作编程,本文通过实例代码给大家介绍的Python tkinter实现图片标注功能,感兴趣的朋友一起看看吧
  • PhotoCommenter是一款简单易用的图片标注软件。内置丰富的标注库、图形库。可以方便快捷地对图片添加标注。 详细信息可查看:http://www.laaan.cn/?page_id=1106
  • vue 使用canvas 进行图片标注

    千次阅读 2020-12-28 14:02:27
    <template> <div class="draw"> <div class="drawTop" ref="drawTop" v-if="lineStep == lineNum"> <div> <el-button type @click="resetAll">清空<...撤销&l..
    
    <template>
      <div class="draw">
        <div class="drawTop" ref="drawTop" v-if="lineStep == lineNum">
          <div>
            <el-button type @click="resetAll">清空</el-button>
            <el-button type @click="repeal">撤销</el-button>
            <el-button type @click="canvasRedo">恢复</el-button>
            <el-button type @click="downLoad">下载</el-button>
          </div>
          <div style="width:22%">
            选择绘制类型:
            <el-radio-group v-model="type" size="medium">
              <el-radio-button
                v-for="(item,index) in typeOption"
                :key="index"
                :label="item.value"
                @click.native="radioClick(item.value)"
              >{{item.label}}
              </el-radio-button>
            </el-radio-group>
          </div>
          <div style="width:15%">
            边框粗细:
            <el-slider v-model="lineWidth" :min="0" :max="10" :step="1" style="width:70%"></el-slider>
          </div>
          <div>
            线条颜色:
            <el-color-picker v-model="strokeStyle"></el-color-picker>
          </div>
          <div>
            文字颜色:
            <el-color-picker v-model="fontColor"></el-color-picker>
          </div>
          <div style="width:15%">
            文字大小:
            <el-slider v-model="fontSize" :min="14" :max="36" :step="2" style="width:70%"></el-slider>
          </div>
        </div>
        <div style="height: 100%;width: 100%;position:relative;">
          <div class="content"></div>
          <input v-show="isShow" type="text" @blur="txtBlue" ref="txt" id="txt"
                 style="z-index: 9999;position: absolute;border: 0;background:none;outline: none;"/>
        </div>
      </div>
    </template>
     
    <script>
      export default {
        name: "callout",
        props: {
          imgPath: undefined,
     
        },
        data() {
          return {
            isShow: false,
            canvas: "",
            ctx: "",
            ctxX: 0,
            ctxY: 0,
            lineWidth: 1,
            type: "L",
            typeOption: [
              {label: "线", value: "L"},
              {label: "矩形", value: "R"},
              {label: "箭头", value: "A"},
              {label: "文字", value: "T"},
            ],
            canvasHistory: [],
            step: 0,
            loading: false,
            fillStyle: "#CB0707",
            strokeStyle: "#CB0707",
            lineNum: 2,
            linePeak: [],
            lineStep: 2,
            ellipseR: 0.5,
            dialogVisible: false,
            isUnfold: true,
            fontSize: 24,
            fontColor: "#CB0707",
            fontFamily: '微软雅黑',
            img: new Image(),
          };
        },
        mounted() {
          let _this = this;
          let image = new Image();
          image.setAttribute('crossOrigin', 'anonymous');
          image.src = this.imgPath;
          image.onload = function () {//图片加载完,再draw 和 toDataURL
            if (image.complete) {
              _this.img = image
              let content = document.getElementsByClassName("content")[0];
              _this.canvas = document.createElement("canvas");
              _this.canvas.height = _this.img.height
              _this.canvas.width = _this.img.width
              _this.ctx = _this.canvas.getContext("2d");
              _this.ctx.globalAlpha = 1;
              _this.ctx.drawImage(_this.img, 0, 0)
              _this.canvasHistory.push(_this.canvas.toDataURL());
              _this.ctx.globalCompositeOperation = _this.type;
              content.appendChild(_this.canvas);
              _this.bindEventLisner();
            }
          }
        },
        methods: {
          radioClick(item) {
            if (item != "T") {
              this.txtBlue()
              this.resetTxt()
            }
          },
          // 下载画布
          downLoad() {
            let _this = this;
            let url = _this.canvas.toDataURL("image/png");
            let fileName = "canvas.png";
            if ("download" in document.createElement("a")) {
              // 非IE下载
              const elink = document.createElement("a");
              elink.download = fileName;
              elink.style.display = "none";
              elink.href = url;
              document.body.appendChild(elink);
              elink.click();
              document.body.removeChild(elink);
            } else {
              // IE10+下载
              navigator.msSaveBlob(url, fileName);
            }
          },
          // 清空画布及历史记录
          resetAll() {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.canvasHistory = [];
            this.ctx.drawImage(this.img, 0, 0);
            this.canvasHistory.push(this.canvas.toDataURL());
            this.step = 0;
            this.resetTxt();
          },
          // 清空当前画布
          reset() {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.ctx.drawImage(this.img, 0, 0);
            this.resetTxt();
          },
          // 撤销方法
          repeal() {
            let _this = this;
            if (this.isShow) {
              _this.resetTxt();
              _this._repeal();
            } else {
              _this._repeal();
            }
     
          },
          _repeal() {
            if (this.step >= 1) {
              this.step = this.step - 1;
              let canvasPic = new Image();
              canvasPic.src = this.canvasHistory[this.step];
              canvasPic.addEventListener("load", () => {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.ctx.drawImage(canvasPic, 0, 0);
                this.loading = true;
              });
            } else {
              this.$message.warning("不能再继续撤销了");
            }
          },
          // 恢复方法
          canvasRedo() {
            if (this.step < this.canvasHistory.length - 1) {
              if (this.step == 0) {
                this.step = 1;
              } else {
                this.step++;
              }
              let canvasPic = new Image();
              canvasPic.src = this.canvasHistory[this.step];
              canvasPic.addEventListener("load", () => {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.ctx.drawImage(canvasPic, 0, 0);
              });
            } else {
              this.$message.warning("已经是最新的记录了");
            }
          },
          // 绘制历史数组中的最后一个
          rebroadcast() {
            let canvasPic = new Image();
            canvasPic.src = this.canvasHistory[this.step];
            canvasPic.addEventListener("load", () => {
              this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
              this.ctx.drawImage(canvasPic, 0, 0);
              this.loading = true;
            });
          },
          // 绑定事件,判断分支
          bindEventLisner() {
            let _this = this;
            let r1, r2; // 绘制圆形,矩形需要
            this.canvas.onmousedown = function (e) {
              console.log("onmousedown");
              if (_this.type == "L") {
                _this.createL(e, "begin");
              } else if (_this.type == "R") {
                r1 = e.layerX;
                r2 = e.layerY;
                _this.createR(e, "begin", r1, r2);
              } else if (_this.type == "A") {
                _this.drawArrow(e, "begin")
              } else if (_this.type == "T") {
                _this.createT(e, "begin")
              }
            };
            this.canvas.onmouseup = function (e) {
              console.log("onmouseup");
              if (_this.type == "L") {
                _this.createL(e, "end");
              } else if (_this.type == "R") {
                _this.createR(e, "end", r1, r2);
                r1 = null;
                r2 = null;
              } else if (_this.type == "A") {
                _this.drawArrow(e, "end")
              } else if (_this.type == "T") {
                _this.createT(e, "end")
              }
            };
     
          },
          // 绘制线条
          createL(e, status) {
            let _this = this;
            if (status == "begin") {
              _this.ctx.beginPath();
              _this.ctx.moveTo(e.layerX, e.layerY);
              _this.canvas.onmousemove = function (e) {
                console.log("onmousemove");
                _this.ctx.lineTo(e.layerX, e.layerY);
                _this.ctx.strokeStyle = _this.strokeStyle;
                _this.ctx.lineWidth = _this.lineWidth;
                _this.ctx.stroke();
              };
            } else if (status == "end") {
              _this.ctx.closePath();
              _this.step = _this.step + 1;
              if (_this.step < _this.canvasHistory.length - 1) {
                _this.canvasHistory.length = _this.step; // 截断数组
              }
              _this.canvasHistory.push(_this.canvas.toDataURL());
              _this.canvas.onmousemove = null;
            }
          },
          // 绘制矩形
          createR(e, status, r1, r2) {
            let _this = this;
            let r;
            if (status == "begin") {
              console.log("onmousemove");
              _this.canvas.onmousemove = function (e) {
                _this.reset();
                let rx = e.layerX - r1;
                let ry = e.layerY - r2;
     
                //保留之前绘画的图形
                if (_this.step !== 0) {
                  let canvasPic = new Image();
                  canvasPic.src = _this.canvasHistory[_this.step];
                  _this.ctx.drawImage(canvasPic, 0, 0);
                }
     
                _this.ctx.beginPath();
                _this.ctx.strokeRect(r1, r2, rx, ry);
                _this.ctx.strokeStyle = _this.strokeStyle;
                _this.ctx.lineWidth = _this.lineWidth;
                _this.ctx.closePath();
                _this.ctx.stroke();
              };
            } else if (status == "end") {
              _this.rebroadcast();
              let interval = setInterval(() => {
                if (_this.loading) {
                  clearInterval(interval);
                  _this.loading = false;
                } else {
                  return;
                }
                let rx = e.layerX - r1;
                let ry = e.layerY - r2;
                _this.ctx.beginPath();
                _this.ctx.rect(r1, r2, rx, ry);
                _this.ctx.strokeStyle = _this.strokeStyle;
                _this.ctx.lineWidth = _this.lineWidth;
                _this.ctx.closePath();
                _this.ctx.stroke();
                _this.step = _this.step + 1;
                if (_this.step < _this.canvasHistory.length - 1) {
                  _this.canvasHistory.length = _this.step; // 截断数组
                }
                _this.canvasHistory.push(_this.canvas.toDataURL());
                _this.canvas.onmousemove = null;
              }, 1);
            }
          },
     
          //绘制箭头
          drawArrow(e, status) {
            let _this = this;
            if (status == "begin") {
              //获取起始位置
              _this.arrowFromX = e.layerX;
              _this.arrowFromY = e.layerY;
              _this.ctx.beginPath();
              _this.ctx.moveTo(e.layerX, e.layerY);
            } else if (status == "end") {
              //计算箭头及画线
              let toX = e.layerX;
              let toY = e.layerY;
              let theta = 30;
              let headlen = 10;
              let _this = this;
              let fromX = this.arrowFromX;
              let fromY = this.arrowFromY;
              // 计算各角度和对应的P2,P3坐标
              let angle = Math.atan2(fromY - toY, fromX - toX) * 180 / Math.PI,
              angle1 = (angle + theta) * Math.PI / 180,
              angle2 = (angle - theta) * Math.PI / 180,
              topX = headlen * Math.cos(angle1),
              topY = headlen * Math.sin(angle1),
              botX = headlen * Math.cos(angle2),
              botY = headlen * Math.sin(angle2);
              let arrowX = fromX - topX,
              arrowY = fromY - topY;
              _this.ctx.moveTo(arrowX, arrowY);
              _this.ctx.moveTo(fromX, fromY);
              _this.ctx.lineTo(toX, toY);
              arrowX = toX + topX;
              arrowY = toY + topY;
              _this.ctx.moveTo(arrowX, arrowY);
              _this.ctx.lineTo(toX, toY);
              arrowX = toX + botX;
              arrowY = toY + botY;
              _this.ctx.lineTo(arrowX, arrowY);
              _this.ctx.strokeStyle = _this.strokeStyle;
              _this.ctx.lineWidth = _this.lineWidth;
              _this.ctx.stroke();
     
              _this.ctx.closePath();
              _this.step = _this.step + 1;
              if (_this.step < _this.canvasHistory.length - 1) {
                _this.canvasHistory.length = _this.step; // 截断数组
              }
              _this.canvasHistory.push(_this.canvas.toDataURL());
              _this.canvas.onmousemove = null;
            }
          },
     
          //文字输入
          createT(e, status) {
            let _this = this;
            if (status == "begin") {
     
            } else if (status == "end") {
              let offset = 0;
              if (_this.fontSize >= 28) {
                offset = (_this.fontSize / 2) - 3
              } else {
                offset = (_this.fontSize / 2) - 2
              }
     
              _this.ctxX = e.layerX + 2;
              _this.ctxY = e.layerY + offset;
     
              let index = this.getPointOnCanvas(e);
              _this.$refs.txt.style.left = index.x + 'px';
              _this.$refs.txt.style.top = index.y - (_this.fontSize / 2) + 'px';
              _this.$refs.txt.value = '';
              _this.$refs.txt.style.height = _this.fontSize + "px";
              _this.$refs.txt.style.width = _this.canvas.width - e.layerX - 1 + "px",
                _this.$refs.txt.style.fontSize = _this.fontSize + "px";
              _this.$refs.txt.style.fontFamily = _this.fontFamily;
              _this.$refs.txt.style.color = _this.fontColor;
              _this.$refs.txt.style.maxlength = Math.floor((_this.canvas.width - e.layerX) / _this.fontSize);
              _this.isShow = true;
              setTimeout(() => {
                _this.$refs.txt.focus();
              })
            }
          },
          //文字输入框失去光标时在画布上生成文字
          txtBlue() {
            let _this = this;
            let txt = _this.$refs.txt.value;
            if (txt) {
              _this.ctx.font = _this.$refs.txt.style.fontSize + ' ' + _this.$refs.txt.style.fontFamily;
              _this.ctx.fillStyle = _this.$refs.txt.style.color;
              _this.ctx.fillText(txt, _this.ctxX, _this.ctxY);
              _this.step = _this.step + 1;
              if (_this.step < _this.canvasHistory.length - 1) {
                _this.canvasHistory.length = _this.step; // 截断数组
              }
              _this.canvasHistory.push(_this.canvas.toDataURL());
              _this.canvas.onmousemove = null;
            }
          },
          //计算文字框定位位置
          getPointOnCanvas(e) {
            let cs = this.canvas;
            let content = document.getElementsByClassName("content")[0];
            return {
              x: e.layerX + (content.clientWidth - cs.width) / 2,
              y: e.layerY
            };
          },
          //清空文字
          resetTxt() {
            let _this = this;
            _this.$refs.txt.value = '';
            _this.isShow = false;
          }
        }
      };
    </script>
     
    <style lang="scss" scope>
      * {
        box-sizing: border-box;
      }
     
    
      .draw {
        height: 100%;
        min-width: 420px;
        display: flex;
        flex-direction: column;
      }
     
      .content {
        flex-grow: 1;
        height: 100%;
        width: 100%;
      }
     
      .drawTop {
        display: flex;
        justify-content: flex-start;
        align-items: center;
        padding: 5px;
        height: 52px;
      }
     
      .drawTop > div {
        display: flex;
        align-items: center;
        padding: 5px 5px;
      }
     
      div.drawTopContrllor {
        display: none;
      }
     
      @media screen and (max-width: 1200px) {
        .drawTop {
          position: absolute;
          background-color: white;
          width: 100%;
          flex-direction: column;
          align-items: flex-start;
          height: 30px;
          overflow: hidden;
        }
     
        .drawTopContrllor {
          display: flex !important;
          height: 30px;
          width: 100%;
          justify-content: center;
          align-items: center;
          padding: 0 !important;
        }
      }
    </style>

     

    展开全文
  • 基于关键字的自动图片标注方法,可以更为有效地实现海量图片的管理和检索。然而由于“语义鸿沟”问 题,传统的自动图片标注效果往往并不理想。因此,对不精确的标注结果进行优化就显得尤为重要。文中提出一 种新颖的...
  • 使用Matlab编写简单的图片标注程序

    千次阅读 2018-12-23 00:21:18
    使用Matlab编写简单的图片标注程序 最近在做数字图像处理大作业,本来想使用检测算法检测出图中车辆的位置,无奈水平有限,检测效果不佳,只能被迫使用matlab编写一个手动标注程序了。事实证明这真是项体力活,泪目...

    使用Matlab编写简单的图片标注程序

    最近在做数字图像处理大作业,本来想使用检测算法检测出图中车辆的位置,无奈水平有限,检测效果不佳,只能被迫使用matlab编写一个手动标注程序了。事实证明这真是项体力活,泪目。在学习的过程中CSDN确实帮助了我很多,第一次在这里写帖子,只希望能记录下自己的工作,和大家一起分享,共同进步。
    下面就简单介绍下自己编写的图片标注程序,不过在深度学习领域也有很多现有的软件可以完成标注,由于我需要标注的图片也不多,就懒得费心去找这些了,将就着用自己写的。

    读取图片

    读取图片部分就很简单啦,我就直接上代码:

    axes(handles.axes1);%图片显示在期望的坐标系
    [filename,pathname]=uigetfile({'*.bmp;*.jpg'},'Please Select an image',
    '(你想要读取图片的存放路径)');
    str=[pathname filename];
    if isequal(filename,0)||isequal(pathname,0)
        warndlg('Please select a picture first!','Warning');
        return;
    else
        im=imread(str);
        imshow(im);
    end
    

    注:掌握uigetfile函数的使用就很简单啦!

    标注图片

    使用鼠标框选的方式手动标注图片,部分代码如下:

    k1=waitforbuttonpress;
    point1=get(gca,'CurrentPoint');%获取鼠标左键点击的位置
    finalRect=rbbox;
    point2=get(gca,'CurrentPoint');%获取松开左键的点击位置
    point1=point1(1,1:2);
    point2=point2(1,1:2);
    p1 = min(floor(point1),floor(point2));
    p2 = max(floor(point1),floor(point2));
    offset = abs(floor(point1)-floor(point2)); % offset(1)表示宽,offset(2)表示高
    x = [p1(1) p1(1)+offset(1) p1(1)+offset(1) p1(1) p1(1)];
    y = [p1(2) p1(2) p1(2)+offset(2) p1(2)+offset(2) p1(2)];
    hold on %防止plot时闪烁
    plot(x,y,'w','LineWidth',2);%由获取的两点画矩形
    

    注:这部分也是借鉴了网上的示例,但找不到网址了,希望看到的源码位置的小伙伴记得告诉我,我会标注引用的!毕竟“版权意识”很重要。
    如果想在一张图片上标注多个区域,可以复制上述代码,记得改参数名就可以啦,每一段都是选取一个区域,以此类推。

    保存图片

    [FileName,PathName] = uiputfile({'*.bmp'},'Save Picture',
    '(标注后的图片保存路径)');
    if FileName==0
        return;
    else
        h=getframe(handles.axes1);
        %imwrite(imresize(rgb2gray(h.cdata),0.8),[PathName,FileName]);
        imwrite(rgb2gray(h.cdata),[PathName,FileName]);
    end;
    

    注:这里重点说一下getframe这个函数,被这个折磨不浅。matlab里对这个函数这样解释:Starting in R2015b, if you are using a high-resolution system, then the size of the cdata array in output F might be larger than in previous releases or on other systems. Also, the number of elements in cdata might not match the size in pixels reported by either the Position property of the figure or axes, or the rect input argument. For more information, see DPI-Aware Behavior in MATLAB.
    标注了几十张才发现图片大小被改变了,一查发现这个函数可能受设备分辨率影响在保存图片的时候会自动改变大小。在1920*1080的笔记本上标注时图片放大了1.25倍,笔记本外接显示器时标注的图片大小未发生改变,这一操作简直坑哭我了。所以使用的时候一定要看清楚和原图的尺寸,再使用resize函数就可以了。

    结果演示

    结果截图如下:
    在这里插入图片描述界面就比较直男审美,不过能用就行,我就是用这个含泪标注了400多张图片!

    结语

    第一次在CSDN上分享经验,虽然内容很简单,也借鉴了很多帖子的经验,但毕竟迈出了第一步,以后会不定期在这里记录自己的工作。以上内容如有侵权或者错误的地方,也望伙伴们不吝赐教。

    展开全文
  • vue下用canvas实现图片标注工具,允许图片放大、缩小,允许拖拽图片 效果图片 概述:       之前在js版本上实现过canvas标注工具,最近又拎出来,重新用vue来实现该标注功能,希望能...
    vue下用canvas实现图片标注工具,允许图片放大、缩小,允许拖拽图片

    效果图片
    在这里插入图片描述

    概述:

          之前在js版本上实现过canvas标注工具,最近又拎出来,重新用vue来实现该标注功能,希望能给刚入门的小白一个指引,让小白们能少走点弯路


    实现过程中遇见的问题:

    • canvas放置在html界面上
    • 图片如何加载到canvas中
    • 如何实现图片放大缩小
    • 如何实现图片拖拽
    • 如何实现图片框选功能

    注意点:代码中需要提前引入element-ui 的内容
    以下是实现代码(vue版):
    <template>
      <div class="divbody">
        <el-row class="toolsClass">
          <el-button @click="ifIsEditClick">
            {{ params.editFlag ? "可编辑" : "不可编辑" }}
          </el-button>
          <el-button @click="cleanCanvas">清除标记</el-button>
          <el-button @click="moveImageClick">{{
            params.moveImageFlag ? "可移动" : "不可移动"
          }}</el-button>
        </el-row>
    
        <!-- canvas 区域 -->
        <div class="imgContainer" ref="imgContainer">
          <canvas
            :ref="'refmyCanvas'"
            class="canvasClass"
            :width="divWidth"
            :height="divHeight"
            @mousedown="canvasMouseDown"
            @mouseup="canvasMouseUp"
            @mousemove="canvasMouseMove"
            @mousewheel="canvasMousewheel"
          >
          </canvas>
          <img
            :id="'image'"
            :src="imageSrc"
            :ref="'refimage'"
            class="imgClass"
            @load="uploadImgLoad"
            v-show="false"
          />
    
          <!-- 右侧列表区域 -->
          <div class="houseClass">
            <div
              v-for="(item, index) in nowDictlist"
              :key="item.id"
              class="fatherClass"
            >
              <div class="itemClass">
                <div>
                  <span @click="aeroItemClick">框选区域:</span>
                </div>
    
                <div>
                  <span>颜色:</span>
                  <span
                    :style="
                      'margin-left:8px;border:1px solid black;background-color:' +
                        item.color
                    "
                    >&ensp;&ensp;</span
                  >
                </div>
    
                <div>
                  <el-button
                    type="danger"
                    size="mini"
                    @click="removeHouseClick(index)"
                    >删除</el-button
                  >
                  <el-button type="primary" size="mini" @click="updateHuInfo()"
                    >提交</el-button
                  >
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    // 这里的路径之前写错了,目前已做修改
    export default {
      data() {
        return {
          imgPath: process.env.VUE_APP_BASE_API,
          maxMinStep: 20,
          msg: "图片标注拖拽测试",
          divWidth: 0,
          divHeight: 0,
          imageSrc: "http://localhost/mainPage.jpg",
          // canvas的配置部分
          c: "",
          cxt: "",
          canvasImg: "",
          //------------------------------
          imgScale: 1, // canvas 放大缩小倍数
    
          marginX: 0, // x轴偏移量, 图片专用
          marginY: 0, // x轴偏移量, 图片专用
          //------------------------------
          imgWidth: 0, // img框的宽度
          imgHeight: 0, // img框的高度
          targetMarkIndex: -1, // 目标标注index
          params: {
            currentX: 0,
            currentY: 0,
            flag: false, // 用来判断在canvas上是否有鼠标down的事件,
            editFlag: false,
            editIndex: -1,
            moveImageFlag: false,
            addHouseFlag: false,
            currentHouseIndex: -1
          },
          colorValue: undefined, // 所选中的颜色值
          //-------------------------
          // 当前正在标注的数据内容,因为只有一条,所以只定义一个
          nowDict: {
            x1: undefined,
            y1: undefined,
            x2: undefined,
            y2: undefined,
            //--------------------------
            // 拖拽需要用的标志位
            movex1: undefined,
            movey1: undefined,
            movex2: undefined,
            movey2: undefined,
            //--------------------------
            left: undefined, // x轴侧偏移量,相对于canvas上的值
            top: undefined, // y轴偏移量,相对于canvas上的值
    
            imgScale: 1, // 标注的时候放大缩小倍数
            isclick: false, // 区域是否被点击了
            // 原始图片上的位置
            originX1: undefined,
            originX2: undefined,
            originY1: undefined,
            originY2: undefined,
    
            color: undefined // 上颜色用的的
          },
          ConstKx: undefined,
          ConstKy: undefined,
          nowDictlist: [],
          detailInfor: {},
          detailVisible: false
          //------------------------------
        };
      },
      created() {},
      beforeMount() {
        this.canvasOnDraw(this.imgWidth, this.imgHeight);
      },
      mounted() {
        // 这里是进行初始化canvas的操作 myCanvas
        const self = this;
        try {
          self.c = self.$refs.refmyCanvas;
          self.canvasImg = self.$refs.refimage;
          self.cxt = self.c.getContext("2d");
    
          self.divWidth = self.$refs.imgContainer.offsetWidth;
          self.divHeight = self.$refs.imgContainer.offsetHeight;
        } catch (err) {
          console.log(err, "====");
        }
        // 渲染已经标注的矩形区域
        this.canvasOnDraw(this.imgWidth, this.imgHeight);
      },
      beforeUpdate() {
        // 渲染已经标注的矩形区域
        this.canvasOnDraw(this.imgWidth, this.imgHeight);
      },
      updated() {
        // 渲染已经标注的矩形区域
        this.canvasOnDraw(this.imgWidth, this.imgHeight);
      },
      methods: {
        removeHouseClick(index) {
          this.nowDictlist = [];
          this.nowDict = {};
          // 重新渲染界面上的元素
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        aeroItemClick() {
          this.nowDict.isClick = true;
          // 执行渲染操作
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        // 是否允许图片拖动
        moveImageClick() {
          this.params.moveImageFlag = !this.params.moveImageFlag;
          if (this.params.moveImageFlag) {
            this.params.editFlag = false;
            this.params.addHouseFlag = false;
          }
        },
        // 清除canvas中的内容
        cleanCanvas() {
          this.nowDictlist = [];
          // 重置标志内容
          this.nowDict = {
            x1: undefined,
            y1: undefined,
            x2: undefined,
            y2: undefined,
            //--------------------------
            // 拖拽需要用的标志位
            movex1: undefined,
            movey1: undefined,
            movex2: undefined,
            movey2: undefined,
            //--------------------------
            left: undefined, // x轴侧偏移量,相对于canvas上的值
            top: undefined, // y轴偏移量,相对于canvas上的值
    
            imgScale: 1, // 标注的时候放大缩小倍数
            isclick: false, // 区域是否被点击了
            // 原始图片上的位置
            originX1: undefined,
            originX2: undefined,
            originY1: undefined,
            originY2: undefined,
    
            color: undefined // 上颜色用的的
          };
          // 重置canvas偏移量
          this.marginX = 0;
          this.marginY = 0;
          // 重置放大缩小倍数
          this.imgScale = 1;
    
          // 重置其他按钮状态
          this.params.editFlag = false;
          this.params.moveImageFlag = false;
          this.params.addHouseFlag = false;
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        // 是否允许进行标注
        ifIsEditClick() {
          // 判断一下,只允许标注一条数据,不能乱来
          if (this.targetMarkIndex >= 0) {
            this.params.editFlag = false;
            return;
          } else {
            this.params.editFlag = !this.params.editFlag;
            if (this.params.editFlag) {
              this.params.moveImageFlag = false;
            }
          }
        },
        // 由于使用体验不太好,这段代码暂时不启用
        canvasMousewheel(e) {
          var data = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
          if (data != -1) {
            this.wheelMax();
          } else {
            this.wheelMin();
          }
        },
        // 鼠标滚轮函数
        wheelMax() {
          this.imgScale += 0.01 * this.maxMinStep;
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        wheelMin() {
          this.imgScale -= 0.02 * this.maxMinStep;
          if (this.imgScale <= 1) {
            // 改进后的 非常好的体验
            this.imgScale = 1;
            this.marginX = 0;
            this.marginY = 0;
          }
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        // 根据canvas值,获取原始图片上的坐标值
        getOriginWHValue(nowX, nowY, nowMoveX, nowMoveY, fuckingScale) {
          // 判断一下偏移的方向,x方向负数,代表应该加,正值代表,应该减掉
          if (nowMoveX <= 0) {
            // 应该做加号运算
            nowMoveX = Math.abs(nowMoveX);
            // 坐标值变成1倍的情况处理
            nowX = (nowX + nowMoveX) / fuckingScale;
          } else {
            // 应该做减法运算
            nowX = (nowX - nowMoveX) / fuckingScale;
          }
          // 判断一下偏移的方向,y方向,负数,应该加;正数,应该减法
          if (nowMoveY <= 0) {
            // 应该做加法运算,先取绝对值
            nowMoveY = Math.abs(nowMoveY);
            // 坐标值变成1倍的情况处理
            nowY = (nowY + nowMoveY) / fuckingScale;
          } else {
            // 这里应该做减法运算,然后处理成1倍情况
            nowY = (nowY - nowMoveY) / fuckingScale;
          }
    
          // 获取现有canvas加载的图片大小,1倍情况 [tmpW, tmpH]
          var resPointList = this.changeOldPointToNewPoint(
            this.imgWidth,
            this.imgHeight,
            this.divWidth,
            this.divHeight
          );
          // 现有图片宽度,canvas上的
          var tmpW = resPointList[0];
          var tmpH = resPointList[1];
          // canvas x * kx = 原图 x
          var kx = this.imgWidth / tmpW;
          // canvas y * ky = 原图 y
          var ky = this.imgHeight / tmpH;
          // 全局Canvas操作,后需要用,
          this.ConstCanvasW = tmpW;
          this.ConstCanvasH = tmpH;
    
          this.ConstKx = kx;
          this.ConstKy = ky;
          return [nowX * kx, nowY * ky];
        },
        // 鼠标down事件
        canvasMouseDown(e) {
          // 代表鼠标有down的动作
          this.params.flag = true;
          if (!e) {
            e = window.event;
            // 防止IE文字选中
            this.$refs.refmyCanvas.onselectstart = function() {
              return false;
            };
          }
    
          // 这里先判断一下,看是否是在有效数据,并且初始化参数, 允许编辑
          if (this.params.flag === true && this.params.editFlag === true) {
            this.nowDict.imgScale = this.imgScale;
            // 记录canvas上的xy值
            this.nowDict.x1 = e.offsetX;
            this.nowDict.y1 = e.offsetY;
            this.nowDict.x2 = e.offsetX;
            this.nowDict.y2 = e.offsetY;
    
            // 把颜色值取下来
            this.nowDict.color = "#ff000057";
    
            // 计算出原始图片上的x y 值
            var result1 = this.getOriginWHValue(
              this.nowDict.x1,
              this.nowDict.y1,
              // 偏移值
              this.marginX,
              this.marginY,
              this.imgScale
            );
            var result2 = this.getOriginWHValue(
              this.nowDict.x2,
              this.nowDict.y2,
              // 偏移值
              this.marginX,
              this.marginY,
              this.imgScale
            );
            // 把原图坐标写回字典中
            this.nowDict.originX1 = result1[0];
            this.nowDict.originY1 = result1[1];
            this.nowDict.originX2 = result2[0];
            this.nowDict.originY2 = result2[1];
    
            this.nowDictlist = [];
            this.nowDictlist.push(this.nowDict);
            // // 执行渲染操作
            this.canvasOnDraw(this.imgWidth, this.imgHeight);
          } else {
            // 允许拖拽图片
            if (this.params.moveImageFlag === true && this.params.flag === true) {
              // 首次拖拽,记住位置
              this.nowDict.movex1 = e.offsetX;
              this.nowDict.movey1 = e.offsetY;
              this.nowDict.movex2 = e.offsetX;
              this.nowDict.movey2 = e.offsetY;
              // 执行渲染操作
              this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } else {
              // 这里是被点击的情况,额外进行处理,也是牛皮了
              var tmp_x = e.offsetX;
              var tmp_y = e.offsetY;
              // 这里其实也得判断一下,当前自己勾选的矩形有没有被点击
              var xx1 =
                (this.nowDict.originX1 / this.ConstKx) * this.imgScale +
                this.marginX; // 这里应该加上一个偏移值
              var yy1 =
                (this.nowDict.originY1 / this.ConstKy) * this.imgScale +
                this.marginY; // 这里应该加上一个偏移值
    
              var xx2 =
                (this.nowDict.originX2 / this.ConstKx) * this.imgScale +
                this.marginX; // 这里应该加上一个偏移值
              var yy2 =
                (this.nowDict.originY2 / this.ConstKy) * this.imgScale +
                this.marginY; // 这里应该加上一个偏移值
    
              // 调整两个点位,找出左上角顶点
              var FinalPointListNow = this.findWhichIsFirstPoint(
                xx1,
                yy1,
                xx2,
                yy2
              );
              xx1 = FinalPointListNow[0];
              yy1 = FinalPointListNow[1];
              xx2 = FinalPointListNow[2];
              yy2 = FinalPointListNow[3];
              // 说明点位在矩形之内
              if (xx1 <= tmp_x && tmp_x <= xx2) {
                if (yy1 <= tmp_y && tmp_y <= yy2) {
                  this.nowDict.isClick = true;
                  // 这里需要判断一下huid是否为空,若不为空,咋调用一下父元素的方法
                  if (this.nowDict.huId) {
                    // huid存在,调用父元素方法
                    this.fatherDetailMethod(this.nowDict.huId);
                  }
                } else {
                  this.nowDict.isClick = false;
                }
              } else {
                this.nowDict.isClick = false;
              }
              // 执行渲染操作
              this.canvasOnDraw(this.imgWidth, this.imgHeight);
            }
          }
        },
        // 鼠标移动
        canvasMouseMove(e) {
          if (e === null) {
            e = window.event;
          }
          // 这里是正在标注的情况
          if (this.params.flag === true && this.params.editFlag === true) {
            this.nowDict.x2 = e.offsetX;
            this.nowDict.y2 = e.offsetY;
    
            var result2 = this.getOriginWHValue(
              this.nowDict.x2,
              this.nowDict.y2,
              // 偏移值
              this.marginX,
              this.marginY,
              this.imgScale
            );
            // 把原图坐标写回字典中
            this.nowDict.originX2 = result2[0];
            this.nowDict.originY2 = result2[1];
            // 执行渲染操作
            this.canvasOnDraw(this.imgWidth, this.imgHeight);
          } else {
            // 这里是 允许拖拽图片
            if (this.params.moveImageFlag && this.params.flag) {
              this.marginX = e.offsetX - this.nowDict.movex1 + this.marginX;
              this.marginY = e.offsetY - this.nowDict.movey1 + this.marginY;
              this.nowDict.movex1 = e.offsetX;
              this.nowDict.movey1 = e.offsetY;
    
              // 这里其实得做个优化,不能超出边缘
              if (this.marginY < 0) {
                if (Math.abs(this.marginY) >= this.c.height * this.imgScale - 300) {
                  this.marginY = (this.c.height * this.imgScale - 300) * -1;
                }
              } else {
                // 说明是大于0 的
                if (this.marginY >= this.c.height - 300) {
                  this.marginY = this.c.height - 300;
                }
              }
    
              if (this.marginX < 0) {
                if (Math.abs(this.marginX) >= this.c.width * this.imgScale - 300) {
                  this.marginX = (this.c.width * this.imgScale - 300) * -1;
                }
              } else {
                if (this.marginX >= this.c.width - 300) {
                  this.marginX = this.c.width - 300;
                }
              }
              // 执行渲染操作
              this.canvasOnDraw(this.imgWidth, this.imgHeight);
            }
          }
        },
        // 鼠标抬起
        canvasMouseUp(e) {
          // 当正在编辑的标志位为true时,需要传回数据
          if (this.params.editFlag === true) {
            // 把数据传回父组件,保存用的
            this.$emit("mapPointJson", JSON.stringify(this.nowDict));
          }
          this.params.flag = false;
          this.params.editFlag = false;
          // 停止区域标注
          this.params.addHouseFlag = false;
          // 重新渲染界面
          this.canvasOnDraw(this.imgWidth, this.imgHeight);
        },
        // 加载图片用的
        uploadImgLoad(e) {
          try {
            this.imgWidth = e.path[0].naturalWidth;
            this.imgHeight = e.path[0].naturalHeight;
            this.canvasOnDraw(this.imgWidth, this.imgHeight);
          } catch (err) {
            console.log(err, " img==s");
          }
        },
        // 输入两个坐标值,判断哪个坐标值离左上角最近,其中特殊情况需要进行坐标查找工作
        findWhichIsFirstPoint(x1, y1, x2, y2) {
          // 首先判断x轴的距离谁更近
          if (x1 <= x2) {
            // 说明x1 比较小,接下来判断y谁更近
            if (y1 <= y2) {
              // 说明第一个坐标离得更近,直接顺序return就好
              return [x1, y1, x2, y2];
            } else {
              // 这里遇见一个奇葩问题,需要进行顶角变换
              return [x1, y2, x2, y1];
            }
          } else {
            // 这里是x1 大于 x2 的情况
            if (y2 <= y1) {
              return [x2, y2, x1, y1];
            } else {
              // y2 大于 y1 的情况, 这里需要做顶角变换工作
              return [x2, y1, x1, y2];
            }
          }
        },
        // can vas绘图部分
        canvasOnDraw(imgW = this.imgWidth, imgH = this.imgHeight) {
          const imgWidth = imgW;
          const imgHeight = imgH;
          try {
            this.divWidth = this.$refs.imgContainer.offsetWidth;
            this.divHeight = this.$refs.imgContainer.offsetHeight;
          } catch (err) {
            return;
          }
          // 清除canvas内容
          this.cxt.clearRect(0, 0, imgWidth, imgHeight);
          // 当前的图片和现有的canvas容器之前的一个关系,是否有必要,我们后续做讨论
          var resPointList = this.changeOldPointToNewPoint(
            imgWidth,
            imgHeight,
            this.divWidth,
            this.divHeight
          );
          // 这里在加载图片之类的
          this.cxt.drawImage(
            this.canvasImg,
    
            0,
            0,
            imgWidth,
            imgHeight,
    
            this.marginX, // canvas 上的 x 偏移量
            this.marginY, // canvas 上的 y坐标位置 偏移量。
    
            resPointList[0] * this.imgScale, // width, img图像放大后的宽度
            resPointList[1] * this.imgScale // height, img图像放大后的宽度
          );
          //-------------------------------------
          // 这里在画矩形框 , 坐标值变成1倍情况,然后变成现有倍数,注意,处理过程中,需要减掉偏移量
          var x1 =
            (this.nowDict.originX1 / this.ConstKx) * this.imgScale + this.marginX; // 这里应该加上一个偏移值
          var y1 =
            (this.nowDict.originY1 / this.ConstKy) * this.imgScale + this.marginY; // 这里应该加上一个偏移值
    
          var x2 =
            (this.nowDict.originX2 / this.ConstKx) * this.imgScale + this.marginX; // 这里应该加上一个偏移值
          var y2 =
            (this.nowDict.originY2 / this.ConstKy) * this.imgScale + this.marginY; // 这里应该加上一个偏移值
    
          // 2个顶点转换函数
          const FinalPointList = this.findWhichIsFirstPoint(x1, y1, x2, y2);
          // 重新配置两个顶点数据 x1变成左上角顶点,x2 变成右下角顶点
          x1 = FinalPointList[0];
          y1 = FinalPointList[1];
          x2 = FinalPointList[2];
          y2 = FinalPointList[3];
    
          var wid = x2 - x1;
          var hei = y2 - y1;
          // 绘制矩形
          this.cxt.strokeRect(x1, y1, x2 - x1, y2 - y1);
          this.cxt.font = "16px Arial";
    
          // 被点击的情况
          if (this.nowDict.isClick) {
            // 这里是在处理高亮的地方
            this.cxt.fillStyle = "rgba(255, 0, 0, 0.1)";
            // 线条颜色
            this.canvasDrowBorder("#000000", x1, y1, wid, hei);
            // 内容填充,高亮内容
            this.canvasDrowInnerColor("rgba(200, 0, 0, 0.3)", x1, y1, wid, hei);
          } else {
            // 没有被点击的情况
            this.cxt.fillStyle = this.nowDict.color
              ? this.nowDict.color
              : "#ffffff57";
            // 线条颜色
            this.canvasDrowBorder("#000000", x1, y1, wid, hei);
            // 内容填充颜色
            this.canvasDrowInnerColor(this.nowDict.color, x1, y1, wid, hei);
          }
          this.cxt.fillText("标题->", x1, parseInt(y1) - 6);
        },
        // canvas框选区域的内容颜色
        canvasDrowInnerColor(color, x, y, w, h) {
          this.cxt.fillStyle = color;
          this.cxt.fillRect(x, y, w, h);
        },
        // canvas框选区域的边框颜色
        canvasDrowBorder(color, x, y, w, h) {
          this.cxt.strokeStyle = color;
          this.cxt.strokeRect(x, y, w, h);
        },
        // 尺寸变换函数
        changeOldPointToNewPoint(imgw, imgH, canvasW, canvasH) {
          // 这里有个要求,先以宽度为准,然后再一步步调整高度
          var tmpW = canvasW;
          var tmpH = (tmpW * imgH) / imgw;
          // 如果转换之后的高度正好小于框的高度,则直接进行显示
          if (tmpH <= canvasH) {
            // 尺寸完美匹配
            return [tmpW, tmpH];
          } else {
            // 高度超出框了,需要重新调整高度部分
            tmpW = canvasW;
            tmpH = (tmpW * imgH) / imgw;
            var count = 1;
            var raise = 0.05;
            while (tmpH > canvasH || tmpW > canvasW) {
              tmpW = tmpW * (1 - raise * count);
              tmpH = (tmpW * imgH) / imgw;
            }
            return [tmpW, tmpH];
          }
        }
      }
    };
    </script>
    <style lang="scss" scoped>
    .divbody {
      width: 100%;
      height: 100%;
    }
    
    .imgContainer {
      position: relative;
      /* width: 100vw; */
      width: 90vw;
      height: 88vh;
    }
    
    .canvasClass {
      position: absolute;
      width: auto;
      height: auto;
      max-width: 100%;
      max-height: 100%;
      border: 1px solid black;
      background-color: black;
    }
    
    .imgClass {
      width: auto;
      height: auto;
      max-width: 100%;
      max-height: 100%;
    }
    
    .toolsClass {
      position: absolute;
      z-index: 999;
      padding: 8px 8px;
      background-color: #ffffffc1;
    }
    
    .houseClass {
      position: absolute;
      float: right;
      width: 200px;
      height: 100px;
      right: 0px;
      top: 200px;
      z-index: 999;
      background-color: white;
      border: 1px solid;
      overflow: auto;
    }
    
    .fatherClass {
      height: 10%;
      width: calc(100% - 16px);
      margin: 8px;
    }
    
    .itemClass {
      cursor: pointer;
      width: calc(100% - 0px);
    }
    
    .detail {
      width: 100%;
      // display: flex;
      flex-wrap: wrap;
      max-height: 650px;
      padding: 0 20px;
      .info-title {
        line-height: 30px;
        background-color: #f5f7fa;
        padding: 5px 10px;
        margin: 10px 0;
        font-weight: 700;
        span {
          display: inline-block;
          &::before {
            display: inline-block;
            content: "|";
            line-height: 18px;
            border-radius: 5px;
            width: 5px;
            background-color: #2161fb;
          }
        }
      }
      .detail_item_infor {
        // width:33%;
        display: flex;
        flex-wrap: nowrap;
        align-items: flex-start;
        padding: 8px 0;
        .title_l {
          text-align: left;
          color: #333;
          align-items: center;
          font-weight: 700;
          margin-right: 10px;
        }
        .infor {
          flex: 1;
          align-items: center;
        }
      }
      .line {
        border-bottom: 1px dashed #d9d9d9;
        margin-bottom: 10px;
        height: 2px;
        width: 100%;
      }
      .active {
        background: #f8f8f8;
      }
    }
    </style>
    
    
    展开全文
  • 业务需求:对图片有水印的位置进行记录。开发过程,在网上也找了几个标注插件,但是都不满足业务需求,我只能根据cropper这个裁切图片的插件进行二次改造,所幸,改成造好了
  • 不同于 iOS 平台上图片标注类应用百花齐放的局面,Android 平台上「能用」的图片标注应用少之又少,功能全面、体验优秀的更是凤毛麟角。 因此如果你也在苦寻一款好用、高效且功能丰富,支持高亮、打码、笔刷甚至...
  • 使用labelme进行图片标注

    千次阅读 2020-09-27 20:51:23
    使用之前需要先使用Anaconda安装labelme,并且从命令行输入labelme打开其操作界面 打开文件和文件夹 在界面左上角,可以打开一个文件open 或者一个文件目录open dir, ...打开目录时可以在右下角选择相应图片 ...
  • 图片标注工具Labelme-简明使用教程

    千次阅读 2021-01-06 14:25:07
    LabelMe 可用于实例分割,语义分割,目标检测,分类任务的数据集标注工作。 在线标注版本 python版本 labelme官方文档 分类标注:Classification 目标检测标注:Object Detection 语义分割标注:Semantic ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 100,128
精华内容 40,051
关键字:

图片标注