这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

业务背景

由于当前项目中需要实现身份证拍照识别的功能,如果是小程序可以使用微信提供的 ocr-navigator 插件实现,但是在企业微信的H5中没有提供该插件,所以需要手动实现该功能。

需求分析及资料查阅

众所周知,前端H5中浏览器打开相机打开的是原生相机,无法在相机的界面上覆盖自定义的元素,比如实现类似下面的UI界面,无法使用相机拍照功能来直接实现,所以只能另辟蹊径。

  • 通过查阅资料发现,可以通过MediaDevices.getUserMedia()来实现媒体流的输出,这时可以在页面中添加video元素,然后把stream流的值赋值给video的srcObject属性,就可以把video输出到页面上,这样就可以在video元素上面添加自定义元素,实现UI效果。

  • 还需要解决的问题是:如何点击下面的拍照按钮时把获取画面转换成图片,并调用Api实现图片识别功能。 此时需要使用canvas来实现。通过canvas将video视频的当前帧绘制到画布上,然后将其转换成图片,然后调用接口来实现身份证识别。

snapPhoto() {
const canvas = document.querySelector("#mycanvas");
canvas.width = this.video.videoWidth;
canvas.height = this.video.videoHeight;
canvas.getContext("2d").drawImage(this.video, 0, 0);
const imageBase64 = canvas.toDataURL("image/png", 0.6);
return imageBase64
}

需求实现

话不多说,直接上代码(注意:该页面代码 vue-cli3 + vue2 + vant + 企业微信环境)

<template>
<div class="ocr-id-card">
<div id="cover" class="cover">
<div class="id-card-container"></div>
<video ref="videoRef" class="media-video" autoplay playsinline></video>
</div>
<div class="footer-tip font-24 radius-32 color-fff flex-center">请将证件放于框内拍摄</div>
<div class="footer-btn">
<div class="album" @click="chooseLocalImage">
<img src="@/assets/parttime-operator/album.png" alt="" class="album-img width-68 height-68" />
</div>
<div id="snap" class="record-btn" @click="snapPhoto"></div>
</div> <canvas id="mycanvas" class="card-canvas"></canvas>
</div>
</template>
<script>
import { uploadFileApi, idCardOcrApi } from "@/apis/common";
import { base64URLToFile } from "@/utils/base64-to-img";
export default {
data() {
return {
image_url: "", // 身份证url
imageBase64: "", // 身份证照片 base64
cardSide: "FRONT", // 身份证正反面 FRONT:身份证有照片的一面(人像面)BACK:身份证有国徽的一面(国徽面
video: {},
videoTrack: {}
};
},
mounted() {
const { cardSide } = this.$route.query;
this.cardSide = cardSide;
this.watchPageVisible();
},
beforeRouteLeave(to, from, next) {
if (this.videoTrack) {
this.videoTrack.stop();
}
next();
},
methods: {
// 调用摄像头
openCamera() {
// constraints: 指定请求的媒体类型和相对应的参数
const constraints = {
audio: false,
video: {
width: 1150,
height: 768,
frameRate: { ideal: 60 }, // 视频流帧率
facingMode: "environment" // 后置摄像头
}
};
// 兼容部分浏览器
if (!navigator.mediaDevices) navigator.mediaDevices = {};
// 一些浏览器部分支持 mediaDevices,不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性,只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// 首先,如果有getUserMedia的话,就获得它
const getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.oGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error("getUserMedia is not implemented in this browser"));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
// 获取视频流
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
this.videoTrack = stream.getVideoTracks()[0];
this.video = document.querySelector(".media-video");
if (this.video) {
this.video.srcObject = stream;
this.video.onloadedmetadata = () => {
this.video.play();
};
}
})
.catch(function(err) {
console.error(err);
});
},
// 监控页面visibilitychange
watchPageVisible() {
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
this.openCamera();
} else {
if (this.video && this.video.srcObject) {
this.video.srcObject.getTracks().forEach(track => track.stop());
}
}
});
},
// 获取视频的一帧作为图片转换为base64,调用接口识别身份证信息
snapPhoto() {
const canvas = document.querySelector("#mycanvas");
canvas.width = this.video.videoWidth * 0.9;
canvas.height = this.video.videoHeight * 0.9;
canvas.getContext("2d").drawImage(this.video, 0, 0);
const imageBase64 = canvas.toDataURL("image/png", 0.6);
this.idCardRecognition(imageBase64);
},
// 身份证照片识别
async idCardRecognition(imageBase64) {
try {
this.$toast.loading({
duration: 0, // 持续展示
message: "识别中...",
forbidClick: true,
loadingType: "spinner"
});
const params = { cardSide: this.cardSide, imageBase64 };
const result = await idCardOcrApi(params);
if (Object.keys(result).length) {
const {
Name,
IdNum,
ValidDate,
AdvancedInfo: { IdCard }
} = result;
if (IdCard) {
const imageBase64 = "data:image/png;base64," + IdCard;
const file = await base64URLToFile(imageBase64);
this.image_url = await this.uploadFile(file);
}
const id_card_end_time =
ValidDate && ValidDate.indexOf("长期") === -1 ? ValidDate.split("-")[1].replace(/\./g, "/") : "";
const id_card_info = {
id_card_name: Name ? Name : "",
id_card_num: IdNum ? IdNum : "",
long_term: ValidDate ? (ValidDate.indexOf("长期") > -1 ? 1 : 2) : 0,
id_card_end_time
};
if (this.cardSide === "FRONT") {
id_card_info.id_card_front = this.image_url;
} else {
id_card_info.id_card_back = this.image_url;
}
this.$store.commit("COMMON/setIdCardInfo", id_card_info);
} else {
const file = await base64URLToFile(imageBase64);
this.image_url = await this.uploadFile(file);
const id_card_info = {};
if (this.cardSide === "FRONT") {
id_card_info.id_card_front = this.image_url;
} else {
id_card_info.id_card_back = this.image_url;
}
this.$store.commit("COMMON/setIdCardInfo", id_card_info);
}
this.$toast.clear();
this.$toast({
message: "识别成功",
duration: 800,
onClose: () => {
this.$router.go(-1);
}
});
} catch (err) {
console.log(err);
}
},
// 从相册选择图片
chooseLocalImage() {
// eslint-disable-next-line no-undef
wx.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album"],
success: async res => {
const id = res.localIds[0];
// eslint-disable-next-line no-undef
wx.getLocalImgData({
localId: id,
success: async res => {
await this.idCardRecognition(res.localData);
this.$toast.clear();
},
fail: err => {
console.error("getLocalImgData err", err);
}
});
}
});
},
// 上传文件
uploadFile(file) {
return new Promise(async (resolve, reject) => {
try {
this.$toast.loading({
message: "上传并识别中",
forbidClick: true,
loadingType: "spinner"
});
const params = new FormData();
params.append("file", file);
params.append("type", 1);
params.append("file_name", file.name);
const { url } = await uploadFileApi(params);
resolve(url);
} catch (err) {
reject(err);
}
});
}
}
};
</script>
<style lang="less" scoped>
.ocr-id-card {
width: 100vw;
z-index: 2000;
background: #fff;
overflow: hidden;
-webkit-overflow-scrolling: touch; .cover {
width: 100vw;
height: calc(100vh - 300px);
position: fixed;
top: 0;
left: 0;
z-index: 2001; .id-card-container {
width: 708px;
height: 460px;
background: url("~@/assets/parttime-operator/ocr-border.png") 0 0 no-repeat;
background-size: 708px 460px;
position: fixed;
top: 322px;
left: 50%;
transform: translateX(-50%);
z-index: 2004;
}
}
.media-video {
width: 100vw;
height: 100%;
position: absolute;
top: -25px;
left: 0;
}
.footer-tip {
width: 312px;
height: 64px;
background: rgba(0, 0, 0, 0.5);
position: fixed;
bottom: 392px;
left: 50%;
transform: translateX(-50%);
z-index: 2003;
}
.footer-btn {
width: 100vw;
height: 300px;
background: #fff;
position: fixed;
bottom: 0;
left: 0;
z-index: 2005;
.record-btn {
width: 108px;
height: 108px;
background: url("~@/assets/parttime-operator/take-photo.png") 0 0 no-repeat;
background-size: 108px 108px;
position: absolute;
top: 76px;
left: 50%;
transform: translateX(-50%);
z-index: 2006;
}
.album {
width: 80px;
height: 80px;
position: absolute;
top: 90px;
left: 120px;
z-index: 2006;
}
}
.card-canvas {
position: fixed;
left: -9999px;
top: -9999px;
z-index: 0;
backface-visibility: hidden;
transform: translateZ(0);
}
}
</style>

功能优化及兼容性bug修复

兼容性问题机注意点

  1. 本地调试打开相机需要使用https协议下才能正常调用获取媒体流的api
  2. ios环境下初次打开相机会展示直播界面,安卓系统正常
  3. 媒体流帧率问题,视频分辨率问题,顶部空白问题。
  4. ios有滚动条问题,安卓系统正常
  5. 页面退出时关闭媒体流输入,关闭相机,进入时打开媒体流输入。

解决方案

  • 本地开发时开启htpps
  devServer: {
https: true,
xxx...
}
  • 页面中的元素使用fixed定位,并设置z-index高一些
  • 设置视频流帧率和视频流的分辨率大小,下面的width和height可根据实际情况来调整大小
const constraints = {
audio: false,
video: {
width: 1150,
height: 768,
frameRate: { ideal: 60 }, // 视频流帧率
facingMode: "environment" // 后置摄像头
}
};
  • ios有滚动条问题,尝试了一些css处理方案,无效,欢迎大家评论区指点迷津。

  • 调用ocr图片识别可以调用后端接口或者第三方的API来实现,例如腾讯云OCR 最后实现效果

本文转载于:

https://juejin.cn/post/7293778347607097394

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--如何在H5中实现OCR拍照识别身份证功能的更多相关文章

  1. 如何在myeclipse中实现jquery的自动提示功能

    在web开发过程中,myeclipse中jsp可以实现自动提示功能,但是jquery代码却无法实现自动提示,需要自己一个个手动去输入,效率过低,怎么办? 工具/原料   jquery 1.8.3.js ...

  2. 百度OCR文字识别-身份证识别

    简介 一.介绍 身份证识别 API 接口文档地址:http://ai.baidu.com/docs#/OCR-API/top 接口描述 用户向服务请求识别身份证,身份证识别包括正面和背面. 请求说明 ...

  3. 记录5-如何在UltraEdit中编译和运行Java

    1点击“高级”,再点击“工具配置” 2点击“插入”,在“菜单项”名称上输入“编译java程序”,在“命令行”里输入“javac %n%e”,在工作目录上填“%p”. 3切换到“输出”项,选择“输出到列 ...

  4. 【社交系统研发日记】如何在 Laravel 中 “规范” 的开发验证码发送功能

    顺便发个小通知:7月15日ThinkSNS+开源版发布,同时非开源的APP也走出内测阶段,体验二维码也全面发布体验. 什么是ThinkSNS ? ThinkSNS(简称TS),一款全平台综合性社交系统 ...

  5. 如何在 Laravel 中 “规范” 的开发验证码发送功能

    什么是ThinkSNS ? ThinkSNS(简称TS),一款全平台综合性社交系统,为国内外大中小企业和创业者提供社会化软件研发及技术解决方案,目前最新版本为ThinkSNS+(简称TS+).Thin ...

  6. 如何在Mongodb中实现数据超时自动删除功能?

    在工作过程中,我们难免会遇到这样的问题,我们想保存一些数据,但是我们对这些数据的要求并不高,有时候往往只是想要某个时间范围内的数据,比如我们如果永远只关心从当前时间往前推半年内的数据特性,那么我们就不 ...

  7. 如何在Hexo中实现自适应响应式相册功能

    用最清晰简洁的方法整合一个响应式相册 效果 技术选型 由于我选用的主题使用了fancyBox作为图片弹出展示的框架,查看后表示很不错,能满足需要 http://fancyapps.com/fancyb ...

  8. 微信小程序中如何上传图片来识别身份证银行卡?

    Page({ shibie2(){ //识别银行卡 var that=this wx.chooseImage({ //选择图片 count: 1, //上传数量 sizeType: ['origina ...

  9. PHP:基于百度大脑api实现OCR文字识别

    有个项目要用到文字识别,网上找了很多资料,效果不是很好,偶然的机会,接触到百度大脑.百度大脑提供了很多解决方案,其中一个就是文字识别,百度提供了三种文字识别,分别是银行卡识别.身份证识别和通用文字识别 ...

  10. 我是如何在SQLServer中处理每天四亿三千万记录的

    首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务. ...

随机推荐

  1. MySQL-顺序IO和随机IO的区别

    顺序IO是指读写操作的访问地址连续.在顺序IO访问中,HDD所需的磁道搜索时间显着减少,因为读/写磁头可以以最⼩的移动访问下一个块.数据备份和日志记录等业务是顺序IO业务.随机IO是指读写操作时间连续 ...

  2. Windows 10 快捷键大全|日常办公效率加倍

    ## 复制.粘贴及其他常规     Ctrl + X 剪切选定项. Ctrl + C(或 Ctrl + Insert) 复制选定项. Ctrl + V(或 Shift + Insert) 粘贴选定项. ...

  3. 思维分析逻辑 4 DAY

    目录 竞品分析 波特五力模型 竞品分析步骤 分析目的 对比分析 初步结论 活动营销分析 用户增长分析 用户增长基本模型 渠道思维(前期) 用户思维(中期) ROI思维(后期) 增长思维 北极星指标:一 ...

  4. 致敬英雄,共悼逝者,css 让页面变黑白

    壹 ❀ 引 今天是四月四日清明节,也是全国哀悼抗疫烈士的一天.细心的同学可以发现,不仅是娱乐活动以及游戏全部停止,当我们打开各大门户网站,网站页面也都变成了黑白,那么具体怎么做呢,这里可以借用CSS3 ...

  5. CF1826D Running Miles

    题目链接 题解 知识点:贪心,前缀和,枚举. 首先考虑一个贪心结论,选择区间端点一定是两个最大值,因此 \(i_1 = l,i_3 = r\) . 考虑变形式子 \((b_l + l) + b_{i_ ...

  6. 【分布式】load balance 02-consistent hash algorithm 一致性哈希算法原理详解

    负载均衡系列专题 01-负载均衡基础知识 02-一致性 hash 原理 03-一致性哈希算法 java 实现 04-负载均衡算法 java 实现 概念 一致哈希是一种特殊的哈希算法. 在使用一致哈希算 ...

  7. Git Conventional Commits (Git代码提交说明规范)

    Conventional Commits (代码提交说明规范) Conventional Commits 是关于Git Commit 提交代码时, 填写的说明文字的一个规范. 这个规范提供了一套易于理 ...

  8. springboot中前端ajax如何给controller提交数组参数?

    说明 我有个需求,前端批量添加一堆商品明细.也就是说会有一个商品ID,然后一堆商品明细,多行. 如此一来,针对后端接口肯定是要以数组或列表方式接收这个商品明细数组了. 前端代码 关键地方在于以form ...

  9. zabbix-server.service failed解决方法

    1.问题描述 centos7中安装的zabbix server在重启系统后无法启动了,查看状态报错如下: 2.问题原因 selinux没有关闭! 3.解决 永久关闭selinux, 将SELINUX值 ...

  10. React18 之 Suspense

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:佳岚 Suspense Suspense 组件我们并不陌生 ...