上篇我们大致了解了如何运用OpenCV在Android上进行图片基本特征的提取
Android OpenCV应用篇四:图片特征检测
本篇我们运用一些图片特征提取方法来做一个OCR目标纸片扫描修正的小工具。
参考:《深入OpenCV Android应用开发》
OCR目标纸片扫描修正效果
左边是我们相机实际拍摄图片,右边为处理过后的效果。OK,今天我们就来运用之前的知识来实现这样一个功能。
前言
开始之前,我们先来思考一下我们的大致有哪些步骤:
- 纸面识别
- 轮廓检测
- 角点检测
- 角点归位
- 透视变换
纸面识别
开始之前,为了提高效率,我们将图片进行缩放处理,并进行一次高斯模糊减少噪声:
val scalFactor = calcScaleFactor(srcOrig.rows(), srcOrig.cols())
val src = Mat()
Imgproc.resize(
srcOrig,
src,
Size(srcOrig.cols() / scalFactor.toDouble(), srcOrig.rows() / scalFactor.toDouble())
)
Imgproc.GaussianBlur(src, src, Size(5.0, 5.0), 1.0)
fun calcScaleFactor(rows: Int, cols: Int): Int {
var ideaRows = 0
var ideaCols = 0
if (rows < cols) {
ideaRows = 240
ideaCols = 320
} else {
ideaCols = 240
ideaRows = 320
}
val value = Math.min(rows / ideaRows, cols / ideaCols)
return if (value < 0) {
1
} else {
value
}
}
接下来,我们使用K-均值聚类算法对图像进行处理。(K-均值聚类算法),其效果将图片的背景与纸面有更加清晰的区别。
首先执行包含两个聚类中心的K均值聚类
val samples = Mat(src.rows() * src.cols(), 3, CvType.CV_32F)
for (y in 0 until src.rows()) {
for (x in 0 until src.cols()) {
for (z in 0 until 3)
samples.put(x + y * src.cols(), z, src.get(y, x)[z])
}
}
然后执行K-均值算法
val clusterCount = 2
val lables = Mat()
val attempts = 5
val centers = Mat()
Log.i("kmeans", "--------start--------")
Core.kmeans(
samples,
clusterCount,
lables,
TermCriteria(TermCriteria.MAX_ITER or TermCriteria.EPS, 10000, 0.0001),
attempts,
Core.KMEANS_PP_CENTERS,
centers
)
我们得到了两个聚类中心,并且原始图像中每个像素都有了标签,然后我们利用这两个聚类来检测哪一个是纸面。
找出两个中心的颜色与白色之间的欧式距离(欧式距离),较近的我们认为是纸面。
val center0 = calcWhiteDist(centers.get(0, 0)[0], centers.get(0, 1)[0], centers.get(0, 2)[0])
val center1 = calcWhiteDist(centers.get(1, 0)[0], centers.get(1, 1)[0], centers.get(1, 2)[0])
Log.i("calcWhiteDist", "--------end--------")
val paperCluter = if (center0 < center1) {
0
} else {
1
}
/**
* 计算距离
*/
fun calcWhiteDist(r: Double, g: Double, a: Double): Double {
return Math.sqrt(Math.pow(255 - r, 2.0) + Math.pow(255 - g, 2.0) + Math.pow(255 - a, 2.0))
}
进行图像分割,将前景显示为白色,将背景显示为黑色
val srcRes = Mat(src.size(), src.type())
val srcGray = Mat()
for (y in 0 until src.rows()) {
for (x in 0 until src.cols()) {
val clusterIdx = lables.get(x + y * src.cols(), 0)[0].toInt()
if (clusterIdx == paperCluter) {
srcRes.put(y, x, 0.0, 0.0, 0.0, 255.0)
} else {
srcRes.put(y, x, 255.0, 255.0, 255.0, 255.0)
}
}
}
处理后我们会得到的图像:
对比差别可以看出,我们将灰色的背景全部设置成来纯黑色,将纸面设置成来纯白色。
至此,我们已经可以从图像中识别出背景与纸面
轮廓检测
接下来我们进行轮廓检测。
Log.i("Canny", "--------start--------")
Imgproc.cvtColor(srcRes, srcGray, Imgproc.COLOR_BGR2GRAY)
Imgproc.Canny(srcGray, srcGray, 50.0, 150.0)
Log.i("Canny", "--------end--------")
Log.i("findContours", "--------start--------")
val contours = ArrayList<MatOfPoint>()
val hierarchy = Mat()
Imgproc.findContours(srcGray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE)
Log.i("contours", "${contours.size}")
Log.i("findContours", "--------end--------")
Log.i("contourArea", "--------start--------")
var index = 0
var maxim = Imgproc.contourArea(contours[0])
for (contourIdx in 0 until contours.size) {
val temp = Imgproc.contourArea(contours[contourIdx])
if (maxim < temp) {
maxim = temp
index = contourIdx
}
}
Log.i("contourArea", "--------end--------")
val drawing = Mat.zeros(srcRes.size(), CvType.CV_8UC1