思路
建议:为了更好的发现梯度之间的差异,尽量在 RGB色彩空间寻找梯度。然后对RGB空间寻找到的梯度,可以取RGB三个通道中最大值的梯度差,作为那一个像素点的梯度。同时,也可以直接用求取出来的梯度图像进行灰度化,再进行梯度二值化。(简而言之:先在RGB空间求梯度,然后灰度化,然后二值化)
对比:为什么不先转灰度,再求梯度呢?
理由:先求梯度,可以最大限度的保留各个通道值间的差异。更能反映出图像本来梯度的分布。若先转灰度,则在变成灰度图像的过程中OpenCV默认采取的算法会基于一个权重的考虑(一般对绿色比较敏感,也就是说对红色或者绿色权重不一样)。这样也就是说 权重和梯度稍微有一定的差异。这些细微的差异可能对后面的 轮廓发现、过滤 等操作造成影响。
-
第一步:图像梯度(色差)求取
用Scharr算子求取梯度(建议用Scharr算子,而不是Sobel算子。原因:Scharr算子对边缘提取效果比Sobel算子强。且Sobel算子容易受到一些其他的外来因素干扰。详细可以参考 OpenCV-图像处理(17、Sobel算子)) -
第二步:梯度二值化(相关知识点,可以参考:OpenCV-图像处理(14、基本阈值操作))
-
第三步:轮廓发现与过滤(相关知识点,可以参考:OpenCV-图像处理(28、轮廓发现(find contour in your image)))
轮廓发现与面积大小过滤:findContours 。在此之前需要对图像进行开操作,目的:减少轮廓发现的计算量。更快的发现面积。
程序构成
- 一般程序设计符合 MVC 开发框架。除了上次创建的两个Activity 以外,现将识别算法放入一个新的Java文件中去(app->右击 新建一个Package和Java类TextImageProcessor,然后在类中写我们的识别算法)
代码实现
- TextImageProcessor 类中的代码
public class TextImageProcessor {
private String TAG = "OCR-Algorithm";
// 寻找和发现银行卡区域
public Mat findCard(Uri fileUri){
// 读取BGR色彩空间的图像
Mat src = Imgcodecs.imread(fileUri.getPath());
if(src.empty()) {
return null;
}
// 求取梯度
Mat grad_x = new Mat();//X方向的梯度
Mat grad_y = new Mat();//Y方向的梯度
Mat abs_grad_x = new Mat();//梯度求出来的值可能是负值也可能会超出255,导致溢出。
Mat abs_grad_y = new Mat();
Imgproc.Scharr(src, grad_x, CvType.CV_32F, 1, 0);//求取梯度
Imgproc.Scharr(src, grad_y, CvType.CV_32F, 0, 1);
Core.convertScaleAbs(grad_x, abs_grad_x);//求绝对值
Core.convertScaleAbs(grad_y, abs_grad_y);
grad_x.release();//释放内存,珍惜内存
grad_y.release();
Mat grad = new Mat();//合成整体梯度图像
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
abs_grad_x.release();//释放内存
abs_grad_y.release();
//return grad;
// 二值化图像
Mat binary = new Mat();
Imgproc.cvtColor(grad, binary, Imgproc.COLOR_BGR2GRAY);//灰度化
//Imgproc.threshold(binary, binary, 40, 255, Imgproc.THRESH_OTSU | 1);//传参设置阈值类型
Imgproc.threshold(binary, binary, 40, 255, Imgproc.THRESH_BINARY);//二值化,40是一个经验值
grad.release();
//return binary;//将二值化的图像返回,查看过程图像
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));//定义算子
Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_OPEN, kernel);//形态学 开操作
List<MatOfPoint> contours = new ArrayList<>();// 每个轮廓都有N多个点组成
Mat hireachy = new Mat();
// 寻找轮廓
Imgproc.findContours(binary, contours, hireachy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0));
int hw = binary.cols()/2;
int hh = binary.rows()/2;
Rect roi = new Rect();
for(int i=0; i<contours.size(); i++) {
//循环裁剪图片,直至图片剩余度主要是待识别区域
Rect rect = Imgproc.boundingRect(contours.get(i));
if(rect.width > hw) {
roi.x <