Android OpenCV + tess-two 实现身份证识别

目录

效果展示

关键代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
            <TextView
                android:text="源图像"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:src="@drawable/ic_img"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:text="进行二值化处理,并通过闭操作去除多余噪点"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/img_binary"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:text="开操作,让数字联结到一起,方便查找轮廓"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/img_coupling"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:text="查找出信息所在的轮廓"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/img_contours"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:text="找出信息所在的外接矩形"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/img_numrect"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:text="最后识别结果如下"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/img_user"
                android:layout_marginTop="20dp"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
            <TextView
                android:id="@+id/tv_ocrinfo"
                android:layout_marginBottom="20dp"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:layout_gravity="center_horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </ScrollView>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var activityMainBinding: ActivityMainBinding
    private var ocrDataFilePath = "" //数据识别的文件路径
    private var ocrInfoText:StringBuilder = StringBuilder()
    @SuppressLint("HandlerLeak")
    private val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            when(msg.what){
                0->{
                    val info:String = msg.obj as String
                    ocrInfoText.appendLine(info)
                }
                1->{
                    //识别成功
                    activityMainBinding.tvOcrinfo.text = ocrInfoText.toString()
                }
            }

            super.handleMessage(msg)
        }
    }
    private val mLoaderCallback: BaseLoaderCallback = object : BaseLoaderCallback(this) {
        override fun onManagerConnected(status: Int) {
            when (status) {
                SUCCESS -> {
                    //导入源图像
                    val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_img)

                    val src = Mat()
                    Utils.bitmapToMat(bitmap, src) //将bitmap转换为Mat
                    val thresholdImage = Mat(src.size(),src.type()) //这个二值图像用于找出关键信息的图像
                    val thresholdImageOcr = Mat(src.size(),src.type()) //这个二值图像用于识别信息
                    val cannyImage = Mat(src.size(),src.type())
                    //将图像转换为灰度图像
                    Imgproc.cvtColor(src, thresholdImage, Imgproc.COLOR_RGBA2GRAY)
                    //将图像转换为边缘二值图像
                    Imgproc.threshold(thresholdImage,thresholdImageOcr,140.0,255.0, Imgproc.THRESH_BINARY)
                    Imgproc.threshold(thresholdImage,thresholdImage,100.0,255.0, Imgproc.THRESH_BINARY)

                    //闭操作去掉多余的杂点
                    var kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(bitmap.width * 0.002, bitmap.width * 0.002)) //获取结构元素
                    Imgproc.morphologyEx(thresholdImage, thresholdImage, Imgproc.MORPH_CLOSE, kernel)
                    //显示当前阶段效果图像
                    val binaryBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
                    Utils.matToBitmap(thresholdImageOcr,binaryBitmap)
                    activityMainBinding.imgBinary.setImageBitmap(binaryBitmap)

                    //开操作让数字联结到一起方便查出数字的位置
                    kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(bitmap.width * 0.05, bitmap.width * 0.036)) //获取结构元素
                    Imgproc.morphologyEx(thresholdImage, cannyImage, Imgproc.MORPH_OPEN, kernel)
                    //显示当前阶段效果图像
                    val couplingBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
                    Utils.matToBitmap(cannyImage,couplingBitmap)
                    activityMainBinding.imgCoupling.setImageBitmap(couplingBitmap)


                    //查找边缘
                    Imgproc.Canny(cannyImage, cannyImage, 100.0, 200.0,3)

                    //膨胀让边缘更明显
                    kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(bitmap.width * 0.0036, bitmap.width * 0.0036)) //获取结构元素
                    Imgproc.dilate(cannyImage, cannyImage, kernel) //膨胀操作
                    //显示当前阶段效果图像
                    val contoursBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
                    Utils.matToBitmap(cannyImage,contoursBitmap)
                    activityMainBinding.imgContours.setImageBitmap(contoursBitmap)

                    val hierarchy = Mat()
                    val contours: ArrayList<MatOfPoint> = ArrayList()
                    //轮廓发现
                    Imgproc.findContours(
                        cannyImage,
                        contours,
                        hierarchy,
                        Imgproc.RETR_EXTERNAL,
                        Imgproc.CHAIN_APPROX_NONE
                    )

                    //找出信息所在的轮廓
                    val allRect = ArrayList<Rect>()
                    contours.forEach {
                        val rect = Imgproc.boundingRect(it)
                        //left为0的是不正确的
                        if(rect.x != 0 && rect.x + rect.width != src.width()){
                            //对区域进行小量的扩充,方便识别数据
                            rect.x = rect.x - 10
                            rect.y = rect.y - 10
                            rect.width = rect.width + 20
                            rect.height = rect.height + 20
                            allRect.add(rect)
                        }
                    }
                    //对包括头像的全部区域进行排序(头像排在最后)
                    allRect.sortByDescending { -(it.x + it.width) }
                    //展示头像区域
                    val userIconRect = allRect[allRect.size - 1]
                    val userBitmap = Bitmap.createBitmap(bitmap,userIconRect.x,userIconRect.y,userIconRect.width,userIconRect.height)
                    activityMainBinding.imgUser.setImageBitmap(userBitmap)
                    //剔除头像区域(方便后面的OCR识别)
                    val infoRect = ArrayList<Rect>(allRect.take(allRect.size - 1))

                    //对矩形轮廓进行排序(姓名往下依次排列)
                    val c1: Comparator<Rect> = Comparator { o1, o2 ->
                        if (abs(o1.y - o2.y) <= 10) {
                            //可容误差为10(因为有些识别的区域y轴存在小量偏移,比如性别和民族)
                            //当y轴在同一位置的时候,比较x轴
                            o1.x - o2.x
                        } else {
                            o1.y - o2.y
                        }
                    }
                    infoRect.sortWith(c1)

                    //画出信息所在的位置
                    val showInfoRectImg = Mat()
                    src.copyTo(showInfoRectImg)
                    infoRect.forEach {
                        Imgproc.rectangle(showInfoRectImg,it,Scalar(0.0, 255.0, 0.0, 255.0),(bitmap.width * 0.003).toInt(), 8)
                    }
                    Utils.matToBitmap(showInfoRectImg,bitmap)
                    activityMainBinding.imgNumrect.setImageBitmap(bitmap)


                    ocrInfo(binaryBitmap,infoRect)


                    //释放资源
                    thresholdImage.release()
                    thresholdImageOcr.release()
                    cannyImage.release()
                    src.release()
                    showInfoRectImg.release()

                }
                else -> {
                    super.onManagerConnected(status)
                }
            }
        }
    }

    /**
     * 根据信息所在的位置,识别信息
     */
    private fun ocrInfo(dstBitmap: Bitmap, infoRect: ArrayList<Rect>) {
        thread {
            initOcrData()
            if(!TextUtils.isEmpty(ocrDataFilePath)){
                // 开始调用Tess函数对图像进行识别
                val tessBaseAPI = TessBaseAPI()
                tessBaseAPI.setDebug(true)
                tessBaseAPI.init(ocrDataFilePath, "chi_sim")
//                tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "0123456789") // 识别白名单
                tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+=-[]}{;:'\"\\|~`,./<>?…】丨") // 识别黑名单
                tessBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_SINGLE_WORD)//设置识别模式

                infoRect.forEach {
                    val ocrBitmap = Bitmap.createBitmap(dstBitmap,it.x,it.y,it.width,it.height)
                    //当识别的字为单个字符的时候,切换识别模式为单字符的,识别的比较准确(这里设置宽高比只要小于1.5就是单字符)
                    //用大的值除以小的值,这样才不至于产生小于0的值
                    val maxVal = max(ocrBitmap.width,ocrBitmap.height).toDouble()
                    val minVal = min(ocrBitmap.width,ocrBitmap.height).toDouble()
                    if(maxVal / minVal <= 1.5){
                        tessBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_SINGLE_WORD)//设置识别模式
                    }else{
                        tessBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO)//设置识别模式
                    }
                    tessBaseAPI.setImage(ocrBitmap)//设置需要识别图片的bitmap
                    val number = tessBaseAPI.utF8Text
                    val msg = Message()
                    msg.what = 0
                    msg.obj = number
                    handler.sendMessage(msg)
                }
                tessBaseAPI.end()
                handler.sendEmptyMessage(1)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
    }

    /**
     * 加载数据识别的文件
     */
    private fun initOcrData() {
        val ocrDataStream = resources.openRawResource(R.raw.chi_sim)
        getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.let {
            ocrDataFilePath = it.absolutePath
            val ocrDataFile = File("${ocrDataFilePath}${File.separator}tessdata${File.separator}chi_sim.traineddata")
            if(!ocrDataFile.exists()){
                FileIOUtils.writeFileFromIS(ocrDataFile,ocrDataStream)
            }
        }
    }

    override fun onResume() {
        super.onResume()
        if (!OpenCVLoader.initDebug()) {
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback)
        } else {
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS)
        }
    }
}
案例源码

https://gitee.com/itfitness/opencv-idcard-ocr

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值