OpenCV处理拍照表格(一)
环境配置
https://www.learn2crack.com/2016/03/setup-opencv-sdk-android-studio.html
非常新的一篇在AS中安装OpenCV
的教程,按教程装好了环境并测试通过。
注意教程中没有讲到的是想要使用OpenCV
的相关功能,需要安装下载包中apk目录下的对应处理器的OpenCV manager
。并在使用OpenCV
的活动中加入以下内容:
1 | private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { |
1 | //Initialize at every resume |
http://docs.opencv.org/java/3.1.0/>
opencv的官方文档。
更正:上面的3.1版的文档并没有详细的方法解释!
所以只能看
http://docs.opencv.org/java/2.4.11/
2.4.11的。
处理的大致思路
目前想到的思路是:
- 图像彩色转灰度
- 灰度图像设置阈值后二值化即变成完全黑白
- 去除多余的噪点
- 边缘识别
- 透视变形
- 矩形识别
- 分割识别出的矩形
- OCR对矩形进行识别,读取数据。
实现
灰度与二值化
开始采用OpenCV中BitmapToMat
方法,将文件以Bitmap
的形式读取,再转换为Mat
格式再进行处理。
后面发现了OpenCV自带的imread
方法,传入文件路径和Mat
的格式后就可以方便地获得一个Mat
对象。
另外如果在这里采用Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE
标签,就可以直接以灰度的形式读取图像,省去了颜色转化的步骤。并且这个Mat
的格式就是下面一步二值化要求传入的8UC1
(8位单通道)格式。
下面就是二值化步骤,OpenCV
提供了两个函数,第一个是普通的Threshold
函数:
threshold
public static double threshold(Mat src,
Mat dst,double thresh,double maxval,int type)
Parameters:
src
- input array (single-channel, 8-bit or 32-bit floating point).
dst
- output array of the same size and type assrc
.
thresh
- threshold value.
maxval
- maximum value to use with theTHRESH_BINARY
andTHRESH_BINARY_INV
thresholding types.
type
- thresholding type (see the details below).
传入图像,传出图像,阈值,填充的最深颜色,填充方法(达到阈值就填充最深颜色或相反),就可以根据每个像素的灰度值与阈值进行比较来决定填充的值为0或是最深。
定阈值的方法虽然可以对一张图像通过调整达到最优的效果,但是对于不同光照条件下拍摄出来的照片,因为整体亮度的不同,定阈值显然无法适应所有的情况。
所以就有了第二种函数,adaptiveThreshold
,除了传入上面的这些参数外,增加了三个重要的参数
adaptiveThreshold
public static void adaptiveThreshold(Mat src,Mat dst,double maxValue,int adaptiveMethod,int thresholdType,int blockSize,double C)
Parameters:src
- Source 8-bit single-channel image.dst
- Destination image of the same size and the same type assrc
.maxValue
- Non-zero value assigned to the pixels for which the condition is satisfied. See the details below.adaptiveMethod
- Adaptive thresholding algorithm to use,ADAPTIVE_THRESH_MEAN_C
orADAPTIVE_THRESH_GAUSSIAN_C
. See the details below.thresholdType
- Thresholding type that must be eitherTHRESH_BINARY
orTHRESH_BINARY_INV
.blockSize
- Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on.C
- Constant subtracted from the mean or weighted mean (see the details below). Normally, it is positive but may be zero or negative as well.
blockSize
:对某个像素周围进行采样的范围。adaptiveMethod
:根据上面的范围求阈值的方法,有两种:
- mean平均,简单地取采样范围内的平均值作为阈值。
- gaussian高斯,以高斯函数为基础,简单地说就是近的地方权重更高、远的地方权重低,来求阈值。
C
:求出来的阈值减去的常量。
这三个参数就是决定二值化效果的关键,我找最优值的方法比较暴力,写了一个嵌套的循环设置这两个值,再输出到文件导出到电脑上用肉眼比较。最后确定的值为17-10。
adaptiveMethod
我用的是mean
,因为表格相对来说黑白比较明显,并不需要去根据距离的远近来决定阈值。
17这个值在我的手机拍摄出来的效果里面是最好的,但由于不同拍摄设备的分辨率不同,就造成了笔画所占据的像素的数量的不同,可以想到的是在高分辨率的情况下这个值应该要相应地增大,打个比方说我一个笔画的粗细就有17个像素,那么这个范围内检测出的阈值就会非常高从而导致笔画的残缺不全。
C
这个值还是需要经过测试来得出的,设置的不同对最后效果的影响是最大的,直接会决定最后出来的图片是笔画过粗或是笔画残缺。
下面是处理前后的效果:
可以看到二值化以后的图像只有黑白两色,但是明显有许多的噪点。
去噪
二值化之后就是去噪了,去噪的目的是把图像中的独立的点去掉。
去噪的方法是腐蚀,跟字面意思一样,就是缩小图案的范围,当图像的范围本身就很小时(噪点就是一个个这样的独立点),缩小后自然就不见了。
可以想到,在去噪后,部分笔画也随之缩小甚至细的地方会直接消失,所以腐蚀之后要再进行一步膨胀,即把图案的边缘扩大。
因为噪点已经消失,所以也不会因扩大而回来,但笔画依然存在,就会膨胀而得到弥补,也顺便可以补一下残缺的地方。
原理大概就是这样,但是由于OpenCV的这两个操作针对的是图像中的亮点(白色的地方被认为是亮点),而我们的表格又是白底黑字的,实际上黑色的部分是我们想要处理的部分,所以我将这两步交换了,相当于是对黑色的地方先腐蚀后膨胀。
下面是代码实现:
1 | Mat kernelDilate = Imgproc.getStructuringElement(Imgproc.MORPH_DILATE, new Size(2, 2)); |
参数中有一个Kernel
,这个就是处理图像的核,具体的内容不展开,我们利用getStructuringElement
函数可以构建特定的处理核,这个函数第一个参数是构建的核的类型,除了用于代码中用到的膨胀和腐蚀的类型,还有ract
、cross
、ellipse
等不同的形状,后面的size
就是我们设置的重点了,指的是核的大小,可以理解成检测的范围,对于大的噪点自然需要大的范围,但是也意味着笔画细节丢失也更加严重。对于膨胀操作,则可以理解成膨胀的像素数,这个数值越大,最后的结果中的笔画也就越粗。
下面是上述代码的结果对比:
之前
设置了四种不同的参数,可以看到噪点基本都被去除,最后的细节不尽相同。