OpenCV 入门(七)—— 身份证识别

OpenCV 入门系列:

OpenCV 入门(一)—— OpenCV 基础
OpenCV 入门(二)—— 车牌定位
OpenCV 入门(三)—— 车牌筛选
OpenCV 入门(四)—— 车牌号识别
OpenCV 入门(五)—— 人脸识别模型训练与 Windows 下的人脸识别
OpenCV 入门(六)—— Android 下的人脸识别
OpenCV 入门(七)—— 身份证识别

利用 OpenCV 实现身份证识别 Demo 效果:

2024-4-24.身份证识别Demo效果

主要步骤分为两大步:

  1. 利用 OpenCV 从完整的身份证图片中识别出身份证号码区域,并返回身份证号码的图片
  2. 利用 OCR 识别工具将身份证号码图片识别成文字

实际上身份证识别、银行卡识别都是相同的思路。

1、OpenCV 图像识别

1.1 上层代码过程

在 Activity 中,点击“从相册中查找”按钮从相册中选择一张图片转换为一个 640 * 480 的 Bitmap 设置到 ImageView 中:

class MainActivity : AppCompatActivity() {private lateinit var mBinding: ActivityMainBindingprivate var mFullImage: Bitmap? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mBinding = ActivityMainBinding.inflate(layoutInflater)setContentView(mBinding.root)}/*** 从相册中选择一张图片*/fun search(view: View) {val intent = Intent(Intent.ACTION_PICK)intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")startActivityForResult(Intent.createChooser(intent, "选择待识别图片"), REQUEST_CODE)}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_CODE && resultCode == RESULT_OK && data != null) {getResult(data.data)}}private fun getResult(data: Uri?) {// 获取图片路径var imagePath: String? = nullif ("file" == data?.scheme) {Log.i(TAG, "path uri 获得图片")imagePath = data.path} else if ("content" == data?.scheme) {Log.i(TAG, "content uri 获得图片")val filePathColumns = arrayOf(MediaStore.Images.Media.DATA)val cursor = contentResolver.query(data, filePathColumns, null, null, null)if (null != cursor) {if (cursor.moveToFirst()) {val columnIndex = cursor.getColumnIndex(filePathColumns[0])imagePath = cursor.getString(columnIndex)}cursor.close()}}// 根据图片路径生成 Bitmap 并显示if (!TextUtils.isEmpty(imagePath)) {mFullImage?.recycle()mFullImage = toBitmap(imagePath)mBinding.tvIdNumber.text = nullmBinding.ivIdCard.setImageBitmap(mFullImage)}}/*** 根据图片路径生成 Bitmap,宽高要缩放到 STANDARD_ID_CARD_WIDTH* 与 STANDARD_ID_CARD_HEIGHT 的范围内*/private fun toBitmap(imagePath: String?): Bitmap? {if (imagePath == null) {return null}val tempOptions = BitmapFactory.Options()tempOptions.inJustDecodeBounds = trueBitmapFactory.decodeFile(imagePath, tempOptions)// 计算出缩放倍数以及缩放后的宽高var tempWidth = tempOptions.outWidthvar tempHeight = tempOptions.outHeightvar scale = 1while (true) {if (tempWidth <= STANDARD_ID_CARD_WIDTH && tempHeight <= STANDARD_ID_CARD_HEIGHT) {break}tempWidth /= 2tempHeight /= 2scale *= 2}// 利用计算好的宽高与缩放倍数解析出一个 Bitmapval options = BitmapFactory.Options()options.outWidth = tempWidthoptions.outHeight = tempHeightoptions.inSampleSize = scalereturn BitmapFactory.decodeFile(imagePath, options)}companion object {private val TAG = MainActivity::class.java.simpleNameprivate const val REQUEST_CODE = 100private const val STANDARD_ID_CARD_WIDTH = 640private const val STANDARD_ID_CARD_HEIGHT = 480}
}

然后点击“查找 ID”按钮时,将完整的身份证 Bitmap 传给 ImageProcessor 交由 Native 层的 OpenCV 进行识别:

	private var mResultImage: Bitmap? = null/*** 从整张图片中截取出身份证号码区域*/fun searchIdImage(view: View) {mBinding.tvIdNumber.text = nullmResultImage = ImageProcessor.getIdNumberArea(mFullImage, Bitmap.Config.ARGB_8888)mFullImage?.recycle()mBinding.ivIdCard.setImageBitmap(mResultImage)}

ImageProcessor 的内容很简单,就定义了一个 JVM 静态的 Native 方法 getIdNumberArea():

class ImageProcessor {companion object {init {System.loadLibrary("ID-Recognition")}@JvmStaticexternal fun getIdNumberArea(fullImage: Bitmap?, config: Bitmap.Config): Bitmap}
}

该方法需要得到识别后身份证号区域的 Bitmap。

1.2 Native 识别过程

Native 层首先要解决 Bitmap 与 Mat 之间相互转换的问题。因为我们从上层传到 Native 的待识别图片是 Bitmap,但是 OpenCV 中是没有 Bitmap 对象的,类似的可以被认为是一张图片的结构是 Mat。那么在给 OpenCV 识别前,就要将 Bitmap 转化成 Mat,识别后再将 Mat 转换成 Bitmap 返回给上层。

OpenCV 提供了转换函数 nBitmapToMat2() 和 nMatToBitmap(),我们还需自己实现一个创建 Bitmap 对象的函数 createBitmap():

#include <jni.h>
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;extern "C" {extern JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat2(JNIEnv *env, jclass, jobject bitmap, jlong m_addr, jboolean needUnPremultiplyAlpha);
extern JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nMatToBitmap(JNIEnv *env, jclass, jlong m_addr, jobject bitmap);/*** 反射调用上层的 Bitmap 的 createBitmap() 创建一个 Bitmap 对象,并且* 将 srcData 的内容填充到 Bitmap 中*/
jobject createBitmap(JNIEnv *env, Mat &srcData, jobject config) {int width = srcData.cols;int height = srcData.rows;// 反射 Bitmap.createBitmap() 并调用以创建 Bitmap 对象jclass bitmapClass = env->FindClass("android/graphics/Bitmap");jmethodID createBitmapMethod = env->GetStaticMethodID(bitmapClass,"createBitmap","(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");jobject bitmap = env->CallStaticObjectMethod(bitmapClass, createBitmapMethod, width, height,config);// 将 srcData 转换成 bitmapJava_org_opencv_android_Utils_nMatToBitmap(env, bitmapClass, (jlong) &srcData, bitmap);return bitmap;
}
}

接下来再实现 OpenCV 的识别函数:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_opencv_id_recognition_ImageProcessor_getIdNumberArea(JNIEnv *env, jclass clazz,jobject full_image, jobject config) {Mat src_img;Mat dst_img;Mat temp_img;// 1.通过 OpenCV 提供的函数,将上层传来的 Bitmap 转换为 Mat 对象Java_org_opencv_android_Utils_nBitmapToMat2(env, clazz, full_image, (jlong) &src_img, false);// 2.将图片无损压缩至 640 * 400resize(src_img, src_img, FIXED_ID_CARD_SIZE);// 3.灰度化cvtColor(src_img, temp_img, COLOR_BGR2GRAY);// 4.二值化threshold(temp_img, temp_img, 100, 255, THRESH_BINARY | THRESH_OTSU);// 5.膨胀操作Mat eroded_img = getStructuringElement(MORPH_RECT, Size(20, 10));erode(temp_img, temp_img, eroded_img);// 6.轮廓检测vector<vector<Point>> contours;vector<Rect> rects;findContours(temp_img, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));for (int i = 0; i < contours.size(); i++) {Rect rect = boundingRect(contours[i]);if (rect.width > rect.height * 9) {rects.push_back(rect);rectangle(dst_img, rect, Scalar(0, 255, 255));dst_img = src_img(rect);}}// 7.筛选结果,如果 rects 有多个元素,则挑选纵坐标靠下的if (rects.size() == 1) {dst_img = src_img(rects[0]);} else if (rects.size() > 1) {int lowPoint = 0;Rect finalRect;for (auto &rect: rects) {if (rect.tl().y > lowPoint) {lowPoint = rect.tl().y;finalRect = rect;}}rectangle(temp_img, finalRect, Scalar(255, 255, 0));dst_img = src_img(finalRect);}// 8. 根据最终的 Mat 创建 Bitmap 作为返回值jobject bitmap = createBitmap(env, dst_img, config);// 9. 释放资源src_img.release();dst_img.release();temp_img.release();return bitmap;
}

2、OCR 识别

上一步我们能得到一个包含身份证号码的 Bitmap,接下来需要使用 OCR 识别技术将图片中的身份证号码识别成文字。OCR 全称 Optical Character Recognition,是一个对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。

我们使用的是 Tess-two。Tess-two 是 TesseraToolForAndroid 的一个 git 分支,它具有如下特征:

  1. 简单易用
  2. 开源且支持离线使用
  3. 为 Android 平台定制的 Java API

首先我们将识别模型文件 cn.traineddata 拷贝到 /src/main/assets 目录下,在 Activity 的 onCreate() 中启动协程,将该模型文件拷贝到手机中,并初始化 Tess:

	override fun onCreate(savedInstanceState: Bundle?) {...lifecycleScope.launch {initTess()}}private suspend fun initTess() {coroutineScope {// 1.显示进度showProgress()val result = async {mTessBaseAPI = TessBaseAPI()// 2.通过流将识别模型拷贝到手机中try {val inputStream = assets.open("$DEFAULT_LANGUAGE.traineddata")val assetFile = File("/sdcard/tess/tessdata/$DEFAULT_LANGUAGE.traineddata")if (!assetFile.exists()) {assetFile.parentFile?.mkdirs()val fos = FileOutputStream(assetFile)val buffer = ByteArray(2048)var len: Intwhile (inputStream.read(buffer).also { len = it } != -1) {fos.write(buffer, 0, len)}fos.close()}inputStream.close()// init 传入的 datapath 必须是包含 tessdata 的目录return@async mTessBaseAPI?.init("/sdcard/tess", DEFAULT_LANGUAGE) ?: false} catch (e: IOException) {e.printStackTrace()}return@async false}// 3.处理异步任务结果dismissProgress()if (!result.await()) {Toast.makeText(this@MainActivity, "load trainedData failed", Toast.LENGTH_SHORT).show()}}}companion object {private const val DEFAULT_LANGUAGE = "cn"}

注意 TessBaseAPI.init() 的第一个参数,路径必须是包含了 tessdata 目录的父目录,否则初始化会抛异常。

最后,点击“识别文字”按钮时,将被识别的 Bitmap 设置给 Tess 然后获取文字结果即可:

	fun recognition(view: View) {mTessBaseAPI?.setImage(mResultImage)mBinding.tvIdNumber.text = mTessBaseAPI?.utF8TextmTessBaseAPI?.clear()}

当然,从最终的识别结果来看,并没有达到百分百的准确率,这与训练样本的数量不够有关。Tesseract-OCR 的样本训练方法,可参考超级详细的Tesseract-OCR样本训练方法。

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

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

相关文章

四川景源畅信:抖音个人开通橱窗操作流程介绍详情

随着短视频平台的快速发展&#xff0c;抖音已经成为越来越多人展示自我、分享生活的舞台。而其中&#xff0c;个人橱窗功能则为内容创作者提供了一个展示和销售商品的窗口。如果你是一位想要在抖音上开通个人橱窗的创作者或商家&#xff0c;了解详细的操作流程是迈向成功的第一…

如何通过前端表格控件在10分钟内完成一张分组报表?

前言&#xff1a; 当今时代&#xff0c;报表作为信息化系统的重要组成部分&#xff0c;在日常的使用中发挥着关键作用。借助报表工具使得数据录入、分析和传递的过程被数字化和智能化&#xff0c;大大提高了数据的准确性及利用的高效性。而在此过程中&#xff0c;信息化系统能…

无经验计科应届生前端面试遇到的问题整理

js数据类型有几种&#xff0c;分别是 原始数据类型&#xff08;Primitive data types&#xff09;: 字符串&#xff08;String&#xff09;: 用于表示文本数据&#xff0c;使用单引号&#xff08;‘’&#xff09;或双引号&#xff08;“”&#xff09;括起来。 数字&#xff…

AArch64 内存管理

本文是对arm developer网站《Learn the architecture - AArch64 memory management Guide》的学习笔记&#xff08;Documentation – Arm Developer&#xff09; 一、背景概述 本文介绍了AArch64中的内存转换&#xff0c;这是内存管理的关键&#xff0c;它解释了虚拟地址如何转…

第3章 WebServer重构

3.1 重构原生Web服务框架 3.1.1 分析原生Web服务框架 在服务端代码的 ClientHandler 中&#xff0c;请求解析、处理请求、返回响应的代码混杂在一起&#xff0c;这样的设计会导致代码难以维护和理解。为了提高代码的可读性、可维护性和可扩展性&#xff0c;我们需要对这些代码…

ComfyUI搭建和注意事项for WIN[笔记]

下载ComfyUI(GitHub - comfyanonymous/ComfyUI: The most powerful and modular stable diffusion GUI, api and backend with a graph/nodes interface.) 从源码上搭建比较麻烦&#xff0c;一般不推荐&#xff0c;所以跑到release里面找一个下载。我的显卡是GeFore GTX 1050 …

飞腾E2000运行Zephyr操作系统

Phytium-Zephyr-SDK 1. 仓库介绍 1.1 本仓库特色 此项目是一个开源软件&#xff0c;专为物联网领域设计&#xff0c;基于Zephyr实时操作系统&#xff0c;针对Phytium系列CPU进行了专门的适配和应用开发。我们的目标是降低开发者的使用门槛&#xff0c;提供了部署文档和使用指…

同向双指针(滑动窗口)算法

209. 长度最小的子数组 这里的更新结果就题来定 class Solution {public int minSubArrayLen(int target, int[] nums) {int sum 0;int len 0;int f 0;for(int left 0, right 0; right < nums.length;){//求和sum nums[right];while(sum > target){//lenint t ri…

MLP实现fashion_mnist数据集分类(1)-模型构建、训练、保存与加载(tensorflow)

1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、fashion_mnist数据集下载与展示 (train_image,train_label),(test_image,test_label) tf.keras.datasets.fashion_mnist.l…

张大哥笔记:卖盗版网课,获利 100 万被抓

这几天刷视频&#xff0c;看到一个新闻&#xff0c;某大学生卖盗版网课&#xff0c;把别人2000多正版网课&#xff0c;以做活动名义售卖20元&#xff0c;获利100多万被抓。 下方图片来自&#xff1a;极目新闻 卖这种盗版网课&#xff0c;门槛低&#xff0c;成本低&#xff0c;…

解决3D模型只显示线框材质的方法---模大狮模型网

在3D建模和渲染过程中&#xff0c;正确的材质和纹理是呈现逼真效果的关键。然而&#xff0c;有时候用户可能会遇到一个常见问题&#xff0c;即3D模型在渲染或查看时只显示线框材质&#xff0c;而没有正确的表面纹理和颜色。本文将介绍解决这一问题的几种方法&#xff0c;帮助用…

7 人赚 960 亿美元,数字天才的首次独舞

巴菲特股东大会 一年一度的巴菲特股东大会如常召开&#xff0c;只不过这次坐在老爷子左手边的不再是老搭档查理芒格&#xff0c;而是钦点的未来继任者&#xff0c;格雷格阿贝尔。 随着芒格&#xff08;99岁&#xff09;的离开&#xff0c;巴菲特&#xff08;93岁&#xff09;也…

【.sh】Shell 脚本文件的基础内容

目录 &#x1f30a;1. 基础知识 &#x1f30a;2. 简单示例 &#x1f30d;2.1 示例一 输出"Hello, World!" &#x1f30d;2.2 示例二 输入输出创建 &#x1f30a;1. 基础知识 .sh文件是什么&#xff1f; .sh文件即 Shell 脚本文件&#xff0c;通常用来执行一系列…

Ranni: Taming Text-to-Image Diffusion for Accurate Instruction Following

Ranni: Taming Text-to-Image Diffusion for Accurate Instruction Following abstract 我们引入了一个语义面板作为解码文本到图像的中间件&#xff0c;支持生成器更好地遵循指令 Related work 最近的工作还通过包含额外的条件&#xff08;如补全掩码[15&#xff0c;45]、…

10 华三vlan技术介绍

AI 解析 -Kimi-ai Kimi.ai - 帮你看更大的世界 (moonshot.cn) 虚拟局域网&#xff08;VLAN&#xff09;技术是一种在物理网络基础上创建多个逻辑网络的技术。它允许网络管理员将一个物理网络分割成多个虚拟的局域网&#xff0c;这些局域网在逻辑上是隔离的&#xff0c;但实际…

【LeetCode刷题记录】105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树

105 从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,1…

Pandas入门篇(三)-------数据可视化篇2(pandas-plot篇)

目录 概述一、格式1. 生成pandas.plotting对象来生成图表2. 调用plot()函数来生成图表3.支持的图表类型 二、单变量绘图常用图表1. 柱状图&#xff08;bar&#xff09;使用场景代码实现 2. 折线图&#xff08;line&#xff09;&#xff08;默认即为折线图&#xff09;适用场景代…

计算机毕业设计 | vue+springboot 在线花店后台管理系统(附源码)

1&#xff0c;绪论 1.1 项目背景 随着社会发展&#xff0c;网上购物已经成为我们日常生活的一部分。但是&#xff0c;至今为止大部分电商平台都是从人们日常生活出发&#xff0c;出售都是一些日常用品比如&#xff1a;食物、服装等等&#xff0c;并未发现一个专注于鲜花的电商…

从一到无穷大 #25 DataFusion:可嵌入,可扩展的模块化工业级计算引擎实现

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言架构总览与可扩展性Catalog and Data SourcesFront End逻辑计划与逻辑计划优化器…

使用台式机打开腾讯会议后打开麦克风提示:未检测到可用的麦克风,请插入设备后重试

解决方法如下&#xff1a; 台式机是不带输入设备的&#xff0c;也就是不自带麦克风的&#xff0c;而笔记本电脑是带的。这时候在台式机上可以通过插入有线耳机充当输入设备&#xff0c;就可以正常使用麦克风功能了。 插入之后&#xff0c;可以看到设置里面声音界面包含了一个…