在离不开手机的时代,快捷的记录简化为了轻轻按下屏幕边的一两个自定义按钮:截图。在这种全新记载手段的驯化下,我们渐渐降低了笔记和备忘录的使用频率,取而代之的,是让数字化的信息以图片的方式保存了下来,虽然这样的方法非常方便快捷,但同样,令查找信息,变得更加困难。

曾几何时,我设想过这样的一个应用:只需要输入关键字,就能获取包含对应关键字的截图和包含关键字形容的图片作为结果(图片识物);对于拥有大量图文素材的创作者来说,这无疑是提高效率的好方法。可能是由于这类功能太过于定制化,所以当今国内互联网上并没有类似的toC应用,最多也只是像百度网盘那种比较粗糙的归类,而不是逐一细化的打标签,普通人能用到的类似的功能只有谷歌和百度的搜图,但这种面向大众的业务,其能力也还是比较局限,稍有不慎就会被发律师函……咳咳,所以,何尝不试试创造一个个人图片搜索引擎呢?对于开发者而言,我们能利用更多的专业知识做到普通人做不到的事情,也正是这样的尝试,能够让普罗大众获得更多的便利。那么事不宜迟,咱们这就去整一个!

和普通文字搜索的逻辑一样,图片搜索的关键在于准确判断出每张图片的内容并打上标签,这样我们在使用文字搜索的时候就能通过语义上的关联查找到符合条件的图片;如何让电脑像人类一样识别图片上的信息呢?这就不得不和大家介绍一种人工智能技术了:光学字符识别,简称ocr,它能够根据图片的点阵布局来判断某个区域包含了哪些具体的字符;其实这类技术很早就出现了,比如十多年前的汉王,紫光等等,但早期的技术只能识别特定排版和字体组成的图片,最终还是架不住字形的多样性而陷入停滞;得益于近些年人工智能、深度学习领域的高速发展,ocr技术又在阿里、腾讯、百度等大厂的折腾之下发生了质的飞跃,识别率和识别速度都提升了不少。

其实正常来说,我们不仅仅需要依靠ocr一种技术来打标签,还需要一种「智能识物」的人工智能技术,它能智能判断图片的场景,和其中出现的人与物;但同和君目前还没有找到一种可量化的标准来衡量各平台的识别准确率,所以本期视频就暂且拿可量化的ocr来试试水吧~

开发的第一步,就是要找到识别率最高的平台,再利用他们的接口为我们的图片打标签。虽说是有免费的软件诸如谷歌家的tesseract,又或者自己通过训练也可以获得具备一定识别能力的模型,但毕竟人家大厂的实力摆在那里,而且啊,用脑子想一想,不充钱你会变得更强吗?所以为了最终的实用度,还是花点钱买服务吧。

在这期视频中,同和收集了四家提供ocr服务的云计算平台来横向对比,分别是腾讯云、阿里云、讯飞云和百度云。从数据库中挑选100张包含各类字符和排版各异的图片作为数据集,将识别的结果写入数据库,再通过人肉手打的方式添加验证集,之后用结巴分词等数据清洗算法从验证集获取图片的真实标签,接着和识别结果作对比,就能计算出真实识别率了。

对于这个结果我一开始也有点难以接受,没想到寄以厚望的阿里云识别率竟然这么低……反倒是风评不佳的百度,识别率以微弱的优势超越腾讯云位列第一,emmm,大概这就是All in AI 的结果吧~当然,仅仅关注加权平均数并不是统计学中的优良作风;我们通过降序排列各平台的识别结果,可以发现,每个平台对于不同种类的图片,其识别结果各有千秋,比如阿里就比较善于从模糊的图片中准确地提取文字,腾讯对于截图类图片有着几乎完美的识别率,而且对各种类型的图片的识别率都在90%以上,稳定性非常高;而百度呢?除了对过长的截图和背景不太清晰的图片不太敏感以外,基本没什么瑕疵……

好的,带大家领略过各个平台真实的水平之后,接下来我们就要讨论性价比这个话题了,但感觉这个话题也没什么讨论的必要……

综上所述,我们应该根据识别图片对象的特点,和对应平台的性价比,来综合选购云产品,当然如果你并不在乎开销,而仅仅追求最极致的搜图体验,那么你甚至可以多挑几家分别识别一遍,然后综合匹配多个识别结果来做查询。

 

前端代码:

<template>
  <div class="app-container" @keyup.right="handleRightUpdate" @keyup.left="handleLeftUpdate">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="图片类型" prop="picType">
        <el-select v-model="queryParams.picType" placeholder="请选择图片类型" clearable size="small" value="">
          <el-option
            v-for="dict in picTypeOptions"
            :key="dict.dictValue"
            :label="dict.dictLabel"
            :value="dict.dictValue"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="图片信息" prop="picContain">
        <el-input
          v-model="queryParams.picContain"
          placeholder="请输入图片信息"
          clearable=""
          size="small"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="warning"
          icon="el-icon-download"
          size="mini"
          :disabled="multiple"
          @click="handleExport"
          v-hasPermi="['business:pic_search:export']"
        >导出</el-button>
      </el-col>
     <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <el-table v-loading="loading" :data="pic_searchList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="图片id" align="center" prop="picId" />
      <el-table-column label="图片名称" align="center" prop="picName" />
      <el-table-column label="图片缩略图" align="center" prop="picName">
        <template slot-scope="scope">
          <img :src="pic_url + scope.row.picName" alt="" class="pic-in-list">
        </template>
      </el-table-column>
      <el-table-column label="图片大小" align="center" prop="picLength" />
      <el-table-column label="图片类型" align="center" prop="picType" :formatter="picTypeFormat" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['business:pic_search:edit']"
          >查看</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['business:pic_search:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改图片搜索对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body="">
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="图片信息" prop="picContain">
          <el-input v-model="form.picContain" class="pic_search_result" type="textarea" placeholder="请输入图片信息" :disabled="is_unlock_modify"/>
        </el-form-item>
        <el-form-item label="图片展示" prop="picName">
          <img :src="pic_url + form.picName" alt="" class="pic-in-dialog">
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleLeftUpdate">上一张</el-button>
        <el-button type="primary" @click="handleRightUpdate">下一张</el-button>
        <el-button v-if="is_unlock_modify === true" type="warning" @click="unlockModify">修 改</el-button>
        <el-button v-else type="success" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listPic_search, getPic_search, delPic_search, addPic_search, updatePic_search, exportPic_search } from "@/api/business/pic_search";

export default {
  name: "Pic_search",
  data() {
    return {
      // 路径
      pic_url: process.env.VUE_APP_BASE_API + "/profile/pic_search/",
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 图片搜索表格数据
      pic_searchList: [],
      // 图片id列表
      pic_id_list: [],
      // 选中图片的id
      temp_local_id: null,
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 是否解鎖修改按鈕
      is_unlock_modify: true,
      // 图片类型字典
      picTypeOptions: [],
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        picType: null,
        picContain: null
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
      }
    };
  },
  created() {
    console.log("test");
    this.getList();
    this.getDicts("bus_pic_type").then(response => {
      this.picTypeOptions = response.data;
    });
  },
  methods: {
    /** 查询图片搜索列表 */
    getList() {
      this.loading = true;
      this.pic_id_list = [];
      listPic_search(this.queryParams).then(response => {
        this.pic_searchList = response.rows;
        for (const i in this.pic_searchList) {
          if(!this.pic_searchList.hasOwnProperty(i))
            continue;
          this.pic_id_list.push(this.pic_searchList[i]["picId"])
        }
        this.total = response.total;
        this.loading = false;
      });
    },
    // 图片类型字典翻译
    picTypeFormat(row) {
      return this.selectDictLabel(this.picTypeOptions, row.picType);
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.is_unlock_modify = true;
    },
    // 表单重置
    reset() {
      this.form = {
        picName: null,
        picId: null,
        picUrl: null,
        picMd5: null,
        picLength: null,
        picType: null,
        picContain: null
      };
      this.resetForm("form");
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** 多选框选中数据 */
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.picId);
      this.single = selection.length!==1;
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加图片搜索";
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      const picId = row.picId || this.ids;
      this.temp_local_id = picId;
      getPic_search(picId).then(response => {
        this.form = response.data;
        this.open = true;
        this.title = "修改图片搜索";
      });
    },
    /** 按下键盘右键的操作 */
    handleRightUpdate() {
      const temp_located = this.pic_id_list.indexOf(this.temp_local_id);
      if (temp_located<this.pic_id_list.length-1) {
        this.temp_local_id = this.pic_id_list[temp_located+1];
        getPic_search(this.temp_local_id).then(response => {
          this.form = response.data;
          this.is_unlock_modify = true;
        });
      }
    },
    handleLeftUpdate() {
      const temp_located = this.pic_id_list.indexOf(this.temp_local_id);
      if (temp_located>0) {
        this.temp_local_id = this.pic_id_list[temp_located-1];
        getPic_search(this.temp_local_id).then(response => {
          this.form = response.data;
          this.is_unlock_modify = true;
        });
      }
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.picId != null) {
            updatePic_search(this.form).then(response => {
              if (response.code === 200) {
                this.msgSuccess("修改成功");
                this.open = false;
                this.is_unlock_modify = true;
                this.getList();
              }
            });
          } else {
            addPic_search(this.form).then(response => {
              if (response.code === 200) {
                this.msgSuccess("新增成功");
                this.open = false;
                this.is_unlock_modify = true;
                this.getList();
              }
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const picIds = row.picId || this.ids;
      this.$confirm('是否确认删除图片搜索编号为"' + picIds + '"的数据项?', "警告", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
        }).then(function() {
          return delPic_search(picIds);
        }).then(() => {
          this.getList();
          this.msgSuccess("删除成功");
        }).catch(function() {});
    },
    /** 导出按钮操作 */
    handleExport() {
      const queryParams = this.queryParams;
      this.$confirm('是否确认导出所有图片搜索数据项?', "警告", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
        }).then(function() {
          return exportPic_search(queryParams);
        }).then(response => {
          this.download(response.msg);
        }).catch(function() {});
    },
    unlockModify() {
      this.is_unlock_modify = false;
    }
  }
};
</script>

相关图片识别&识别结果表文件:

(等我下班回去发)

 


人生有無數種可能,人生有無限的精彩,人生沒有盡頭。一個人只要足夠的愛自己,尊重自己內心的聲音,就算是真正的活著。