Android架构师Android

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

2022-05-09  本文已影响0人  itfitness

目录

效果展示

实现步骤

实现步骤在代码中都有详细的注释,以及运行效果上也注明了每一步,因此这里就展示下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

上一篇下一篇

猜你喜欢

热点阅读