android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别

前言

在日常的开发中,我们有时会遇到添加银行卡的需求,这时候,产品可能会让你仿一下支付宝之类的相机扫描识别银行卡号。很多时候,做这样的需求会去找找稳定的第三方,本文通过 OpenCV 结合识别的需求带你分析如何实现银行卡号的识别。由于作者技术有限,本文仅从如何做到识别的思路上介绍,文中例子不适用于实际开发,也不是所有银行卡都能识别,但希望读者可以在实现的思路上给予一些启发,以及更深入熟悉 OpenCV 的组合使用。

1. 银行卡识别思路分析

1.1 银行卡一般具有的特征

银行卡一般会有 银行、卡号、银联标识等等,主要的是卡号区域,大多数银行卡卡号都是在下方显示的。那么,在检索一张图片的时候,要首先找到卡在哪里,卡一般是长方形,所以我们在背景色不非常接近下可以找到银行卡的轮廓。

1.2 总体实现思路和步骤

在图片中找到银行卡区域 --> 在银行卡区域找到卡号区域 --> 在卡号区域中找到卡号每个数字的集合 --> 识别数字

2. 在图片中找到银行卡区域

2.1 实现思路和步骤

高斯模糊降噪处理

边缘梯度增强

取增强绝对值

合并 Mat

转灰度

二值化

从二值图像中检索轮廓

从集合中找合适的轮廓

2.2 具体实现

/**

* 在图片中找到银行卡区域

* @param mat 图片

* @param rect 银行卡区域

* @return 是否成功

*/

int ocr::find_card_area(const Mat &mat, Rect &card_area) {

// 1. 高斯模糊降噪处理

Mat blurMat;

GaussianBlur(mat, blurMat, Size(5, 5), BORDER_DEFAULT, BORDER_DEFAULT);

// 2. 边缘梯度增强

// Scharr( InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT )

// ddepth:输出图像的深度

// dx:x方向上的差分阶数

// dy:y方向上的差分阶数

// 对 x 增强

Mat grade_x;

Scharr(blurMat, grade_x, CV_32F, 1, 0);

// 对 y 增强

Mat grade_y;

Scharr(blurMat, grade_y, CV_32F, 0, 1);

// 3. 取增强绝对值

Mat abs_grade_x;

convertScaleAbs(grade_x, abs_grade_x);

Mat abs_grade_y;

convertScaleAbs(grade_y, abs_grade_y);

// 4. 合并 Mat

Mat gradeMat;

addWeighted(abs_grade_x, 0.5, abs_grade_y, 0.5, 0, gradeMat);

//imwrite("/storage/emulated/0/ocr/find_card_area_gradeMat.jpg", gradeMat);

// 5. 转灰度

Mat grayMat;

cvtColor(gradeMat, grayMat, COLOR_BGRA2GRAY);

//imwrite("/storage/emulated/0/ocr/find_card_area_grayMat.jpg", grayMat);

// 6. 二值化

// threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type )

// thresh:设定的阈值

// maxval;当灰度值大于(或小于)阈值时将该灰度值赋成的值

// type:当前二值化的方式 THRESH_BINARY 大于阈值的部分被置为255,小于部分被置为0

// 此处其实是将 白变成黑,黑变成白

Mat binaryMat;

threshold(grayMat, binaryMat, 40, 255, THRESH_BINARY);

//imwrite("/storage/emulated/0/ocr/find_card_area_binaryMat.jpg", binaryMat);

// 7. 从二值图像中检索轮廓

// findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point())

// contours:双重向量,向量内每个元素保存了一组由连续的 Point 点构成的点的集合的向量,每一组Point点集就是一个轮廓

// mode:定义轮廓的检索模式,其中 RETR_EXTERNAL 表示只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略

// method:定义轮廓的近似方法,其中 CHAIN_APPROX_SIMPLE 表示 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours 向量内,拐点与拐点之间直线段上的信息点不予保留

vector > contours;

findContours(binaryMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

LOGE("find_card_area contours.size():%d", contours.size());

// 8. 从集合中找合适的轮廓

for (int i = 0; i < contours.size(); i++) {

// boundingRect 函数计算并返回指定点集或非零像素的灰度图像

Rect rect = boundingRect(contours[i]);

//LOGE("从集合中找合适的轮廓 i=%d rect.width=%d mat.cols=%d", i, rect.width, mat.cols);

if (rect.width > mat.cols / 2 /*&& rect.width != mat.cols*/ && rect.height > mat.rows / 2) {

// 银行卡区域的宽高必须大于图片的一半

LOGE("find_card_area 哈哈哈,找到啦");

card_area = rect;

break;

}

}

//LOGE("card_area h:%d", card_area.height);

// 9. 释放资源

blurMat.release();

grade_x.release();

grade_y.release();

abs_grade_x.release();

abs_grade_y.release();

gradeMat.release();

binaryMat.release();

// 没有找到合适的轮廓 返回失败错误码

if (card_area.empty()) {

LOGE("find_card_area 啊啊啊,找不到");

return -1;

}

return 0;

}

复制代码

3. 在银行卡区域找到卡号区域

3.1 实现思路和步骤

此处仅做一个粗略的位置计算,实际开发时,一般和相机搭配扫描一个框的内容,也可以按框的比例来计算。

此方法有些银行卡会有误差,有误差时,实际开发应该有输入框给用户手动补充识别失败的数字。

卡号区域 起点 (x, y) = (银行卡区域宽度的 1/12, 银行卡区域高度的 1/2)

卡号区域的宽高 width = 银行卡区域宽度的 5/6, height = 银行卡区域高度的 1/4

3.2 具体实现

/**

* 在银行卡区域找到卡号区域

* @param mat 图片

* @param card_area 存放卡号区域

* @return 是否成功

*/

int ocr::find_card_number_area(const Mat &mat, Rect &card_area) {

// 此处仅做一个粗略的位置计算,实际开发时,一般和相机搭配扫描一个框的内容,也可以按框的比例来计算。

// 此方法有些银行卡会有误差,有误差时,实际开发应该有输入框给用户手动补充识别失败的数字。

// 卡号区域 起点 (x, y) = (银行卡区域宽度的 1/12, 银行卡区域高度的 1/2)

// 卡号区域的宽高 width = 银行卡区域宽度的 5/6, height = 银行卡区域高度的 1/4

card_area.x = mat.cols / 12;

card_area.y = mat.rows / 2;

card_area.width = mat.cols * 5 / 6;

card_area.height = mat.rows / 4;

return 0;

}

复制代码

4. 在卡号区域中找到卡号每个数字的集合

4.1 实现思路和步骤

转灰度图

二值化

降噪过滤

取反 黑变白,白变黑

从二值图像中检索轮廓,黑色的背景,白色的数字,可以检测出噪点

将高度小于我们设定的最小值的轮廓区域过滤掉

过滤掉再合起来再取反恢复

过滤后再次检索轮廓

将轮廓提取成矩形轮廓集合

对矩形轮廓集合进行排序

裁剪出单个数字,保存数字

4.2 具体实现

/**

* 在卡号区域中找到卡号每个数字的集合

* @param mat 图片

* @param numbers 存放卡号每个数字的集合

* @return 是否成功

*/

int ocr::find_card_numbers(const Mat &mat, vector &numbers) {

// 1. 转灰度图

Mat grayMat;

cvtColor(mat, grayMat, COLOR_BGRA2GRAY);

// 2. 二值化

Mat binaryMat;

// THRESH_BINARY 大于阈值的部分被置为255,小于部分被置为 0

threshold(grayMat, binaryMat, 39, 255, THRESH_BINARY);

// 3. 降噪过滤

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

// 形态学操作

// morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel,

// Point anchor = Point(-1,-1), int iterations = 1,

// int borderType = BORDER_CONSTANT,

// const Scalar& borderValue = morphologyDefaultBorderValue() );

// op:操作的类型 MORPH_CLOSE 表示闭操作,先膨胀后腐蚀

// kernel:用于膨胀操作的结构元素 如果取值为 Mat, 那么默认使用一个 3 x 3 的方形结构元素,可以使用 getStructuringElement() 来创建结构元素

// anchor:参考点,其默认值为(-1,-1)说明位于kernel的中心位置

// borderType:边缘类型

// borderValue:边缘值

morphologyEx(binaryMat, binaryMat, MORPH_CLOSE, kernel);

// 4. 取反 黑变白,白变黑

Mat binaryNotMat = binaryMat.clone();

bitwise_not(binaryNotMat, binaryNotMat);

// 5. 从二值图像中检索轮廓,黑色的背景,白色的数字,可以检测出噪点

vector > contours;

findContours(binaryNotMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

LOGE("find_card_numbers 1 contours.size():%d", contours.size());

// 6. 将高度小于我们设定的最小值的轮廓区域过滤掉

int mat_area = mat.rows * mat.cols;

int min_h = mat.rows / 4;

for (int i = 0; i < contours.size(); i++) {

// 所有轮廓进行检测过滤

Rect rect = boundingRect(contours[i]);

// 面积太小的噪点过滤掉

int area = rect.width * rect.height;

if (area < mat_area / 200) {

// 小面积轮廓填充为 白色背景

drawContours(binaryMat, contours, i, Scalar(255), -1);

} else if (rect.height < min_h) {

// 小面积轮廓填充为 白色背景

drawContours(binaryMat, contours, i, Scalar(255), -1);

}

}

// 7. 过滤掉再合起来再取反恢复

binaryMat.copyTo(binaryNotMat);

bitwise_not(binaryNotMat, binaryNotMat);

// 8. 过滤后再次检索轮廓

contours.clear();

findContours(binaryNotMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

// 9. 将轮廓提取成矩形轮廓集合,找到最小宽度轮廓

Rect rects[contours.size()];

// 白色的图片,单颜色

Mat contoursMat(binaryMat.size(), CV_8UC1, Scalar(255));

// min_w 存放最小宽度轮廓

int min_w = mat.cols;

for (int i = 0; i < contours.size(); i++) {

rects[i] = boundingRect(contours[i]);

drawContours(contoursMat, contours, i, Scalar(0), 1);

min_w = min(rects[i].width, min_w);

}

// 10. 对矩形轮廓集合进行排序

// 冒泡排序

LOGE("contours.size()=%d", contours.size());

if (contours.size() <= 0) return -1;

for (int i = 0; i < contours.size() - 1; ++i) {

for (int j = 0; j < contours.size() - i - 1; ++j) {

if (rects[j].x > rects[j + 1].x) {

swap(rects[j], rects[j + 1]);

}

}

}

// 11. 裁剪出单个数字,保存数字

numbers.clear();

for (int i = 0; i < contours.size(); i++) {

// >= 最小宽度的两倍,是粘连的数字

if (rects[i].width >= min_h * 2) {

// 处理粘连字符

Mat mat(contoursMat, rects[i]);

int cols_pos = find_split_number(mat);

// 左右两个数字都存进去

Rect rect_left(0, 0, cols_pos - 1, mat.rows);

numbers.push_back(Mat(mat, rect_left));

Rect rect_right(cols_pos, 0, mat.cols, mat.rows);

numbers.push_back(Mat(mat, rect_right));

} else {

Mat number(contoursMat, rects[i]);

numbers.push_back(number);

// 保存数字图片

LOGE("保存数字图片:%d", i);

char name[50];

sprintf(name, "/storage/emulated/0/CardOCR/number_%d.jpg", i);

imwrite(name, number);

}

}

LOGE("numbers.size:%d", numbers.size());

// 释放资源

grayMat.release();

binaryMat.release();

kernel.release();

binaryNotMat.release();

contoursMat.release();

return 0;

}

复制代码

4.3 字符串进行粘连处理

/**

* 字符串进行粘连处理

* @param mat

* @return 粘连的那一列

*/

int ocr::find_split_number(const Mat &mat) {

// 对中心位置的左右 1/4 扫描,记录最少的黑色像素点的这一列的位置,就当做字符串的粘连位置

int mx = mat.cols / 2;

int height = mat.rows;

// 围绕中心左右扫描 1/4

int start_x = mx - mx / 2;

int end_x = mx + mx / 2;

// 字符的粘连位置

int cols_pos = mx;

// 获取像素子

int c = 0;

// 最小的像素值

int min_h_p = mat.rows;

for (int col = start_x; col < end_x; ++col) {

int total = 0;

for (int row = 0; row < height; ++row) {

c = mat.at(row, col)[0];// 单通道

if (c == 0) {

total++;

}

}

if (total < min_h_p) {

min_h_p = total;

cols_pos = col;

}

}

return cols_pos;

}

复制代码

5. 原图和识别效果

原图(图片来源网络,侵删)

2f213d420ea6187f24ad48515cf19feb.png

识别效果

8e29b2800f4a9fb3814dacc4442340bd.png

a41f2f2f167611e1daf1009b2c224649.png

后话

由于时间等原因,还需对识别出来的单个数字进行样本对比,识别出最相似的数字。作者也没有足够的样本,所以此步骤留到后续完善,但本文的初衷是为了分析识别思路和如何使用 OpenCV 进行实现,因此,读者也可结合我的基础上进行完善,本文完整的源码地址是: github.com/Vegen/BankC… ,欢迎 star 和 交流。

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

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

相关文章

鸿蒙测试机型微博,华为多款机型开启鸿蒙尝鲜:微博已适配HarmonyOS小尾巴

日前&#xff0c;华为已经正式宣布&#xff0c;将于6月2日晚20点召开鸿蒙操作系统及华为全场景新品发布会&#xff0c;届时将正式发布鸿蒙OS正式版。同时&#xff0c;今天华为还开启了鸿蒙OS首批消费者尝鲜计划&#xff0c;其中正式版可参与机型包括Mate 40系列、Mate X2、Mate…

android今日头条刷新,仿今日头条刷新vector动画

一般的刷新动画是一个圈圈在转&#xff0c;而头条的比较特殊&#xff0c;直接上写好的效果图(一直不知道怎么把图片尺寸调小o(╯□╰)o)吧~刷新动画_.gif首先整个效果是通过SVG和vector来实现的&#xff0c;如果不是很了解&#xff0c;请看大佬的文章&#xff1a;SVG学习--Anim…

在html中标题字号一共有几种,HTML中常用的几种标签

在HTML中&#xff0c;标签是首要的&#xff0c;也是最重要的东西。一旦进入HTML&#xff0c;认识和理解标签是基本的需要&#xff0c;因为这是区分HTML代码与普通文本的分隔符。这些标签是用来显示文档中的普通文本或转化文本的指令的标签。什么是转化后的文本&#xff1f;要显…

html静态页面引用其他页面,Shtml完美解决静态页面内部调用其他页面(非Iframe、Object、Js方法)...

我想这个是所有前端工程师都会碰到的问题&#xff0c;在你做了很多页面&#xff0c;需要调用同一个头部或者底部的时候&#xff0c;需要嵌套一下&#xff0c;这个时候怎么办Iframe、Object、Js调用的方法就不讨论了&#xff0c;网上搜索一大堆&#xff0c;不过兼容性不好这里给…

鸿蒙手机如何录屏,安卓手机如何屏幕录制视屏?手机视频录制方法

安卓手机如何屏幕录制视屏&#xff1f;手机视频录制方法2018年12月17日 17:05作者&#xff1a;黄页编辑&#xff1a;黄页分享随着科技的不断进步发展,手机已经成为人类不可缺少的一种生活神器,人们已经不满足只是用来打打电话、发发短信那么简单了,手机的用途主要用来社交、娱乐…

html判断为空的函数,javascript怎么判断是否为空字符串?

JavaScript中可以使用if(typeof obj"undefined"||objnull||obj"")语句通过判断字符串的数据类型来判断字符串是否为空。判断字符串是否为空的方法函数&#xff1a;function isEmpty(obj){if(typeof obj "undefined" || obj null || obj "…

et200sp模块接线手册_西门子PN/PN耦合器学习应用系列(1)-外观及接线

早在2017年我曾写过两篇文章介绍西门子PN/PN耦合器&#xff0c;文章链接如下&#xff1a;初识西门子PNPN耦合器(PN/PN Coupler)&#xff1b;如何在博途(TIA Portal)环境下组态PNPN耦合器&#xff1f;当时PN/PN耦合器的固件版本还是V3.x。随着产品的升级&#xff0c;新版本的PN/…

js 条码枪扫描_年会展台 精彩不断 | 沧田:从打印到扫描录入 国产品牌从未停止...

11月23日-25日&#xff0c;中国现代办公行业年会(以下简称COAA年会)在南昌召开。今年对于OA行业而言&#xff0c;国产品牌的崛起成为主要特征之一。以针式打印机起家的沧田&#xff0c;在本次展会中展示了多款重量级产品&#xff0c;涵盖了针式打印机、激光一体机、条码打印机、…

投后管理岗面试_2020天津水务招79人,管理岗+操作岗,专科起报

Hello大家好&#xff0c;我们今天的国企招聘主要说的是天津水务。天津水务的公告和去年相比晚了几个月&#xff0c;而且要求也变了一些——变成了校招&#xff08;要求2020年应届生&#xff09;&#xff0c;虽然条件还是不高——大专起报。2点要求基本的条件就是要求&#xff1…

微信没有回车键怎么换行_在东平相亲网加了心仪对方的微信,但是没有话题怎么办?...

最近很多东平单身小伙伴问红娘&#xff1a;加了对方微信不知道聊什么&#xff0c;觉得两个人没有共同话题&#xff0c;而且感觉该聊的都聊了&#xff0c;每天早晚安也发了&#xff0c;够热情了。还有的说不知道怎么回信息或者不知道跟ta说什么话题该怎么办&#xff1f;1其实这些…

中国重汽微服务管理_springcloud微服务架构实战:商家管理微服务设计

商家管理微服务设计商家管理微服务是一个独立的RESTAPI应用&#xff0c;这个应用通过接口服务对外提供商家信息管理、商家权限管理和菜单资源管理等方面的功能。商家管理微服务开发在merchant-restapi模块中实现&#xff0c;有关这一类型模块的依赖引用、配置、启动程序的设计等…

html文字竖直书写,css 文字竖直居中的写法和图片垂直居中代码(图文)

界面上文章左右居中使用text-aligin:center&#xff0c;上下竖直居中的写法如下&#xff0c;图片垂直居中的代码文字、图片居中显示的方法.content{height: 40px;line-height: 40px;border:1px double #abc;}.myimg{/*非IE的主流浏览器识别的垂直居中的方法*/display: table-ce…

怎么判断到了月初_双春年与无春年怎么区分?2021年是寡妇年吗?

导读&#xff1a;双春年与无春年怎么区分&#xff1f;2021年是什么春年&#xff1f;就在昨天&#xff0c;我写了一篇关于寡妇年的文章&#xff0c;没想到大家热情高涨&#xff0c;关注度挺高的&#xff0c;还有的朋友私信问我这个双春年、有春年和无春年该怎么分&#xff0c;今…

乐橙本地录像回放不了_乐橙智能锁来告诉你:你家门真的安全吗?

你家门锁真的安全吗&#xff1f;你以为家门反锁就没事了&#xff1f;最近&#xff0c;看到一则新闻&#xff1a;女主人反锁门后出门喝喜酒&#xff0c;回家后发现家中遭窃&#xff0c;现金、珠宝首饰等全部遗失。还有那些专门行窃的小偷&#xff0c;他们会先按下房间门铃&#…

swift html编辑器,SwiftUI 的可视化编辑工具

原标题&#xff1a;SwiftUI 的可视化编辑工具作者&#xff1a;希德&#xff0c;iOS 开发者&#xff0c;前“有经验的前端开发工程师”&#xff0c;就职于网易严选。正在写书《Thinkable SwiftUI》(严重拖稿中)Session 10185: https://developer.apple.com/videos/play/wwdc2020…

如何备份数据_如何通过归档、备份和灾难恢复实现多云数据保护

点击上方“蓝色字体”&#xff0c;选择 “设为星标”关键讯息&#xff0c;D1时间送达&#xff01;为了提高业务连续性并更好地控制成本&#xff0c;企业将在2020年向IT专业人员施加压力&#xff0c;要求他们打破内部和外部基础设施之间以及公共云提供商离散环境之间的孤岛。为此…

清华大学计算机专业高中选课系统,【清华大学计算机实验教学中心】_清华大学计算机实验教学中心...

计算机实验教学中心面向全校所有专业本科生开课,每年开出计算机实验教学课超过7000人门,完成实验学时数逾750000.计算机学科是一门理论与实践结合得非常紧密的学科,因此实验教学中心的教学指导小组在教学实践中归纳出一套计算机学科特有的实验教学模式:除了独立设置的部分专题实…

树展示 移动端_百度移动端开始用网站品牌名代替网址显示

最近&#xff0c;有站长发现&#xff0c;百度移动端最近做了部分改版&#xff1a;移动端部分网站域名开始逐渐被网站相关名称代替&#xff0c;PC端还是用域名展示&#xff0c;卢松松博客网站域名也被替换成网站品牌名显示!点击添加图片描述&#xff08;最多60个字&#xff09;编…

分享到facebook没有封面图_拾柒自制书封面图分享~

最近大家都在写书 我自己写完收集了很多图片和文字素材现在拿出来分享给大家~希望你们可以用的到分享几张时候做封面的图片给大家哈&#xff01;还有特别多文字素材 大家需要也可以分享给大家呀&#xff01;祝大家都能幸福快乐❤️

桌面计算机盖帘,一种可调节高度的计算机显示器支架的制作方法

本实用新型涉及计算机硬件领域&#xff0c;尤其是一种可调节高度的计算机显示器支架。背景技术&#xff1a;随着科技的进步&#xff0c;社会的发展&#xff0c;人们的生活水平得到了很大的提高&#xff0c;计算机对人们已不再陌生&#xff0c;几乎每家必备一台。通常情况下&…