银行卡识别-HMS

931 阅读8分钟

概述

对于APP需要支付功能,一般我们会优先集成支付宝和微信。当支付流水较多或用户有特殊需求的时候,我们就需要提供多种支付方式,最常见的是绑定银行卡进行扣款,这样可以进行大额交易,同时能够省下大笔手续费,还可以简化手续。银行卡绑定通常需要输入银行卡、开户人、身份证号、银行预留电话,有时候还需输入开户行等。银行卡号较长,手动输入容易出错,多次输入容易被锁定,而且容易导致用户体验差,甚至流失用户,所以银行卡自动识别至关重要。提供银行卡识别功能的厂家有很多,其中华为AI机器学习服务中银行卡识别服务可以将银行卡信息通过视频流方式输入,得到图像中银行卡的卡号、有效期等重要文本信息,并且支持视频流15度角进行识别。该服务配合身份证识别,可以为用户提供实名认证、身份审核、卡号录入等实用功能,降低输入成本,为用户提供更加友好的操作体验,而且免费。本文将介绍华为银行卡识别的使用。

使用

  1. 注册成为华为开发者。 在开发应用前需要在华为开发者联盟网站上注册成为开发者并完成实名认证,具体方法请参见帐号注册认证。若已注册,本步略。

  2. 创建应用。 参见创建项目在项目下添加应用完成应用的创建。若已创建,本步略。

  3. 开通服务。 使用ML Kit服务需要开发者在AppGallery Connect上打开ML Kit服务开关,具体操作步骤请参见开通服务。服务类型比较多,子服务嵌套层级较深,切勿找错。若已开通,本步略。

  4. 添加AGC配置文件。 如果在AppGallery Connect中开通了相关服务则需要将“agconnect-services.json”文件添加到您的App中。若已添加,请忽略,若没有添加,请参考添加AGC配置文件

  5. 配置Maven仓地址。 配置Maven仓地址就是在Android Studio项目级“build.gradle”文件中添加添加HUAWEI agcp插件以及Maven代码库:

buildscript {
    repositories {
        google()
        jcenter()
        maven { url 'https://developer.huawei.com/repo/' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.2'
        classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.2'
        classpath 'com.huawei.agconnect:agcp:1.4.1.300'

    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://developer.huawei.com/repo/' }
    }
}

maven { url 'developer.huawei.com/repo/' }不可忽略,否则相关依赖文件无法下载。

  1. 移除未使用的二进制文件(选)。 ML Kit同时支持32位和64位架构,如您只需要64位架构的二进制文件,可以参见如下方法移除32位的二进制文件:
android{
    defaultConfig{
        ndk.abiFilters 'arm64-v8a', 'x86_64'
    }
}
  1. 多语言设置。 当项目集成完后打开相应的页面发现全是英文,如果想要转换为中文,需要在应用级的“build.gradle”文件中配置语言:
android {
        defaultConfig {
                ...
                resConfigs "zh-rCN", "需要支持的其他语言"
        }
}  

另一种方式就是将所应用的字符串在本地strings.xml中再写一份自己希望的语言,操作相对复杂,不如第一种方式简单,但绝对有效。

  1. 配置混淆脚本。 为了避免混淆HMS Core SDK导致功能异常,开发者需要在编译APK前在Android工程的混淆配置文件proguard-rules.pro中配置混淆配置文件:
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
  1. 添加权限。 银行卡识别将会用到相机权限,网络权限,内存读写权限等,所以首先需要在AndroidManifest.xml文件中申请权限:
<!--相机权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--使用网络权限-->
<uses-permission android:name="android.permission.INTERNET" />
<!--写权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--读权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--录音权限-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--获取网络状态权限-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--获取wifi状态权限-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

其中有些权限需要在代码中动态申请,比如相机权限:

private static final int CAMERA_PERMISSION_CODE = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
...
    // 检查应用是否有相机权限。
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {    
        // 应用拥有相机权限。
        ...
    } else {  
        // 申请相机权限。
        requestCameraPermission();
    }
}
private void requestCameraPermission() {
    final String[] permissions = new String[]{Manifest.permission.CAMERA};   
    if (!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {
        ActivityCompat.requestPermissions(this, permissions, CAMERA_PERMISSION_CODE);        
        return;    
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
    if (requestCode != CAMERA_PERMISSION_CODE) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);       
        return;    
    }
    if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        return;    
   }
}

动态申请权限先检查权限,如果有就进行下一步,如果没有就申请权限,申请的同时需要给出申请的理由及拒绝后的后果,否则应用上不了架(最新的安全协议规定,还要有隐私安全协议)。申请成功后进行下一步,如果用户拒绝则给出后果。

  1. 添加银行卡识别依赖。 如果项目中已经集成了华为的一些服务,那么1-9都可以省略。添加依赖需要在应用级的“build.gradle”文件中配置依赖:
 dependencies{
      // 引入银行卡plugin与识别能力集合包。
      implementation 'com.huawei.hms:ml-computer-card-bcr:2.0.3.301'
  }

或者:

dependencies{
    // 引入银行卡识别plugin包
    implementation 'com.huawei.hms:ml-computer-card-bcr-plugin:2.0.3.301'
}

两个依赖的区别在于前者依赖包稍大,功能较强,适配较好,而后者依赖包比较小。 同时需要在文件头apply plugin: 'com.android.application'下一行添加如下配置:

apply plugin: 'com.huawei.agconnect'

最后在AndroidManifest.xml文件中进行相关配置从而可以自动更新机器学习模型到设备:

<manifest
    ...
    <meta-data
        android:name="com.huawei.hms.ml.DEPENDENCY"
        android:value= "bcr"/>
    ...
</manifest>
  1. 配置识别参数。 设置识别参数,调用识别器captureFrame接口进行识别:
private void startCaptureActivity(MLBcrCapture.Callback callback) {
    MLBcrCaptureConfig config = new MLBcrCaptureConfig.Factory()
        // 设置银行卡识别期望返回的结果类型。
        // MLBcrCaptureConfig.RESULT_NUM_ONLY:仅识别卡号。
        // MLBcrCaptureConfig.RESULT_SIMPLE:仅识别卡号、有效期信息。
        // MLBcrCaptureConfig.ALL_RESULT:识别卡号、有效期、发卡行、发卡组织和卡类别等信息。
        .setResultType(MLBcrCaptureConfig.RESULT_SIMPLE)
        // 设置识别界面横竖屏,支持三种模式:
        // MLBcrCaptureConfig.ORIENTATION_AUTO: 自适应模式,由物理感应器决定显示方向。
        // MLBcrCaptureConfig.ORIENTATION_LANDSCAPE: 横屏模式。
        // MLBcrCaptureConfig.ORIENTATION_PORTRAIT: 竖屏模式。
        .setOrientation(MLBcrCaptureConfig.ORIENTATION_AUTO)
        .create();
    MLBcrCapture bankCapture = MLBcrCaptureFactory.getInstance().getBcrCapture(config);
    bankCapture.captureFrame(this, callback);
}

通常我们会使用RESULT_SIMPLE(返回银行卡所有信息)和ORIENTATION_AUTO(自适应模式)。

  1. 创建识别回调。 发者可以直接调用银行卡识别插件,无需自己处理视频流,直接通过回调函数获取识别结果:
private MLBcrCapture.Callback callback = new MLBcrCapture.Callback() {
    @Override
    public void onSuccess(MLBcrCaptureResult bankCardResult){
        // 识别成功处理。
    }
    @Override
    public void onCanceled(){
        // 用户取消处理。
    } 
    // 识别不到任何文字信息或识别过程发生系统异常的回调方法。
    // retCode:错误码。
    // bitmap:检测失败的卡证图片。
    @Override
    public void onFailure(int retCode, Bitmap bitmap){
        // 识别失败处理。
  }
    @Override
    public void onDenied(){
        // 相机不支持等场景处理。
    }
};

MLBcrCaptureResult 就是识别的结果,具体包含信息如下:

public final class MLBcrCaptureResult {
   ...
    @KeepOriginal
    public int getErrorCode() {
        return this.a;
    }
    ...
    @KeepOriginal
    public String getNumber() {
        return this.b;
    }
    ...
    @KeepOriginal
    public String getIssuer() {
        return this.c;
    }
    ...
    @KeepOriginal
    public String getOrganization() {
        return this.d;
    }
    ...
    @KeepOriginal
    public String getExpire() {
        return this.e;
    }
    ...
    @KeepOriginal
    public Bitmap getOriginalBitmap() {
        return this.h;
    }
    ...
    @KeepOriginal
    public Bitmap getNumberBitmap() {
        return this.i;
    }
    ...
    @KeepOriginal
    public String getType() {
        return this.g;
    }
    ...
}

之所以贴出所有get方法是因为除了get方法可以看出具体是什么,其他都是a,b,c...之类的无法识别。具体参数说明如下:

方法说明
getErrorCode识别错误码
getNumber银行卡号
getIssuer银行卡开户行
getOrganization银行卡所属(银联等)
getExpire开卡日期
getOriginalBitmap银行卡照片
getNumberBitmap银行卡号照片
getType银行卡类型
实际代码结果如下:
银行卡识别结果

自此银行卡默认识别功能集成完毕。

  1. 效果展示。 结果 默认银行卡识别扫描页面不允许截屏或录屏,所以上图中没有展示。当涉及到敏感信息,开发者也可以在相关页面进行配置,不允许截屏或录屏:
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Window window = getWindow();
        window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
    }

WindowManager.LayoutParams.FLAG_SECURE就是禁止截屏的意思。

  1. 自定义布局(选)。
  • 如果需要对扫面界面进行定制,使用CustomView可以实现自定义扫面界面:
CustomView remoteView= new CustomView.Builder()
.setContext(this)
//设置扫描框矩形坐标,必须设置,否则无法识别。
.setBoundingBox(mScanRect)
// 设置银行卡识别期望返回的结果类型。
// MLBcrCaptureConfig.RESULT_NUM_ONLY:仅识别卡号。
// MLBcrCaptureConfig.RESULT_SIMPLE:仅识别卡号、有效期信息。
// MLBcrCaptureConfig.RESULT_ALL:识别卡号、有效期、发卡行、发卡组织和卡类别等信息。
.setResultType(MLBcrCaptureConfig.RESULT_SIMPLE)
// 设置识别结果的回调。
.setOnBcrResultCallback(callback).build();
  • 在回调中可以获得识别结果:
private CustomView.OnBcrResultCallback callback = new CustomView.OnBcrResultCallback() {
        @Override
        public void onBcrResult(MLBcrCaptureResult idCardResult) {
            if (idCardResult.getErrorCode() == 0 ){
                // 检测结果处理。
            } else {
                // 错误处理。
            }
        }
};
  • 扫描框坐标的获取:
Rect mScanRect = createScanRectFromCamera();
// 创建扫描框坐标。
private Rect createScanRectFromCamera() {
        Point point = getRealScreenSize();
        int screenWidth = point.x;
        int screenHeight = point.y;
        Rect rect = createScanRect(screenWidth, screenHeight);
        return rect;
}
// 获取真实屏幕尺寸。
private Point getRealScreenSize() {
        int heightPixels = 0;
        int widthPixels = 0;
        Point point = null;
        WindowManager manager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
 
        if (manager != null) {
           Display d = manager.getDefaultDisplay();
           DisplayMetrics metrics = new DisplayMetrics();
           d.getMetrics(metrics);
           heightPixels = metrics.heightPixels;
           widthPixels = metrics.widthPixels;
           if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) {
               try {
                   heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d);
                              widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d);
                              Log.i(TAG, "2 heightPixels=" + heightPixels + " widthPixels=" + widthPixels);
                     } catch (IllegalArgumentException e) {
                                        Log.w(TAG, "getRealScreenSize exception");
                               } catch (IllegalAccessException e) {
                                        Log.w(TAG, "getRealScreenSize exception");
                         } catch (InvocationTargetException e) {
                                        Log.w(TAG, "getRealScreenSize exception");
                                     } catch (NoSuchMethodException e) {
                                        Log.w(TAG, "getRealScreenSize exception");
                                     }
                } else if (Build.VERSION.SDK_INT >= 17) {
                           android.graphics.Point realSize = new android.graphics.Point();
                           try {
                              Display.class.getMethod("getRealSize", android.graphics.Point.class).invoke(d, realSize);
                              heightPixels = realSize.y;
                              widthPixels = realSize.x;
                           } catch (IllegalArgumentException e) {
                              Log.w(TAG, "getRealScreenSize exception");
                           } catch (IllegalAccessException e) {
                              Log.w(TAG, "getRealScreenSize exception");
                           } catch (InvocationTargetException e) {
                              Log.w(TAG, "getRealScreenSize exception");
                           } catch (NoSuchMethodException e) {
                              Log.w(TAG, "getRealScreenSize exception");
                           }
                }
        }
        Log.i(TAG, "getRealScreenSize widthPixels=" + widthPixels + " heightPixels=" + heightPixels);
        point = new Point(widthPixels, heightPixels);
        return point;
}
// 创建坐标信息。
private Rect createScanRect(int screenWidth, int screenHeight) {
        final float heightFactor = 0.8f;
        final float CARD_SCALE = 0.63084F;
        int width = Math.round(screenWidth * heightFactor);
        int height = Math.round((float) width * CARD_SCALE);
        int leftOffset = (screenWidth - width) / 2;
        int topOffset = (int) (screenHeight * TOP_OFFSET_RATIO) - height / 2;
        Rect rect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
        return rect;
}

总结

  • 本文中集成一共有14步,对于非第一次集成华为服务的开发者来讲,真正有用的是9、10、11、12步。如果是第一次集成只要按步骤进行不会耗费太多时间。
  • 华为银行卡识别代码控制简单,无需开发者处理相机图片问题,扫描速度较快,适用于多种网络,可自定义布局。

说明