JAVA,OpenCV简单实现“全能扫描王”的功能

1 篇文章 0 订阅
1 篇文章 0 订阅

前段时间自己在研究行人检测方面的东西,用的是OpenCV,在网上查询相关资料的时候看到一些文章介绍如何使用OpenCV实现类似“全能扫描王”的功能。最近正好有空闲时间,于是花了几天时间研究了下,现在记录下在研究过程中自己的一些心得,希望对各位有些许帮助。

先分享一篇相关文章,大家可以看下:http://www.cnblogs.com/skyfsm/p/7324346.html

一、准备工作

1)、下载OpenCV的jar包,maven项目在pom中加如下节点

<dependency>
    <groupId>org.bytedeco.javacpp-presets</groupId>
    <artifactId>opencv</artifactId>
    <version>3.4.3-1.4.3</version>
</dependency>

2)、下载OpenCV的dll

可以在OpenCV官网下载3.4.3的完整安装包,从里面找到opencv_java343.dll,也可以在https://download.csdn.net/download/rwzhang/10887211这里下载单独的dll

3)、将dll加入到项目中

如图所示,选择你存放dll的文件夹

注:在项目启动入口加代码 System.loadLibrary(Core.NATIVE_LIBRARY_NAME),否则无法运行

二、大致实现步骤

整个过程步骤大致如下:

1、图像预处理:对图像进行高斯模糊、二值化、闭运算等操作,使我们可以更方便的找到需要的轮廓

2、定位4个角的坐标:找到轮廓后定位轮廓的4个角

3、透视变换:得到轮廓的4个角后对目标进行透视变换

以上3个步骤想要实现得完美还是非常复杂的,例如以下情况:

如果我们要定位下面这张图片的4个角

原图

一种办法就是图像处理得非常好,每个角都非常清晰,这样就比较方便定位,但是很多情况下处理后得到的轮廓是这样的

很明显,左下角缺了一块,这样的话我们定位出来的角就有可能不准确,见下图圆圈圈住的地方

我们实际需要的是这样的

还有一个比较复杂的问题就是如何获取目标对象真实的高度和宽度,比如下面这张图

我们很难得到这张纸真实的高度,无法按照真实的比例进行透视变换。网上有文章说需要通过“相机标定”去解决这个问题,暂时没有时间去研究。

等会下面我会着重说一下4角定位,相机标定这个问题以后有时间了再看下。

三、相关代码

1)、图像预处理及获取对象的轮廓

//读取原始图像
Mat src = Imgcodecs.imread(input);
Mat dst = new Mat();
//Imgproc.pyrMeanShiftFiltering(src, dst, 50, 10);//均值偏移
//output(TMP_FOLDER + "/0_meanshift.jpg", dst);
	
Mat kernel = new Mat(3, 3, CvType.CV_32F,new Scalar(-1)); 
kernel.put(1, 1, 8.9);
Imgproc.filter2D(src, dst, src.depth(),kernel);//锐化
output(TMP_FOLDER + "/" + (i++) + "_sharpening.jpg", dst);
		
Imgproc.cvtColor(dst, dst, Imgproc.COLOR_RGB2GRAY);//灰度化
output(TMP_FOLDER + "/" + (i++) + "_gray.jpg", dst);
		
//Imgproc.equalizeHist(dst, dst);//直方图均衡化
//output(TMP_FOLDER + "/" + (i++) + "_equalizeHist.jpg", dst);
		
ImageUtil.gammaCorrection(dst, dst, 0.8f);//gamma校正
output(TMP_FOLDER + "/" + (i++) + "_gamma.jpg", dst);
		
Imgproc.GaussianBlur(dst, dst, new Size(5, 5), 0, 0);//高斯滤波
output(TMP_FOLDER + "/" + (i++) + "_gaussianBlur.jpg", dst);
		
Imgproc.threshold(dst, dst, 0, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);//二值化
output(TMP_FOLDER + "/" + (i++) + "_thresholding.jpg", dst);

Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
		
//Imgproc.dilate(dst, dst, element);//膨胀
//output(TMP_FOLDER + "/" + (i++) + "_dilate.jpg", dst);
		
Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_CLOSE, element);//闭运算
output(TMP_FOLDER + "/" + (i++) + "_morph_close.jpg", dst);
		
//有些图像多做次腐蚀检测边缘的效果感觉更好些
Imgproc.erode(dst, dst, element);//腐蚀
output(TMP_FOLDER + "/" + (i++) + "_erode.jpg", dst);
		
Imgproc.Canny(dst, dst, 30, 120, 3);//边缘检测
output(TMP_FOLDER + "/" + (i++) + "_canny.jpg", dst);
		
//查找轮廓
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat(); 
Imgproc.findContours(dst, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
		
//加粗增强所有找到的轮廓
Imgproc.drawContours(dst, contours, -1, new Scalar(255), 3);
output(TMP_FOLDER + "/" + (i++) + "_strong.jpg", dst);
		
//再次查找轮廓
contours.clear();
hierarchy = new Mat(); 
Imgproc.findContours(dst, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
MatOfPoint mpoint = getMaximum(contours);
contours.clear();
contours.add(mpoint);
//画出唯一轮廓
dst.setTo(new Scalar(0));//填充为黑色
Imgproc.drawContours(dst, contours, -1, new Scalar(255, 255, 255), 3);
output(TMP_FOLDER + "/" + (i++) + "_lastContours.jpg", dst);

2)、定位要抽取对象的4个角的坐标

我们通过霍夫变换获得4条边包含的所有线段,当时看了很多文章都是直接使用HoughLinesP获得线段后直接去获取线段的交点,但是自己用的时候返回的很多都是非常短的线段,上面那篇文章的作者使用改变霍夫变换参数的方法去获取长线段,但是循环去调整参数感觉运行效率非常低(至少在java中运行起来非常慢)。一开始我想的方法是将整个图像分为4个区域,因为拍照扫描的时候基本对象都是位于屏幕相对中心位置的,那么将图像分为左上、右上、左下、右下四个区域,获取每个区域包含线段的point,然后计算每个区域离中心点最远的那个point就是我们要找的角,如下图

但是后来发现这种方式有个缺陷,就是之前说过的问题,由于图像处理问题没办法精确定位到左下角坐标。

后来想到了另一个方法,大致步骤如下:

1、还是先将图像分成4个区域

2、然后将线段分别归类到对应区域的集合中

/**
 * 将线段放置到对应区域的对象中
 * @param centerPoint
 * @param point
 * @param areaLines 存放区域线段集合的对象
 * @param line
 */
private AreaLines putLines(Point centerPoint, Mat lines, int imgWidth, int imgHeight){
	AreaLines areaLines = new AreaLines(imgWidth, imgHeight);
	for(int i = 0;i < lines.rows();i++){
		double[] line = lines.get(i, 0);
		Point p1 = new Point(line[0], line[1]);
		Point p2 = new Point(line[2], line[3]);

		putLines(centerPoint, p1, areaLines, line);
		putLines(centerPoint, p2, areaLines, line);
	}
	return areaLines;
}

/**
 * 将线段放置到对应区域的对象中
 * @param centerPoint
 * @param point
 * @param areaLines 存放区域线段集合的对象
 * @param line
 */
private void putLines(Point centerPoint, Point point, AreaLines areaLines, double[] line){
	if(point.x <= centerPoint.x && point.y <= centerPoint.y){//左上区域
		if(!areaLines.getLeft_top_area().getLines().contains(line)){
			areaLines.getLeft_top_area().getLines().add(line);
		}
	}else if(point.x > centerPoint.x && point.y <= centerPoint.y){//右上区域
		if(!areaLines.getRight_top_area().getLines().contains(line)){
			areaLines.getRight_top_area().getLines().add(line);
		}
	}else if(point.x <= centerPoint.x && point.y > centerPoint.y){//左下区域
		if(!areaLines.getLeft_bottom_area().getLines().contains(line)){
			areaLines.getLeft_bottom_area().getLines().add(line);
		}
	}else{
		if(!areaLines.getRight_bottom_area().getLines().contains(line)){//右下区域
			areaLines.getRight_bottom_area().getLines().add(line);
		}
	}
}

3、将每个区域的线段分成2组,就是角的两边,使用斜率分组,同一边的线段斜率必然是相近的

4、将一组中的N条线段合并为一条线段,取头尾的point

5、将合并后的线段延长到整个图像两边(不延长的话某些线段可能没有交点,如上图左下区域的两条边线)后计算交点

/**
 * 获取两条线段的交点
 * @return
 */
public Point getCrossPoint() throws Exception{
	List<double[]> group1 = new ArrayList<double[]>();
	List<double[]> group2 = new ArrayList<double[]>();
	//计算出每条线段水平方向的角度,按角度对线段进行分组
	List<Item> lineList = getLinesAngle();
	if(lineList.isEmpty()){
		throw new Exception(Utils.type2Label(type) + "没有线段");
	}
	double angle = lineList.get(0).getAngle();
	for(Item item : lineList){
		double _angle = item.getAngle();
		//角度相差30以内的认为是一组
		if(Math.abs(angle - _angle) <= 30){
			group1.add(item.getLine());
		}else{
			group2.add(item.getLine());
		}
	}
	//将多条线段合并为一条线段,角的两边一共合并出2条线段
	double[] longLine1 = getLine(group1);
	double[] longLine2 = getLine(group2);
		
	//延长线段到图像两端
	double[] fullLine1 = getExtendedLine(longLine1);
	double[] fullLine2 = getExtendedLine(longLine2);
		
	//计算交点
	Point crossPoint = Utils.getCrossPoint(fullLine1, fullLine2);
	if(crossPoint == null){
		throw new Exception(Utils.type2Label(type) + "未找到交点");
	}
	return crossPoint;
}

这样我们就得到了4个角比较精确的point

3)、进行透视变换

这里虽然还没有弄清相机标定的问题,不过在大多数情况下我们拍照扫描一个文件时不太会出现图像纵深的问题,所以暂时先不考虑,简单的获取目标的高度宽度做下透视变换。

//开始做透视变换
Mat mat = new Mat();
mat.push_back(new MatOfPoint2f(ltp));
mat.push_back(new MatOfPoint2f(rtp));
mat.push_back(new MatOfPoint2f(rbp));
mat.push_back(new MatOfPoint2f(lbp));
		
Size outputSize = getOutputSize(ltp, rtp, rbp, lbp);
		
Mat size = new Mat();
size.push_back(new MatOfPoint2f(new Point(0, 0)));
size.push_back(new MatOfPoint2f(new Point(outputSize.width, 0)));
size.push_back(new MatOfPoint2f(new Point(outputSize.width, outputSize.height)));
size.push_back(new MatOfPoint2f(new Point(0, outputSize.height)));
Mat pt = Imgproc.getPerspectiveTransform(mat, size);
Imgproc.warpPerspective(src, src, pt, new Size(outputSize.width, outputSize.height));
output(TMP_FOLDER + "/" + (i++) + "_final.jpg", src);

到这里基本就结束了,运行后的结果如下:

原图1:

运行结果1:

原图2:

运行结果2:

最后可以根据需要对图像做进一步处理,比如二值化等操作。

源码可以从这里下载https://download.csdn.net/download/rwzhang/10887278,因为一边研究一边在写,修修改改的,可能代码看着稍微有一点乱,但是注释都写了,应该还是能看清的。等有时间了把图像预处理和相机标定再研究下,这两块也是非常重要的,直接决定了是否能够找到需要的轮廓以及还原比例是否正确!

  • 7
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
使用Java语言调用OpenCV实现微信扫描二维码引擎功能的步骤如下: 首先,确保已经安装了Java开发环境和OpenCV库。 1. 导入所需的Java库和OpenCV库: 在Java项目中,我们需要导入相应的Java库和OpenCV库。可以使用构建工具如Maven或Gradle来导入这些库。 2. 加载OpenCV库: 在Java代码中,需要调用OpenCV库的功能之前,需要先加载OpenCV库。可以使用`System.loadLibrary()`方法来加载OpenCV核心库。 3. 打开摄像头并捕获图像: 在Java中,可以使用OpenCV的`VideoCapture`类来打开摄像头,并通过`read()`方法捕获图像。 4. 对捕获到的图像进行处理: 使用OpenCV提供的图像处理功能,可以对捕获到的图像进行二维码识别的预处理,如去噪、图像增强等。 5. 进行二维码识别: 使用OpenCV提供的二维码识别功能,可以对预处理后的图像进行二维码识别。可以调用相应的方法来进行识别,并获取二维码的信息。 6. 使用微信API进行扫码确认: 如果需要将二维码的信息传递到微信进行扫码确认,可以调用微信提供的API,将识别到的二维码信息传递给微信进行验证。 7. 处理识别结果: 根据微信返回的结果,可以进行相应的处理。例如,可以根据识别结果进行跳转、处理业务逻辑等。 以上是使用JavaOpenCV调用微信扫描二维码引擎的基本步骤。通过使用OpenCV库的图像处理功能和二维码识别功能,结合微信提供的API,可以实现二维码的扫描和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值