Browse Source

1、注释掉数据库等无关配置

2、新增经典4点转换方法,实现矩形图提取矫正。基本可以通用
fetches/lml_1992/master
songer4081 5 years ago
parent
commit
722825baf9
6 changed files with 373 additions and 33 deletions
  1. +6
    -1
      pom.xml
  2. +222
    -0
      src/main/java/com/acts/opencv/base/BaseMethodController.java
  3. +32
    -32
      src/main/resources/spring-context.xml
  4. +1
    -0
      src/main/webapp/index.jsp
  5. BIN
      src/main/webapp/statics/sourceimage/pic_transform.png
  6. +112
    -0
      src/main/webapp/view/base/fourPointTransform.jsp

+ 6
- 1
pom.xml View File

@@ -228,7 +228,12 @@
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>

<!-- hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.1</version>
</dependency>

</dependencies>
<build>


+ 222
- 0
src/main/java/com/acts/opencv/base/BaseMethodController.java View File

@@ -3,6 +3,8 @@ package com.acts.opencv.base;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -17,6 +19,7 @@ import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
@@ -42,6 +45,8 @@ import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import cn.hutool.core.util.NumberUtil;


@Controller
@RequestMapping(value = "base")
@@ -1083,4 +1088,221 @@ public class BaseMethodController extends BaseController {
//
// }

public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

}
@RequestMapping(value = "picTransform")
public void picTransform(HttpServletResponse response, String imagefile) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
logger.info("\n 图像转换开始");
//我们假设我们识别的图片如样例一样有明显的边界,那我们可以用边缘检测算法将真正有效区域抽离出来,
//以此来提高识别准确度和识别精度
//先进行边缘检测
// String sourcePath = "d:\\test\\abc\\a.png";
String sourcePath = Constants.PATH + imagefile;
Mat source = Highgui.imread(sourcePath);
// Mat destination = new Mat(source.rows(), source.cols(), source.type());
//复制一个source作为四点转换的原图,因为source在轮廓识别时会被覆盖,建议图像处理时都将原图复制一份,
//因为opencv的很多算法都会更改传入的soure图片,如果不注意可能就会导致各种异常。
Mat orign = source.clone();
//为了加速图像处理,以及使我们的边缘检测步骤更加准确,我们将扫描图像的大小调整为具有500像素的高度。
Mat dst = source.clone();
//缩放比例
double ratio = NumberUtil.div(500, orign.height());
System.out.println("----------"+ratio);
double width = ratio*orign.width();
Imgproc.resize(source, dst, new Size(width,500));
// 灰度化,加载为灰度图显示
Mat gray = dst.clone();
Imgproc.cvtColor(dst,gray,Imgproc.COLOR_BGR2GRAY);
Highgui.imwrite("d:\\test\\abc\\o1.png", gray);
//高斯滤波,去除杂点等干扰
Imgproc.GaussianBlur(gray,gray, new Size(5, 5), 0);
//canny边缘检测算法,经过canny算法或的图像会变成二值化效果
Mat edges = gray.clone();
Imgproc.Canny(gray,edges,75, 200);
Highgui.imwrite("d:\\test\\abc\\o2.png", edges);
String destPath = "d:\\test\\abc\\dst.png";
Mat hierarchy = new Mat(gray.rows(), gray.cols(), CvType.CV_8UC1, new Scalar(0));
Vector<MatOfPoint> contours = new Vector<MatOfPoint>();
//轮廓识别,查找外轮廓
Imgproc.findContours(edges, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point());
List<Point> listPoint = new ArrayList<>();
for(int i =0; i<contours.size();i++) {
MatOfPoint2f newPoint = new MatOfPoint2f(contours.get(i).toArray());
// 周长,第1个参数是轮廓,第二个参数代表是否是闭环的图形
double peri = 0.01 * Imgproc.arcLength(newPoint, true);
MatOfPoint2f approx = new MatOfPoint2f();
// approx.convertTo(approx, CvType.CV_32F);
//近似轮廓逼近,然后通过获取多边形的所有定点,如果是四个定点,就代表是矩形
Imgproc.approxPolyDP(newPoint,approx, peri, true);
//只考虑矩形,如果近似轮廓有4个点,我们就认为已经找到了该矩形。
if (approx.rows() == 4) {
//通过reshape函数将4个点取出来(4行2列的矩阵)
Mat points = approx.reshape(2, 4);
System.out.println(points.dump());
double[] point1 = points.get(0, 0);
double[] point2 = points.get(1, 0);
double[] point3 = points.get(2, 0);
double[] point4 = points.get(3, 0);
//之前因为我们已经将图片进行了缩放,所以此处要将图片尺寸还原
listPoint.add(new Point(point1[0]/ratio,point1[1]/ratio));
listPoint.add(new Point(point2[0]/ratio,point2[1]/ratio));
listPoint.add(new Point(point3[0]/ratio,point3[1]/ratio));
listPoint.add(new Point(point4[0]/ratio,point4[1]/ratio));
for (Point d : listPoint) {
System.out.println(d);
}
System.out.println("######################");
break;
}

}
//绘制轮廓,注意是在缩放过的图片上绘制的,别在原图上画,肯定画的不对。
Imgproc.drawContours(dst, contours, -1, new Scalar(0, 255, 0), 2);
Highgui.imwrite("d:\\test\\abc\\o3.png", dst);
Mat resullt = fourPointTransform(orign, listPoint);
Highgui.imwrite(destPath, resullt);
// 方式2,回写页面图片流
try {
byte[] imgebyte = OpenCVUtil.covertMat2Byte1(resullt);
renderImage(response, imgebyte);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* py中imutils中经典的4点转换方法的java实现
* @author song.wang
* @date 2019年8月20日
* @param source
* @param listPoint
* @return Mat
*
* 更新日志
* 2019年8月20日 song.wang 首次创建
*/
private static Mat fourPointTransform(Mat source,List<Point> listPoint) {
//获得点的顺序
List<Point> newOrderList = orderPoints(listPoint);
for (Point point : newOrderList) {
System.out.println(point);
}
//计算新图像的宽度,它将是右下角和左下角x坐标之间或右上角和左上角x坐标之间的最大距离
//此处的顺序别搞错0,1,2,3依次是左上[0],右上[1],右下[2],左下[3]
Point leftTop = newOrderList.get(0);
Point rightTop = newOrderList.get(1);
Point rightBottom = newOrderList.get(2);
Point leftBottom = newOrderList.get(3);
double widthA = Math.sqrt(Math.pow(rightBottom.x-leftBottom.x, 2)
+Math.pow(rightBottom.y-leftBottom.y, 2));
double widthB = Math.sqrt(Math.pow(rightTop.x-leftTop.x, 2)
+Math.pow(rightTop.y-leftTop.y, 2));
int maxWidth = Math.max((int)widthA, (int)widthB);

//计算新图像的高度,这将是右上角和右下角y坐标或左上角和左下角y坐标之间的最大距离,
//这里用到的初中数学知识点和点的距离计算(x1,y1),(x2,y2)距离=√((x2-x1)^2+(y2-y1)^2)
double heightA = Math.sqrt(Math.pow(rightTop.x-rightBottom.x, 2)
+Math.pow(rightTop.y-rightBottom.y, 2));
double heightB = Math.sqrt(Math.pow(leftTop.x-leftBottom.x, 2)
+Math.pow(leftTop.y-leftBottom.y, 2));
int maxHeight = Math.max((int)heightA, (int)heightB);
System.out.println("宽度:"+maxWidth);
System.out.println("高度:"+maxHeight);
//现在我们指定目标图像的尺寸,构造目标点集以获得图像的“鸟瞰图”(即自上而下的视图),
//再次指定左上角,右上角的点,右下角和左下角的顺序
Point dstPoint1 = new Point(0,0);
Point dstPoint2 = new Point(maxWidth-1,0);
Point dstPoint3 = new Point(maxWidth-1,maxHeight-1);
Point dstPoint4 = new Point(0,maxHeight-1);

//计算透视变换矩阵rectMat原四顶点位置,dstMat目标顶点位置
MatOfPoint2f rectMat = new MatOfPoint2f(leftTop,rightTop,rightBottom,leftBottom);
MatOfPoint2f dstMat = new MatOfPoint2f(dstPoint1, dstPoint2, dstPoint3, dstPoint4);
//opencv透视转换方法
Mat transmtx = Imgproc.getPerspectiveTransform(rectMat, dstMat);
//注意定义的新图像宽高设置
Mat resultMat = Mat.zeros((int)maxHeight-1, (int)maxWidth-1, CvType.CV_8UC3);
Imgproc.warpPerspective(source, resultMat, transmtx, resultMat.size());
Highgui.imwrite("D:\\test\\abc\\t2.png", resultMat);

//返回矫正后的图像
return resultMat;
}
/**
* 4点排序,四个点按照左上、右上、右下、左下组织返回
* @author song.wang
* @date 2019年8月16日
* @param listPoint
* @return List<Point>
*
* 更新日志
* 2019年8月16日 song.wang 首次创建
*/
private static List<Point> orderPoints(List<Point> listPoint) {
//python中有很多关于数组的函数处理如排序、比较、加减乘除等,在这里我们使用List进行操作
//如numpy.argsort;numpy.argmin;numpy.argmax;sum(axis = 1);diff(pts, axis = 1)等等,有兴趣的可以查阅相关资料
//四个点按照左上、右上、右下、左下组织返回
//直接在这里添加我们的排序规则,按照x坐标轴升序排列,小的放前面
Collections.sort(listPoint, new Comparator<Point>() {
public int compare(Point arg0, Point arg1) {
if(arg0.x < arg1.x){
return -1;
}else if (arg0.x> arg1.x){
return 1;
}else{
return 0;
}
}
});
//排序之后前2个点就是左侧的点,后2个点为右侧的点
//对比Y轴,y值小的是左上的点,y大的是左下的点
Point top_left = new Point();
Point bottom_left = new Point();
Point top_right = new Point();
Point bottom_right = new Point();

Point leftPoint1 = listPoint.get(0);
Point leftPoint2 = listPoint.get(1);
Point rightPoint1 = listPoint.get(2);
Point rightPoint2 = listPoint.get(3);
if(leftPoint1.y > leftPoint2.y){
top_left = leftPoint2;
bottom_left = leftPoint1;
}else{
top_left = leftPoint1;
bottom_left = leftPoint2;
}
//定位右侧的2个点右上和右下使用方法是毕达哥拉斯定理,就是勾股定理距离长的认为是右下角
//计算左上方点和右侧两个点的欧氏距离
//(y2-y1)^2+(x2-x1)^2 开根号
double rightLength1 = Math.sqrt(Math.pow((rightPoint1.y - top_left.y), 2)
+ Math.pow((rightPoint1.x - top_left.x), 2));
double rightLength2 = Math.sqrt(Math.pow((rightPoint2.y - top_left.y), 2)
+ Math.pow((rightPoint2.x - top_left.x), 2));
if(rightLength1>rightLength2){
//长度长的那个是右下角,短的为右上角;这个算法有一种情况会有可能出问题,比如倒梯形,但是在正常的俯角拍摄时不会出现这种情况
//还有一种方案是按照左侧的那种对比方案,根据y轴的高度判断。
top_right = rightPoint2;
bottom_right = rightPoint1;
}else{
top_right = rightPoint1;
bottom_right = rightPoint2;
}
//按照左上,右上,右下,左下的顺时针顺序排列,这点很重要,透视变换时根据这个顺序进行对应
List<Point> newListPoint = new ArrayList<>();
newListPoint.add(top_left);
newListPoint.add(top_right);
newListPoint.add(bottom_right);
newListPoint.add(bottom_left);
return newListPoint;
}
}

+ 32
- 32
src/main/resources/spring-context.xml View File

@@ -34,61 +34,61 @@
</bean>

<!--dataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 数据库基本信息配置 -->
<!-- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
数据库基本信息配置
<property name="driverClassName" value="${driverClassName}" />
<property name="Url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化连接数量 -->
初始化连接数量
<property name="initialSize" value="${initialSize}" />
<!-- 最大并发连接数 -->
最大并发连接数
<property name="maxActive" value="${maxActive}" />
<!-- 最小空闲连接数 -->
最小空闲连接数
<property name="minIdle" value="${minIdle}" />
<!-- 配置获取连接等待超时的时间 -->
配置获取连接等待超时的时间
<property name="maxWait" value="${maxWait}" />
<!-- 超过时间限制是否回收 -->
超过时间限制是否回收
<property name="removeAbandoned" value="${removeAbandoned}" />
<!-- 超过时间限制多长; -->
超过时间限制多长;
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
配置一个连接在池中最小生存的时间,单位是毫秒
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<!-- 用来检测连接是否有效的sql,要求是一个查询语句 ||抛弃此种方式的连接检查-->
<!-- <property name="validationQuery" value="${validationQuery}" /> -->
<!-- 申请连接的时候检测 -->
用来检测连接是否有效的sql,要求是一个查询语句 ||抛弃此种方式的连接检查
<property name="validationQuery" value="${validationQuery}" />
申请连接的时候检测
<property name="testWhileIdle" value="${testWhileIdle}" />
<!-- 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
<property name="testOnBorrow" value="${testOnBorrow}" />
<!-- 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
<property name="testOnReturn" value="${testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
打开PSCache,并且指定每个连接上PSCache的大小
<property name="poolPreparedStatements" value="${poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />
<!--属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
监控统计用的filter:stat
日志用的filter:log4j
防御SQL注入的filter:wall -->
防御SQL注入的filter:wall
<property name="filters" value="${filters}" />
</bean>
</bean> -->
<!-- Hibernate对Jpa的实现 -->
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
<!-- 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 指定数据源 -->
<!-- <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
指定数据源
<property name="dataSource" ref="dataSource"/>
<!-- 指定Jpa持久化实现厂商类,这里以Hibernate为例 -->
指定Jpa持久化实现厂商类,这里以Hibernate为例
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
<!-- 指定Entity实体类包路径 -->
指定Entity实体类包路径
<property name="packagesToScan" value="com.acts.opencv"></property>
<!-- 指定JPA属性;如Hibernate中指定是否显示SQL的是否显示、方言等 -->
指定JPA属性;如Hibernate中指定是否显示SQL的是否显示、方言等
<property name="jpaProperties">
<props>
<!-- <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> -->
<!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
@@ -99,20 +99,20 @@
</props>
</property>
<property name="sharedCacheMode" value="ENABLE_SELECTIVE"></property>
</bean>
</bean> -->
<!-- 定义事务 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<!-- <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</bean> -->
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- <tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置 SpringData -->
配置 SpringData
<jpa:repositories base-package="com.acts.opencv" entity-manager-factory-ref="entityManagerFactory"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</bean> -->
<!-- redis 配置
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" />


+ 1
- 0
src/main/webapp/index.jsp View File

@@ -141,6 +141,7 @@ desired effect
<li><a href="#view/base/contours.jsp"><i class="fa fa-circle-o"></i>轮廓</a></li>
<li><a href="#view/base/findtemplate.jsp"><i class="fa fa-circle-o"></i>模板查找</a></li>
<li><a href="#view/base/grayHistogram.jsp"><i class="fa fa-circle-o"></i>灰度直方图</a></li>
<li><a href="#view/base/fourPointTransform.jsp"><i class="fa fa-circle-o"></i>4点转换矫正</a></li>
</ul>
</li>


BIN
src/main/webapp/statics/sourceimage/pic_transform.png View File

Before After
Width: 732  |  Height: 898  |  Size: 862 kB

+ 112
- 0
src/main/webapp/view/base/fourPointTransform.jsp View File

@@ -0,0 +1,112 @@
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@include file="/module/include/common.jsp"%>
<!DOCTYPE html>
<html>
<head>
<sys:header title="首页" extLibs=""></sys:header>
<link rel="stylesheet" href="${ctxStatic}/plugins/iCheck/all.css?t=${version}">
<link rel="stylesheet" href="${ctxStatic}/plugins/bootstrap-slider/slider.css">
<link rel="stylesheet" href="${ctxStatic}/plugins/iCheck/minimal/blue.css?t=${version}">
<script type="text/javascript">
$(function(){
var baseImageFile = "/statics/sourceimage/pic_transform.png"
//var baseImageFile = "/statics/sourceimage/ada.png"
var newImagePath = "/statics/distimage/pic_transform.png"
$("#oldimg").attr("src",baseUrl+baseImageFile);
//$("#newimg").attr("src",baseUrl+baseImageFile);
//二值化
$("#handle").click(function(){
var imagefile = baseImageFile;
//方式2,实时传输图片流的方式
var srcurl = ctxPath+"/base/picTransform?_" + $.now()+"&imagefile="+baseImageFile;
$("#newimg").attr("src",srcurl);
});
//重置
$("#reset").click(function(){
var baseImageFile = "/statics/sourceimage/pic_transform.png";
$("#oldimg").attr("src",baseUrl+baseImageFile);
$("#newimg").attr("src",'');
layer.msg('重置成功!', {icon: 1});
});
});
</script>
</head>
<body>

<div class="box-body">
<div class="box-group" id="accordion">
<!-- we are adding the .panel class so bootstrap.js collapse plugin detects it -->
<div class="panel box box-primary">
<div class="box-header with-border">
<h4 class="box-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
python中经典的四点透视变换方法的java实现。轻松实现图像的透视变换
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in"><!--class="panel-collapse collapse in"中的 in 控制展开 -->
<div class="box-body">
<h4>参考资料:<br>
<a href="https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/">
https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/</a> <br>
<a href="https://www.pyimagesearch.com/2016/03/21/ordering-coordinates-clockwise-with-python-and-opencv/">
https://www.pyimagesearch.com/2016/03/21/ordering-coordinates-clockwise-with-python-and-opencv/</a> <br>
<a href="https://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/">
https://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/</a> <br>
</h4>
</div>
</div>
</div>
</div>
</div>
<!-- /.box-body -->
<h4>&nbsp;&nbsp;效果测试</h4>
<div class="box-body">
<a class="btn btn-info" id="handle"><i class="fa fa-object-ungroup"></i>处理</a>
<a class="btn btn-info" id="reset"><i class="fa fa-refresh"></i>重置</a>
</div>
<div class="row">
<div class="col-sm-8">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">原图</h3>
<span class="label label-primary pull-right"><i class="fa fa-html5"></i></span>
</div><!-- /.box-header -->
<div class="box-body">
<p>原文件。</p>
<img id="oldimg" src="" alt="原图" />
</div><!-- /.box-body -->
</div><!-- /.box -->
</div><!-- /.col -->
<div class="col-sm-4">
<div class="box box-danger">
<div class="box-header with-border">
<h3 class="box-title">处理后的图片</h3>
<span class="label label-danger pull-right"><i class="fa fa-database"></i></span>
</div><!-- /.box-header -->
<div class="box-body">
<p>点击处理按钮后,将显示处理后的文件。</p>
<img id="newimg" src="" alt="处理后的图" />

</div><!-- /.box-body -->
</div><!-- /.box -->
</div><!-- /.col -->
</div>
</body>
<script src="${ctxStatic}/plugins/bootstrap-slider/bootstrap-slider.js?t=${version}"></script>
<script src="${ctxStatic}/plugins/iCheck/icheck.js?t=${version}"></script>
</html>

Loading…
Cancel
Save