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 = ArrayList()
//轮廓发现
Imgproc.findContours(
cannyImage,
contours,
hierarchy,
Imgproc.RETR_EXTERNAL,
Imgproc.CHAIN_APPROX_NONE
)
//找出信息所在的轮廓
val allRect = ArrayList()
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(allRect.take(allRect.size - 1))
//对矩形轮廓进行排序(姓名往下依次排列)
val c1: Comparator = 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) {
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(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