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