目录
效果展示
实现流程
关键代码
关键代码比较简单,这里我是使用官方提供的Java API实现的代码如下
//导入源图像
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,150.0,255.0,Imgproc.THRESH_BINARY)
//查找边缘
Imgproc.Canny(thresholdImage, cannyImage, 100.0, 200.0,3)
val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(3.0, 3.0)) //获取结构元素
//膨胀操作
Imgproc.dilate(cannyImage, cannyImage, kernel)
val hierarchy = Mat()
val contours: ArrayList = ArrayList()
//轮廓发现
Imgproc.findContours(
cannyImage,
contours,
hierarchy,
Imgproc.RETR_EXTERNAL,
Imgproc.CHAIN_APPROX_NONE
)
//找出最大轮廓
var rectMax = Rect()
var maxIndex = 0
for (i in 0 until contours.size) {
val rect = Imgproc.boundingRect(contours[i])
if(rect.width * rect.height > rectMax.width * rectMax.height){
rectMax = rect
maxIndex = i
}
}
//画出小票轮廓
val showContoursImg = Mat()
src.copyTo(showContoursImg)
Imgproc.drawContours(showContoursImg,contours,maxIndex,Scalar(255.0, 0.0, 0.0, 255.0),10, 8)
val contoursBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
Utils.matToBitmap(showContoursImg,contoursBitmap)
activityMainBinding.imgContours.setImageBitmap(contoursBitmap)
//找出最大轮廓的四个顶点
val points = contours[maxIndex]
//左上角顶点
var topLeftPoint:Point? = null
//右下角顶点
var bottomRightPoint:Point? = null
//右上角顶点
var topRightPoint:Point? = null
//左下角顶点
var bottomLeftPoint:Point? = null
points.toList().forEach {
//寻找右上角顶点
if(topRightPoint == null){
topRightPoint = it
}else{
if(topRightPoint!!.x / topRightPoint!!.y < it.x / it.y){
topRightPoint = it
}
}
//寻找右下角顶点
if(bottomRightPoint == null){
bottomRightPoint = it
}else{
if(bottomRightPoint!!.x + bottomRightPoint!!.y < it.x + it.y){
bottomRightPoint = it
}
}
//寻找左上角顶点
if(topLeftPoint == null){
topLeftPoint = it
}else{
if(topLeftPoint!!.x + topLeftPoint!!.y > it.x + it.y){
topLeftPoint = it
}
}
//寻找左下角顶点
if(bottomLeftPoint == null){
bottomLeftPoint = it
}else{
if(bottomLeftPoint!!.x / bottomLeftPoint!!.y > it.x / it.y){
bottomLeftPoint = it
}
}
}
//画出轮廓顶点
val showVerticesImg = Mat()
src.copyTo(showVerticesImg)
Imgproc.circle(showVerticesImg,topLeftPoint,10,Scalar(0.0, 255.0, 0.0, 255.0),50, 8)
Imgproc.circle(showVerticesImg,bottomRightPoint,10,Scalar(0.0, 255.0, 0.0, 255.0),50, 8)
Imgproc.circle(showVerticesImg,topRightPoint,10,Scalar(0.0, 255.0, 0.0, 255.0),50, 8)
Imgproc.circle(showVerticesImg,bottomLeftPoint,10,Scalar(0.0, 255.0, 0.0, 255.0),50, 8)
val verticesBitmap = Bitmap.createBitmap(bitmap.width,bitmap.height,bitmap.config)
Utils.matToBitmap(showVerticesImg,verticesBitmap)
activityMainBinding.imgVertices.setImageBitmap(verticesBitmap)
//进行透视变换
val fromPoints = MatOfPoint2f(topLeftPoint,topRightPoint,bottomLeftPoint,bottomRightPoint)
val toPoints = MatOfPoint2f(
Point(0.0,0.0),
Point(rectMax.width.toDouble(),0.0),
Point(0.0, rectMax.height.toDouble()),
Point(rectMax.width.toDouble(), rectMax.height.toDouble())
)
val transform = Imgproc.getPerspectiveTransform(fromPoints,toPoints)
//创建一个与小票一样比例的Bitmap用来存放最终效果图
val dstBitmap = Bitmap.createBitmap(rectMax.width,rectMax.height,bitmap.config)
//对二值化的图像进行变换(因为这样出来的图文字比较清晰)
Imgproc.warpPerspective(thresholdImage,thresholdImage,transform, Size(rectMax.width.toDouble(), rectMax.height.toDouble()))
Utils.matToBitmap(thresholdImage, dstBitmap) //将Mat转为Bitmap
activityMainBinding.imgDst.setImageBitmap(dstBitmap)
//释放资源
thresholdImage.release()
cannyImage.release()
src.release()
showContoursImg.release()
showVerticesImg.release()
案例源码
完整的代码如下:
https://gitee.com/itfitness/opencv-cutreceipts