美文网首页Android架构师Android
Android OpenCV + tess-two 实现卡片号码

Android OpenCV + tess-two 实现卡片号码

作者: itfitness | 来源:发表于2022-05-09 11:30 被阅读0次

    目录

    效果展示

    实现步骤

    实现步骤在代码中都有详细的注释,以及运行效果上也注明了每一步,因此这里就展示下Activity的代码吧,详细了解可以查看案例源码

    class MainActivity : AppCompatActivity() {
        private lateinit var activityMainBinding: ActivityMainBinding
        private var ocrDataFilePath = "" //数据识别的文件路径
        @SuppressLint("HandlerLeak")
        private val handler = object : Handler() {
            override fun handleMessage(msg: Message) {
                val number:String = msg.obj as String
                LogUtils.eTag("识别结果",number)
                activityMainBinding.etOcrnumber.setText(number)
                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.img)
                        val src = Mat()
                        Utils.bitmapToMat(bitmap, src) //将bitmap转换为Mat
                        val thresholdImage = Mat(src.size(),src.type())
                        val cannyImage = Mat(src.size(),src.type())
                        //将图像转换为灰度图像
                        Imgproc.cvtColor(src, thresholdImage, Imgproc.COLOR_RGBA2GRAY)
                        //将图像转换为边缘二值图像
                        Imgproc.threshold(thresholdImage,thresholdImage,50.0,255.0, Imgproc.THRESH_BINARY)
    
                        //闭操作去掉多余的杂点
                        var kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(6.0, 6.0)) //获取结构元素
                        Imgproc.morphologyEx(thresholdImage, thresholdImage, Imgproc.MORPH_CLOSE, kernel)
                        //显示当前阶段效果图像
                        val binaryBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
                        Utils.matToBitmap(thresholdImage,binaryBitmap)
                        activityMainBinding.imgBinary.setImageBitmap(binaryBitmap)
    
                        //开操作让数字联结到一起方便查出数字的位置
                        kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(60.0, 60.0)) //获取结构元素
                        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(3.0, 3.0)) //获取结构元素
                        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 numberRectsTemp = ArrayList<Rect>()
                        contours.forEach {
                            val rect = Imgproc.boundingRect(it)
                            //根据数字连起来的矩形的宽高比来找出四组数字(这里手动计算大概为2.5到3之间)
                            val ratio = rect.width.toDouble() / rect.height.toDouble()
                            if(ratio in 2.5..3.0){
                                numberRectsTemp.add(rect)
                            }
                        }
                        //对矩形轮廓进行排序
                        numberRectsTemp.sortByDescending { it.width * it.height }
                        //去掉一些小的轮廓,只留下数字的轮廓
                        val numberRects = numberRectsTemp.take(4)
    
                        //画出四组数字位置
                        val showNumberRectImg = Mat()
                        src.copyTo(showNumberRectImg)
                        numberRects.forEach {
                            Imgproc.rectangle(showNumberRectImg,it,Scalar(0.0, 255.0, 0.0, 255.0),10, 8)
                        }
                        Utils.matToBitmap(showNumberRectImg,bitmap)
                        activityMainBinding.imgNumrect.setImageBitmap(bitmap)
    
    
                        //对四块区域进行排序,排出正确的顺序
                        val numberRectSorted = numberRects.sortedBy { it.x}
    
                        //对整个数字区域进行透视变换(这里适合每组都进行透视变换,我这为了省事就没多写)
                        val firstNumberRect = numberRectSorted[0]
                        val lastNumberRect = numberRectSorted[numberRectSorted.size - 1]
                        val fromPoints = MatOfPoint2f(
                            Point(firstNumberRect.x.toDouble(), firstNumberRect.y.toDouble()),
                            Point((lastNumberRect.x + lastNumberRect.width).toDouble(),
                                lastNumberRect.y.toDouble()),
                            Point(firstNumberRect.x.toDouble(),(firstNumberRect.y + firstNumberRect.height).toDouble()),
                            Point((lastNumberRect.x + lastNumberRect.width).toDouble(),
                                (lastNumberRect.y + lastNumberRect.height).toDouble())
                        )
                        val toPoints = MatOfPoint2f(
                            Point(0.0,0.0),
                            Point((lastNumberRect.x + lastNumberRect.width - firstNumberRect.x).toDouble(),0.0),
                            Point(0.0, firstNumberRect.height.toDouble()),
                            Point((lastNumberRect.x + lastNumberRect.width - firstNumberRect.x).toDouble(), firstNumberRect.height.toDouble())
                        )
                        val transform = Imgproc.getPerspectiveTransform(fromPoints,toPoints)
                        //创建一个与小票一样比例的Bitmap用来存放最终效果图
                        val dstBitmap = Bitmap.createBitmap(lastNumberRect.x + lastNumberRect.width - firstNumberRect.x,firstNumberRect.height,bitmap.config)
                        //对二值化的图像进行变换(因为这样出来的图文字比较清晰)
                        Imgproc.warpPerspective(thresholdImage,thresholdImage,transform, Size((lastNumberRect.x + lastNumberRect.width - firstNumberRect.x).toDouble(), firstNumberRect.height.toDouble()))
                        Utils.matToBitmap(thresholdImage, dstBitmap) //将Mat转为Bitmap
                        activityMainBinding.imgDst.setImageBitmap(dstBitmap)
    
    
                        ocrNumber(dstBitmap)
    
    
                        //释放资源
                        thresholdImage.release()
                        cannyImage.release()
                        src.release()
                        showNumberRectImg.release()
                    }
                    else -> {
                        super.onManagerConnected(status)
                    }
                }
            }
        }
    
        /**
         *
         */
        private fun ocrNumber(dstBitmap: Bitmap) {
            thread {
                initOcrData()
                if(!TextUtils.isEmpty(ocrDataFilePath)){
                    // 开始调用Tess函数对图像进行识别
                    val tessBaseAPI = TessBaseAPI()
                    tessBaseAPI.setDebug(true)
                    tessBaseAPI.init(ocrDataFilePath, "enm")
                    tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "0123456789") // 识别白名单
                    tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+=-[]}{;:'\"\\|~`,./<>?") // 识别黑名单
                    tessBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO)//设置识别模式
                    tessBaseAPI.setImage(dstBitmap)//设置需要识别图片的bitmap
                    val number = tessBaseAPI.getUTF8Text()
                    val msg = Message()
                    msg.obj = number
                    handler.sendMessage(msg)
                    tessBaseAPI.end()
                }
            }
        }
    
        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.enm)
            getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.let {
                ocrDataFilePath = it.absolutePath
                val ocrDataFile = File("${ocrDataFilePath}${File.separator}tessdata${File.separator}enm.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)
            }
        }
    }
    

    补充

    tess-two用到的识别数据是从这里下载的:https://github.com/tesseract-ocr/tessdata

    案例源码

    https://gitee.com/itfitness/opencv-cardocr

    相关文章

      网友评论

        本文标题:Android OpenCV + tess-two 实现卡片号码

        本文链接:https://www.haomeiwen.com/subject/yohhurtx.html