图像识别——(java使用opencv答题卡识别)
1.安装:配置linux或windows环境[Linux下配置OpenCV](http://note.youdao.com/noteshare?id=5de54af1ef6fef8352b8f3d3a9356845&sub=3D706CF274274B68B3BA2C9C42254747)[Windows下配置OpenCV](http://note.youdao.com/noteshare
·
具体算法实现:具体算法实现——opencvhttps://blog.csdn.net/qq_39246466/article/details/123819795
1.安装:
配置linux或windows环境 [Linux下配置OpenCV](http://note.youdao.com/noteshare?id=5de54af1ef6fef8352b8f3d3a9356845&sub=3D706CF274274B68B3BA2C9C42254747) [Windows下配置OpenCV](http://note.youdao.com/noteshare?id=e0df335c7bba4d7633874375539c228a&sub=1015DEB3B7C847C28D0BBB87F21EDB59)
核心包:将下载的exe文件运行,取出其中的opencv.jar包
2.核心工具类:
package com.acts.opencv.common.utils;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.util.Date;
import java.util.Vector;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
public class OpenCVUtil {
public static BufferedImage covertMat2Buffer(Mat mat) throws IOException {
long time1 = new Date().getTime();
// Mat 转byte数组
BufferedImage originalB = toBufferedImage(mat);
long time3 = new Date().getTime();
System.out.println("保存读取方法2转=" + (time3 - time1));
return originalB;
// ImageIO.write(originalB, "jpg", new File("D:\\test\\testImge\\ws2.jpg"));
}
public static byte[] covertMat2Byte(Mat mat) throws IOException {
long time1 = new Date().getTime();
// Mat 转byte数组
byte[] return_buff = new byte[(int) (mat.total() * mat.channels())];
Mat mat1 = new Mat();
mat1.get(0, 0, return_buff);
long time3 = new Date().getTime();
System.out.println(mat.total() * mat.channels());
System.out.println("保存读取方法2转=" + (time3 - time1));
return return_buff;
}
public static byte[] covertMat2Byte1(Mat mat) throws IOException {
long time1 = new Date().getTime();
MatOfByte mob = new MatOfByte();
Highgui.imencode(".jpg", mat, mob);
long time3 = new Date().getTime();
// System.out.println(mat.total() * mat.channels());
System.out.println("Mat转byte[] 耗时=" + (time3 - time1));
return mob.toArray();
}
public static BufferedImage toBufferedImage(Mat m) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // get all the pixels
BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);
return image;
}
/**
* 腐蚀膨胀是针对于白色区域来说的,腐蚀即腐蚀白色区域
* 腐蚀算法(黑色区域变大)
* @param source
* @return
*/
public static Mat eroding(Mat source) {
return eroding(source, 1);
}
public static Mat eroding(Mat source, double erosion_size) {
Mat resultMat = new Mat(source.rows(), source.cols(), source.type());
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2 * erosion_size + 1,
2 * erosion_size + 1));
Imgproc.erode(source, resultMat, element);
return resultMat;
}
/**
* 腐蚀膨胀是针对于白色区域来说的,膨胀是膨胀白色区域
* 膨胀算法(白色区域变大)
* @param source
* @return
*/
public static Mat dilation(Mat source) {
return dilation(source, 1);
}
/**
* 腐蚀膨胀是针对于白色区域来说的,膨胀是膨胀白色区域
* @Author 王嵩
* @param source
* @param dilationSize 膨胀因子2*x+1 里的x
* @return Mat
* @Date 2018年2月5日
* 更新日志
* 2018年2月5日 王嵩 首次创建
*
*/
public static Mat dilation(Mat source, double dilation_size) {
Mat resultMat = new Mat(source.rows(), source.cols(), source.type());
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2 * dilation_size + 1,
2 * dilation_size + 1));
Imgproc.dilate(source, resultMat, element);
return resultMat;
}
/**
* 轮廓识别,使用最外轮廓发抽取轮廓RETR_EXTERNAL,轮廓识别方法为CHAIN_APPROX_SIMPLE
* @param source 传入进来的图片Mat对象
* @return 返回轮廓结果集
*/
public static Vector<MatOfPoint> findContours(Mat source) {
Mat rs = new Mat();
/**
* 定义轮廓抽取模式
*RETR_EXTERNAL:只检索最外面的轮廓;
*RETR_LIST:检索所有的轮廓,并将其放入list中;
*RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
*RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。
*/
int mode = Imgproc.RETR_EXTERNAL;
// int mode = Imgproc.RETR_TREE;
/**
* 定义轮廓识别方法
* 边缘近似方法(除了RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
*CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
*CHAIN_APPROX_NONE:将所有的连码点,转换成点。
*CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
*CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法的一种。
*LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。
*/
int method = Imgproc.CHAIN_APPROX_SIMPLE;
Vector<MatOfPoint> contours = new Vector<MatOfPoint>();
Imgproc.findContours(source, contours, rs, mode, method, new Point());
return contours;
}
}
3.答题卡识别实现
controller
package com.acts.opencv.base;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.acts.opencv.common.utils.Constants;
import com.acts.opencv.common.utils.RectComp;
import com.acts.opencv.common.web.BaseController;
@Controller
@RequestMapping(value = "card")
public class CardController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(CardController.class);
/**
* 答题卡识别
* step1 高斯模糊
* 创建者 Songer
* 创建时间 2018年3月22日
*/
@RequestMapping(value = "step1")
public void step1(HttpServletResponse response, String imagefile, Integer ksize) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
logger.info("\n 高斯模糊");
String sourcePath = Constants.PATH + imagefile;
logger.info("url==============" + sourcePath);
// 加载为灰度图显示
Mat source = Highgui.imread(sourcePath, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
Mat destination = new Mat(source.rows(), source.cols(), source.type());
Imgproc.GaussianBlur(source, destination, new Size(2 * ksize + 1, 2 * ksize + 1), 0, 0);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "card1.png";
File dstfile = new File(destPath);
if (StringUtils.isNotBlank(destPath) && dstfile.isFile() && dstfile.exists()) {
dstfile.delete();
logger.info("删除图片:" + destPath);
}
Highgui.imwrite(destPath, destination);
logger.info("生成目标图片==============" + destPath);
renderString(response, Constants.DEST_IMAGE_PATH + "card1.png");
}
/**
* 答题卡识别
* step2 二值化,反向二值化
* 创建者 Songer
* 创建时间 2018年3月22日
*/
@RequestMapping(value = "step2")
public void step2(HttpServletResponse response, String imagefile, Double thresh) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
logger.info("\n 二值化处理");
// 灰度化
// Imgproc.cvtColor(source, destination, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
String sourcePath = Constants.PATH + imagefile;
logger.info("url==============" + sourcePath);
// 加载为灰度图显示
Mat source = Highgui.imread(sourcePath, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
Mat destination = new Mat(source.rows(), source.cols(), source.type());
Imgproc.threshold(source, destination, thresh, 255, Imgproc.THRESH_BINARY_INV);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "card2.png";
File dstfile = new File(destPath);
if (StringUtils.isNotBlank(destPath) && dstfile.isFile() && dstfile.exists()) {
dstfile.delete();
logger.info("删除图片:" + destPath);
}
Highgui.imwrite(destPath, destination);
logger.info("生成目标图片==============" + destPath);
renderString(response, Constants.DEST_IMAGE_PATH + "card2.png");
}
/**
* 答题卡识别
* step3 膨胀腐蚀闭运算(针对反向二值图是开运算)
* 创建者 Songer
* 创建时间 2018年3月22日
*/
@RequestMapping(value = "step3")
public void step3(HttpServletResponse response, String imagefile, Integer ksize) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
logger.info("\n 开运算");
// 灰度化
// Imgproc.cvtColor(source, destination, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
String sourcePath = Constants.PATH + imagefile;
logger.info("url==============" + sourcePath);
// 加载为灰度图显示
Mat source = Highgui.imread(sourcePath, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
Mat destination = new Mat(source.rows(), source.cols(), source.type());
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2 * ksize + 1, 2 * ksize + 1));
Imgproc.morphologyEx(source, destination, Imgproc.MORPH_OPEN, element);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "card3.png";
File dstfile = new File(destPath);
if (StringUtils.isNotBlank(destPath) && dstfile.isFile() && dstfile.exists()) {
dstfile.delete();
logger.info("删除图片:" + destPath);
}
Highgui.imwrite(destPath, destination);
logger.info("生成目标图片==============" + destPath);
renderString(response, Constants.DEST_IMAGE_PATH + "card3.png");
}
/**
* 答题卡识别
* step4 轮廓识别
* 创建者 Songer
* 创建时间 2018年3月22日
*/
@RequestMapping(value = "step4")
public void step4(HttpServletResponse response, String imagefile) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
logger.info("\n 轮廓识别");
// 灰度化
// Imgproc.cvtColor(source, destination, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
String sourcePath = Constants.PATH + imagefile;
logger.info("url==============" + sourcePath);
// 加载为灰度图显示
Mat source = Highgui.imread(sourcePath, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
Highgui.imwrite("D:\\test\\abc\\source.png", source);
//此处固定写死,取每一行选项,切割后进行轮廓识别
Mat ch1 = source.submat(new Rect(170, 52, 294, 32));
Mat ch2 = source.submat(new Rect(170, 104, 294, 32));
Mat ch3 = source.submat(new Rect(170, 156, 294, 32));
Mat ch4 = source.submat(new Rect(170, 208, 294, 32));
Mat ch5 = source.submat(new Rect(170, 260, 294, 32));
Mat ch6 = source.submat(new Rect(706, 50, 294, 32));
Mat ch7 = source.submat(new Rect(706, 104, 294, 32));
Mat ch8 = source.submat(new Rect(706, 156, 294, 32));
Mat ch9 = source.submat(new Rect(706, 208, 294, 32));
Mat ch10 = source.submat(new Rect(706, 260, 294, 32));
Mat ch11 = source.submat(new Rect(1237, 50, 294, 32));
Mat ch12 = source.submat(new Rect(1237, 104, 294, 32));
Mat ch13 = source.submat(new Rect(1237, 156, 294, 32));
Mat ch14 = source.submat(new Rect(1237, 208, 294, 32));
Mat ch15 = source.submat(new Rect(1237, 260, 294, 32));
Mat ch16 = source.submat(new Rect(1766, 50, 294, 32));
Mat ch17 = source.submat(new Rect(1766, 104, 294, 32));
Mat ch18 = source.submat(new Rect(1766, 156, 294, 32));
Mat ch19 = source.submat(new Rect(1766, 208, 294, 32));
Mat ch20 = source.submat(new Rect(1766, 260, 294, 32));
Mat ch21 = source.submat(new Rect(170, 358, 294, 32));
Mat ch22 = source.submat(new Rect(170, 410, 294, 32));
Mat ch23 = source.submat(new Rect(170, 462, 294, 32));
Mat ch24 = source.submat(new Rect(170, 514, 294, 32));
Mat ch25 = source.submat(new Rect(170, 566, 294, 32));
List<Mat> chlist = new ArrayList<Mat>();
chlist.add(ch1);
chlist.add(ch2);
chlist.add(ch3);
chlist.add(ch4);
chlist.add(ch5);
chlist.add(ch6);
chlist.add(ch7);
chlist.add(ch8);
chlist.add(ch9);
chlist.add(ch10);
chlist.add(ch11);
chlist.add(ch12);
chlist.add(ch13);
chlist.add(ch14);
chlist.add(ch15);
chlist.add(ch16);
chlist.add(ch17);
chlist.add(ch18);
chlist.add(ch19);
chlist.add(ch20);
chlist.add(ch21);
chlist.add(ch22);
chlist.add(ch23);
chlist.add(ch24);
chlist.add(ch25);
Mat hierarchy = new Mat();
java.util.TreeMap<Integer,String> listenAnswer = new TreeMap<Integer,String>();
for (int no=0;no<chlist.size();no++) {
Vector<MatOfPoint> contours = new Vector<MatOfPoint>();
Mat ch = chlist.get(no);
Imgproc.findContours(ch, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE, new Point());
Vector<RectComp> rectCompList = new Vector<RectComp>();
for(int i = 0;i<contours.size();i++){
MatOfPoint mop= contours.get(i);
// 获取轮廓外矩,即使用最小矩形将轮廓包裹
Rect rm = Imgproc.boundingRect(mop);
RectComp rc = new RectComp(rm);
rectCompList.add(rc);
}
// System.out.println(no+"size="+rectCompList.size());
Collections.sort(rectCompList);
// for(int t = 0;t<rectCompList.size();t++){
// RectComp rect = rectCompList.get(t);
// System.out.println(rect.getRm().area() + "--------" + rect.getRm().x);
// if (rect.getRm().area() < 300) {// 小于300的pass,完美填图的话是≈1500
// continue;
// }
// if (rect.getRm().x < 68) {
// listenAnswer.put(Integer.valueOf(no), "A");
// } else if ((rect.getRm().x > 68) && (rect.getRm().x < 148)) {
// listenAnswer.put(Integer.valueOf(no), "B");
// } else if ((rect.getRm().x > 148) && (rect.getRm().x < 228)) {
// listenAnswer.put(Integer.valueOf(no), "C");
// } else if (rect.getRm().x > 228) {
// listenAnswer.put(Integer.valueOf(no), "D");
// }
// }
// 因为已经按面积排序了,所以取第一个面积最大的轮廓即可
RectComp rect = rectCompList.get(0);
System.out.println(rect.getRm().area() + "--------" + rect.getRm().x);
if (rect.getRm().area() > 300) {// 小于300的pass,说明未填写,完美填图的话是≈1500
if (rect.getRm().x < 68) {
listenAnswer.put(Integer.valueOf(no), "A");
} else if ((rect.getRm().x > 68) && (rect.getRm().x < 148)) {
listenAnswer.put(Integer.valueOf(no), "B");
} else if ((rect.getRm().x > 148) && (rect.getRm().x < 228)) {
listenAnswer.put(Integer.valueOf(no), "C");
} else if (rect.getRm().x > 228) {
listenAnswer.put(Integer.valueOf(no), "D");
}
} else {
listenAnswer.put(Integer.valueOf(no), "未填写");
}
Mat result = new Mat(ch.size(), CvType.CV_8U, new Scalar(255));
Imgproc.drawContours(result, contours, -1, new Scalar(0, 255, 0), 2);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "ch" + (no + 1) + ".png";
File dstfile = new File(destPath);
if (StringUtils.isNotBlank(destPath) && dstfile.isFile() && dstfile.exists()) {
dstfile.delete();
logger.info("删除图片:" + destPath);
}
Highgui.imwrite(destPath, result);
logger.info("生成目标图片==============" + result);
}
String resultValue = "最终结果:试题编号-答案<br> ";
for (Integer key : listenAnswer.keySet()) {
resultValue += "【" + (key + 1) + ":" + listenAnswer.get(key) + "】";
if ((key + 1) % 5 == 0) {
resultValue += "<br>";
}
}
renderString(response, resultValue);
}
}
package com.acts.opencv.base;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
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;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.acts.opencv.common.utils.Constants;
import com.acts.opencv.common.web.BaseController;
@Controller
@RequestMapping(value = "card2")
public class Card2Controller extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(Card2Controller.class);
/**
* 答题卡识别
* @Author Songer
* @param response
* @param imagefile void
* @Date 2018年10月22日
* 更新日志
* 2018年10月22日 Songer 首次创建
*
*/
@RequestMapping(value = "cardMarking")
public void cardMarking(HttpServletResponse response, String imagefile,Integer picno) {
try {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
long t1 = new Date().getTime();
//String sourceimage1 = "D:\\test\\abc\\card\\test4.jpg";
String sourceimage = Constants.PATH + imagefile;
//表格检测,获取到表格内容,是查找识别区域的部分,返回的是校正之后的图像
Mat mat = markingArea(sourceimage);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "cardResult_3.png";
Highgui.imwrite(destPath, mat);
// Highgui.imwrite("D:\\test\\abc\\card\\card111.png", mat);
//具体的答题卡识别过程,主要就是答案识别部分了
String result = cardResult(mat);
renderString(response, result);
long t2 = new Date().getTime();
logger.info("===耗时"+(t2-t1));
} catch (Exception e) {
e.printStackTrace();
logger.error("答题卡识别异常!", e);
}
}
/**
* 此方法主要是通过边缘检测凸包,查找识别区域。即客观题的框
* @Author Songer
* @Date 2018年9月21日
* 更新日志
* 2018年9月21日 Songer 首次创建
*
*/
public static Mat markingArea(String path){
Mat source = Highgui.imread(path, Highgui.CV_LOAD_IMAGE_COLOR);
Mat img = new Mat();;
Mat result= source.clone();
// 彩色转灰度
Imgproc.cvtColor(source, img, Imgproc.COLOR_BGR2GRAY);
//此处图像预处理,可以使用方式1也可以使用方式2都可以,自己也可以测试下2种方式的差异
// //方式1:通过高斯滤波然后边缘检测膨胀来链接边缘,将轮廓连通便于轮廓识别
// // 高斯滤波,降噪
// Imgproc.GaussianBlur(img, img, new Size(3,3), 2, 2);
// // Canny边缘检测
// Imgproc.Canny(img, img, 20, 60, 3, false);
// // 膨胀,连接边缘
// Imgproc.dilate(img, img, new Mat(), new Point(-1,-1), 3, 1, new Scalar(1));
//
//方式2:使用形态学梯度算法,此算法来保留物体的边缘轮廓很有效果
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5,5));
Imgproc.morphologyEx(img, img, Imgproc.MORPH_GRADIENT, element);
//图像二值化,使用的是OTSU二值化,阈值为170,也可以使用自适用二值化
// Imgproc.adaptiveThreshold(img,img, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 51, 10);
Imgproc.threshold(img,img, 170, 255, Imgproc.THRESH_BINARY|Imgproc.THRESH_OTSU);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "cardResult_1.png";
Highgui.imwrite(destPath, img);
// Highgui.imwrite("D:\\test\\abc\\card\\card1.png", img);
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
//轮廓查找,主要就是找最外表格框
Imgproc.findContours(img, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// 找出轮廓对应凸包的四边形拟合
List<MatOfPoint> squares = new ArrayList<>();
List<MatOfPoint> hulls = new ArrayList<>();
MatOfInt hull = new MatOfInt();
MatOfPoint2f approx = new MatOfPoint2f();
approx.convertTo(approx, CvType.CV_32F);
for (MatOfPoint contour: contours) {
// 边框的凸包
Imgproc.convexHull(contour, hull);
// 用凸包计算出新的轮廓点
Point[] contourPoints = contour.toArray();
int[] indices = hull.toArray();
List<Point> newPoints = new ArrayList<>();
for (int index : indices) {
newPoints.add(contourPoints[index]);
}
MatOfPoint2f contourHull = new MatOfPoint2f();
contourHull.fromList(newPoints);
// 多边形拟合凸包边框(此时的拟合的精度较低)
Imgproc.approxPolyDP(contourHull, approx, Imgproc.arcLength(contourHull, true)*0.02, true);
// 筛选出面积大于某一阈值的,且四边形的各个角度都接近直角的凸四边形
MatOfPoint approxf1 = new MatOfPoint();
approx.convertTo(approxf1, CvType.CV_32S);
//此处是筛选表格框,面积大于40000
if (approx.rows() == 4 && Math.abs(Imgproc.contourArea(approx)) > 40000 &&
Imgproc.isContourConvex(approxf1)) {
double maxCosine = 0;
for (int j = 2; j < 5; j++) {
double cosine = Math.abs(getAngle(approxf1.toArray()[j%4], approxf1.toArray()[j-2], approxf1.toArray()[j-1]));
maxCosine = Math.max(maxCosine, cosine);
}
// 考虑到图片倾斜等情况,角度大概72度
if (maxCosine < 0.3) {
MatOfPoint tmp = new MatOfPoint();
contourHull.convertTo(tmp, CvType.CV_32S);
squares.add(approxf1);
hulls.add(tmp);
}
}
}
// 找出外接矩形最大的四边形
int index = findLargestSquare(squares);
MatOfPoint largest_square = squares.get(index);
if (largest_square.rows() == 0 || largest_square.cols() == 0)
return result;
// 找到这个最大的四边形对应的凸边框,再次进行多边形拟合,此次精度较高,拟合的结果可能是大于4条边的多边形
MatOfPoint contourHull = hulls.get(index);
MatOfPoint2f tmp = new MatOfPoint2f();
contourHull.convertTo(tmp, CvType.CV_32F);
Imgproc.approxPolyDP(tmp, approx, 3, true);
List<Point> newPointList = new ArrayList<>();
double maxL = Imgproc.arcLength(approx, true) * 0.02;
// 找到高精度拟合时得到的顶点中 距离小于低精度拟合得到的四个顶点maxL的顶点,排除部分顶点的干扰
for (Point p : approx.toArray()) {
if (!(getSpacePointToPoint(p, largest_square.toList().get(0)) > maxL &&
getSpacePointToPoint(p, largest_square.toList().get(1)) > maxL &&
getSpacePointToPoint(p, largest_square.toList().get(2)) > maxL &&
getSpacePointToPoint(p, largest_square.toList().get(3)) > maxL)) {
newPointList.add(p);
}
}
// 找到剩余顶点连线中,边长大于 2 * maxL的四条边作为四边形物体的四条边
List<double[]> lines = new ArrayList<>();
for (int i = 0; i < newPointList.size(); i++) {
Point p1 = newPointList.get(i);
Point p2 = newPointList.get((i+1) % newPointList.size());
if (getSpacePointToPoint(p1, p2) > 2 * maxL) {
lines.add(new double[]{p1.x, p1.y, p2.x, p2.y});
logger.info("p1x:"+p1.x+" p1y:"+p1.y+" p2x:"+p2.x+" p2y:"+p2.y);
//画出4条边线,真正识别过程中这些都是可以注释掉的,只是为了方便观察
Core.line(source, new Point(p1.x, p1.y), new Point( p2.x, p2.y), new Scalar(255, 0, 0),4);
}
}
destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "cardResult_2.png";
Highgui.imwrite(destPath, source);
// Highgui.imwrite("D:\\test\\abc\\card\\card2.png", source);
// 计算出这四条边中 相邻两条边的交点,即物体的四个顶点
List<Point> corners = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
Point corner = computeIntersect(lines.get(i),lines.get((i+1) % lines.size()));
corners.add(corner);
}
// 对顶点顺时针排序
sortCorners(corners);
// 计算目标图像的尺寸
Point p0 = corners.get(0);
Point p1 = corners.get(1);
Point p2 = corners.get(2);
Point p3 = corners.get(3);
logger.info(" "+p0.x+" "+p0.y);
logger.info(" "+p1.x+" "+p1.y);
logger.info(" "+p2.x+" "+p2.y);
logger.info(" "+p3.x+" "+p3.y);
double space0 = getSpacePointToPoint(p0, p1);
double space1 = getSpacePointToPoint(p1, p2);
double space2 = getSpacePointToPoint(p2, p3);
double space3 = getSpacePointToPoint(p3, p0);
// 使用最宽和最长的边作为进行图像矫正目标图像的长宽
double imgWidth = space1 > space3 ? space1 : space3;
double imgHeight = space0 > space2 ? space0 : space2;
logger.info("imgWidth:"+imgWidth+" imgHeight:"+imgHeight);
// 如果提取出的图片宽小于高,则旋转90度,因为示例中的矩形框是宽>高的,如果宽小于高应该是图片旋转了
if (imgWidth > imgHeight) {
logger.info("----in");
double temp = imgWidth;
imgWidth = imgHeight;
imgHeight = temp;
Point tempPoint = p0.clone();
p0 = p1.clone();
p1 = p2.clone();
p2 = p3.clone();
p3 = tempPoint.clone();
}
// Mat quad = Mat.zeros((int)imgHeight * 2, (int)imgWidth * 2, CvType.CV_8UC3);
Mat quad = Mat.zeros((int)imgHeight, (int)imgWidth, CvType.CV_8UC3);
MatOfPoint2f cornerMat = new MatOfPoint2f(p0, p1, p2, p3);
// MatOfPoint2f quadMat = new MatOfPoint2f(new Point(imgWidth*0.4, imgHeight*1.6),
// new Point(imgWidth*0.4, imgHeight*0.4),
// new Point(imgWidth*1.6, imgHeight*0.4),
// new Point(imgWidth*1.6, imgHeight*1.6));
//quadMat目标图像的点设置,以之前取出的最长的长宽作为新图像的长宽,创建一个图层
MatOfPoint2f quadMat = new MatOfPoint2f(new Point(0, 0),
new Point(imgWidth, 0),
new Point(imgWidth, imgHeight),
new Point(0, imgHeight));
// 提取图像,使用warpPerspective做图像的透视变换
Mat transmtx = Imgproc.getPerspectiveTransform(cornerMat, quadMat);
Imgproc.warpPerspective(result, quad, transmtx, quad.size());
return quad;
}
// 根据三个点计算中间那个点的夹角 pt1 pt0 pt2
private static double getAngle(Point pt1, Point pt2, Point pt0){
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/Math.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// 找到最大的正方形轮廓
private static int findLargestSquare(List<MatOfPoint> squares) {
if (squares.size() == 0)
return -1;
int max_width = 0;
int max_height = 0;
int max_square_idx = 0;
int currentIndex = 0;
for (MatOfPoint square : squares) {
Rect rectangle = Imgproc.boundingRect(square);
if (rectangle.width >= max_width && rectangle.height >= max_height) {
max_width = rectangle.width;
max_height = rectangle.height;
max_square_idx = currentIndex;
}
currentIndex++;
}
return max_square_idx;
}
// 点到点的距离
private static double getSpacePointToPoint(Point p1, Point p2) {
double a = p1.x - p2.x;
double b = p1.y - p2.y;
return Math.sqrt(a * a + b * b);
}
// 两直线的交点
private static Point computeIntersect(double[] a, double[] b) {
if (a.length != 4 || b.length != 4)
throw new ClassFormatError();
double x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3], x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
double d = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4));
if (d != 0) {
Point pt = new Point();
pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
return pt;
}
else
return new Point(-1, -1);
}
// 对多个点按顺时针排序
private static void sortCorners(List<Point> corners) {
if (corners.size() == 0) return;
Point p1 = corners.get(0);
int index = 0;
for (int i = 1; i < corners.size(); i++) {
Point point = corners.get(i);
if (p1.x > point.x) {
p1 = point;
index = i;
}
}
corners.set(index, corners.get(0));
corners.set(0, p1);
Point lp = corners.get(0);
for (int i = 1; i < corners.size(); i++) {
for (int j = i + 1; j < corners.size(); j++) {
Point point1 = corners.get(i);
Point point2 = corners.get(j);
if ((point1.y-lp.y*1.0)/(point1.x-lp.x)>(point2.y-lp.y*1.0)/(point2.x-lp.x)) {
Point temp = point1.clone();
corners.set(i, corners.get(j));
corners.set(j, temp);
}
}
}
}
/**
* 答题卡识别,增加注释
* 已得到矫正后的图像后,进行后续答案识别算法过程
* @Author Songer
* @param mat
* @return String
* @Date 2018年12月19日
* 更新日志
* 2018年12月19日 王嵩 首次创建
*
*/
public String cardResult(Mat mat){
//设置剪切的边距,目的是裁剪表格边框,防止边框影响轮廓查找,这里设置为20像素
int cutsize = 20;
Mat img_cut = mat.submat(cutsize,mat.rows()-cutsize,cutsize,mat.cols()-cutsize);new Mat();
Mat img_gray = img_cut.clone();
//图像灰度化
Imgproc.cvtColor(img_cut, img_gray, Imgproc.COLOR_BGR2GRAY);
//图像二值化,注意是反向二值化以及OTSU算法
Imgproc.threshold(img_gray,img_gray, 170, 255, Imgproc.THRESH_BINARY_INV|Imgproc.THRESH_OTSU);
Mat temp = img_gray.clone();
//此处使用的是方式2,形态学梯度算法保留填图选项的边框
// //方式1:通过高斯滤波然后边缘检测膨胀来链接边缘,将轮廓连通便于轮廓识别
// // 高斯滤波,降噪
// Imgproc.GaussianBlur(temp, temp, new Size(3,3), 2, 2);
// // Canny边缘检测
// Imgproc.Canny(temp, temp, 20, 60, 3, false);
// // 膨胀,连接边缘
// Imgproc.dilate(temp, temp, new Mat(), new Point(-1,-1), 3, 1, new Scalar(1));
//方式2:使用形态学梯度算法,此算法来保留物体的边缘轮廓很有效果
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5,5));
Imgproc.morphologyEx(temp, temp, Imgproc.MORPH_GRADIENT, element);
Imgproc.threshold(temp, temp, 170, 255, Imgproc.THRESH_BINARY|Imgproc.THRESH_OTSU);
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "cardResult_4.png";
Highgui.imwrite(destPath, temp);
// Highgui.imwrite("D:\\test\\abc\\card\\card3.png", temp);
//按比例截取,此处是根据原答题卡的各列的比例截取成4列,学号那列还要横向分隔一下,上半部分是学号下半部分是答题卡
Mat cut1 = temp.submat(0,temp.rows(),0,(int)(0.275*temp.cols()));
Mat cut_gray1 = img_gray.submat(0,img_gray.rows(),0,(int)(0.275*img_gray.cols()));
Mat cut2 = temp.submat(0,temp.rows(),(int)(0.275*temp.cols()),(int)(0.518*temp.cols()));
Mat cut_gray2 = img_gray.submat(0,img_gray.rows(),(int)(0.275*img_gray.cols()),(int)(0.518*img_gray.cols()));
Mat cut3 = temp.submat(0,temp.rows(),(int)(0.518*temp.cols()),(int)(0.743*temp.cols()));
Mat cut_gray3 = img_gray.submat(0,img_gray.rows(),(int)(0.518*img_gray.cols()),(int)(0.743*img_gray.cols()));
Mat cut4 = temp.submat((int)(0.387*temp.rows()),temp.rows(),(int)(0.743*temp.cols()),temp.cols());
Mat cut_gray4 = img_gray.submat((int)(0.387*img_gray.rows()),img_gray.rows(),(int)(0.743*img_gray.cols()),img_gray.cols());
//学号
Mat cut5 = temp.submat(0,(int)(0.387*temp.rows()),(int)(0.743*temp.cols()),temp.cols());
Mat cut_gray5 = img_gray.submat(0,(int)(0.387*img_gray.rows()),(int)(0.743*img_gray.cols()),img_gray.cols());
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut1.png", cut1);
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut1_gary.png", cut_gray1);
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut2.png", cut2);
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut3.png", cut3);
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut4.png", cut4);
// Highgui.imwrite("D:\\test\\abc\\card\\card_cut5.png", cut5);
List<String> resultList = new ArrayList<String>();
//按列处理
List<String> list1 = processByCol(cut1,cut_gray1,img_cut,5);
List<String> list2 = processByCol(cut2,cut_gray2,img_cut,5);
List<String> list3 = processByCol(cut3,cut_gray3,img_cut,4);
List<String> list4 = processByCol(cut4,cut_gray4,img_cut,4);
//学号单独处理
List<String> list5 = processByCol(cut5,cut_gray5,img_cut,4);
resultList.addAll(list1);
resultList.addAll(list2);
resultList.addAll(list3);
resultList.addAll(list4);
String studentNo = getStudentNo(list5);
logger.info("学生学号为:"+studentNo);
StringBuffer result = new StringBuffer("学生学号为:"+studentNo);
for (int i = 0; i < resultList.size(); i++) {
result.append(resultList.get(i));
logger.info(resultList.get(i));
}
return result.toString();
}
/**
* 根据列单独处理
* @Author Songer
* @param cut1 传入的答案列,一般1-20一列
* @param cut_gary 传入的答案列未处理过
* @param temp 表格范围mat
* @param toIndex 列答案数,即每几个答案一组
* @Date 2018年9月20日
* 更新日志
* 2018年9月20日 Songer 首次创建
*
*/
private static List<String> processByCol(Mat cut1,Mat cut_gray,Mat temp,int answerCols) {
List<String> result = new ArrayList<String>();
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
List<MatOfPoint> answerList = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
//进行轮廓查找,注意因为该答题卡特征是闭合填图区域,所以用这种方式,如果是非闭合填涂区域,则不适用
Imgproc.findContours(cut1.clone(), contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// logger.info(contours.size());
// logger.info("-----w------"+(temp.width()*80/2693-20));
// logger.info("-----h------"+(temp.height()*80/2764-20));
for(int i = 0;i<contours.size();i++){
MatOfPoint mop= contours.get(i);
//每个轮廓给出外包矩形,便于操作
Rect rect = Imgproc.boundingRect(mop);
// logger.info("-----------"+rect.width+" "+rect.height);
//一个填图区域大概占整个表格的w:80/2733 h:0/2804,,所以排除掉太小的轮廓和过大的轮廓
// //绘制每个轮廓图
// Imgproc.drawContours(cut1, contours, i, new Scalar(170), 2);
//此处是为了排除杂点,较小或较大的轮廓都是非填图选项,可以排除,可以按实际情况灵活变动限制条件,最好输出出轮廓便于观察
if(rect.width>(temp.width()*80/2693-20) && rect.height>(temp.height()*80/2764-20) && rect.width<temp.width()*0.05 && rect.height<temp.height()*0.05){
// if(rect.width>50&&rect.height>50&&rect.area()>2500&&rect.area()<10000){
// Core.rectangle(img_cut, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y
// + rect.height), new Scalar(0, 255, 0), 2);
answerList.add(mop);
}
}
Collections.sort(answerList, new Comparator<MatOfPoint>() {
//按照y坐标升序排列
@Override
public int compare(MatOfPoint o1, MatOfPoint o2) {
Rect rect1 = Imgproc.boundingRect(o1);
Rect rect2 = Imgproc.boundingRect(o2);
if(rect1.y<rect2.y){
return -1;
}else if(rect1.y>rect2.y){
return 1;
}else{
return 0;
}
}
});
//每4/5个一组组成新list,并按x坐标排序
//该方式依赖于预处理后的图片没有干扰轮廓。如果图像处理不佳,干扰项没排除,那么此处的分组就会错乱。
//提供一个优化思路:对于闭合填涂区域的可以使用水平垂直投影进行坐标定位点的判定。
int queno = 1;
int totoSize = answerList.size();
for (int i = 0; i < totoSize; i+=answerCols) {
int toIndex = i+answerCols;
if(toIndex>totoSize){
toIndex = totoSize-1;
}
List<MatOfPoint> newList = answerList.subList(i,toIndex);
Collections.sort(newList, new Comparator<MatOfPoint>() {
//按照x坐标升序排列
@Override
public int compare(MatOfPoint o1, MatOfPoint o2) {
Rect rect1 = Imgproc.boundingRect(o1);
Rect rect2 = Imgproc.boundingRect(o2);
if(rect1.x<rect2.x){
return -1;
}else if(rect1.x>rect2.x){
return 1;
}else{
return 0;
}
}
});
String resultChoose = "";
for (int j = 0; j < newList.size(); j++) {
Imgproc.drawContours(cut_gray, newList, j, new Scalar(170), 2);
//掩模提取出轮廓
Mat mask = Mat.zeros(cut_gray.size(), CvType.CV_8UC1);
//绘制轮廓便于观察
Imgproc.drawContours(mask, newList, j, new Scalar(255), -1);
Mat dst = new Mat();
Core.bitwise_and(cut_gray, mask, dst);
//获取填涂百分比,填涂区域的二值化后取出非0点/掩模的轮廓面积
double p100 = Core.countNonZero(dst) * 100 / Core.countNonZero(mask);
String anno = index2ColName(j);
//认为非0像素超过80%的算是填涂
if(p100>80){
resultChoose += anno;
logger.info(p100+" 第"+queno+"行:选项("+anno+")填涂");
}else{
logger.info(p100+" 第"+queno+"行:选项("+anno+")未填涂");
}
// Highgui.imwrite("D:\\test\\abc\\card\\card_x"+i+j+".png", dst);
if(i==0&&j==0){//输出一下第一个掩模
String destPath = Constants.PATH + Constants.DEST_IMAGE_PATH + "cardResult_5.png";
Highgui.imwrite(destPath, dst);
}
}
result.add(resultChoose);
queno++;
}
// Highgui.imwrite("D:\\test\\abc\\card\\card4.png", cut1);
return result;
}
//编号转答案0-A 1-B
public static String index2ColName(int index){
if (index < 0) {
return null;
}
int num = 65;// A的Unicode码
String colName = "";
do {
if (colName.length() > 0) {
index--;
}
int remainder = index % 26;
colName = ((char) (remainder + num)) + colName;
index = (int) ((index - remainder) / 26);
} while (index > 0);
return colName;
}
/**根据表元的列名转换为列号
* @param colName 列名, 从A开始
* @return A1->0; B1->1...AA1->26
*/
public static int colName2Index(String colName) {
int index = -1;
int num = 65;// A的Unicode码
int length = colName.length();
for (int i = 0; i < length; i++) {
char c = colName.charAt(i);
if (Character.isDigit(c)) break;// 确定指定的char值是否为数字
index = (index + 1) * 26 + (int)c - num;
}
return index;
}
/**
* 根据返回的结果集转换为学号
* 因为学号部分的处理跟答案一样是一横行进行处理的,同时返回的是ABCDE等选项结果
* 转换公式为:学生学号=遍历list的index值*(A=1000,B=100,C=10,D=1)相加
* @Author Songer
* @param resultList
* @return String
* @Date 2018年9月20日
* 更新日志
* 2018年9月20日 Songer 首次创建
*
*/
public static String getStudentNo(List<String> resultList){
int studentNo = 0;
for (int i = 0; i < resultList.size(); i++) {
String result = resultList.get(i);
if (result.contains("A")) {
studentNo += 1000 * i;
}
if (result.contains("B")) {
studentNo += 100 * i;
}
if (result.contains("C")) {
studentNo += 10 * i;
}
if (result.contains("D")) {
studentNo += i;
}
}
NumberFormat formatter = new DecimalFormat("0000");
String number = formatter.format(studentNo);
return number;
}
}
/**
* Copyright © 2016-2020 公众学业 All rights reserved.
*/
package com.acts.opencv.common.web;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.http.MediaType;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.acts.opencv.common.mapper.JsonMapper;
import com.acts.opencv.common.utils.Constants;
/**
* 控制器支持类
* 创建者 Songer
* 创建时间 2016年7月21日
*
*/
public abstract class BaseController{
/**
* 添加Model消息
* @param message
*/
protected void addMessage(Model model, String... messages) {
StringBuilder sb = new StringBuilder();
for (String message : messages){
sb.append(message).append(messages.length>1?"<br/>":"");
}
model.addAttribute("message", sb.toString());
}
/**
* 添加Flash消息
* @param message
*/
protected void addMessage(RedirectAttributes redirectAttributes, String... messages) {
StringBuilder sb = new StringBuilder();
for (String message : messages){
sb.append(message).append(messages.length>1?"<br/>":"");
}
redirectAttributes.addFlashAttribute("message", sb.toString());
}
/**
* 客户端返回JSON字符串
* @param response
* @param string
* @return
*/
protected void renderString(HttpServletResponse response, Object object) {
try {
response.reset();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader("Cache-Control", "no-cache, must-revalidate");
PrintWriter writer = response.getWriter();
writer.write(JsonMapper.toJsonString(object));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端返回图片类型
* @param response
* @param object void
* @throws IOException
* @Date 2018年3月13日
* 更新日志
* 2018年3月13日 Songer 首次创建
*
*/
protected void renderImage(HttpServletResponse response, byte[] object) {
try {
response.reset();
response.setContentType("image/*");
ServletOutputStream output = response.getOutputStream();
output.flush();
output.write(object);
output.close();
// ServletOutputStream output = response.getOutputStream();
// FileInputStream fis = new FileInputStream("E:\\tomcat7\\webapps\\java_opencv\\statics\\distimage\\lena.png");
// byte[] buffer = new byte[1024];
// int i = -1;
// while ((i = fis.read(buffer)) != -1) {
// output.write(buffer, 0, i);
// }
// output.flush();
// output.close();
// fis.close();
} catch (IOException e) {
// 如果是ClientAbortException异常,可以不用管,原因是页面参数变化太快,response请求被中断
try {
// response.reset();
PrintWriter writer = response.getWriter();
response.setContentType("text/html;charset=utf-8");
writer.write("无法打开图片!");
writer.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
/**
* 客户端返回字符串
* @param response
* @param string
* @return
*/
protected void renderString(HttpServletResponse response) {
renderString(response, Constants.SUCCESS);
}
/**
* 初始化数据绑定
* 1. 将所有传递进来的String进行HTML编码,防止XSS攻击
* 2. 将字段中Date类型转换为String类型
*/
@InitBinder
protected void initBinder(WebDataBinder binder) {
// String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
}
@Override
public String getAsText() {
Object value = getValue();
return value != null ? value.toString() : "";
}
});
}
}
4.其他工具类
package com.acts.opencv.common.utils;
import org.springframework.web.context.ContextLoader;
/**
* 常量 创建者 Songer 创建时间 2018年3月09日
*
*/
public class Constants {
public static final String CURRENT_USER = "UserInfo";
public static final String WECHAT_USER = "weChatUserInfo";
public static final String REFERENCE_CODE = "referenceCode";
public static final String SUCCESS = "success";
public static final String ERROR = "error";
public static final String SF_FILE_SEPARATOR = System.getProperty("file.separator");// 文件分隔符
public static final String SF_LINE_SEPARATOR = System.getProperty("line.separator");// 行分隔符
public static final String SF_PATH_SEPARATOR = System.getProperty("path.separator");// 路径分隔符
public static final String PATH = ContextLoader.getCurrentWebApplicationContext().getServletContext().getRealPath("/");
/**
* 文件
*/
public static final String SOURCE_IMAGE_PATH = Constants.SF_FILE_SEPARATOR + "statics"
+ Constants.SF_FILE_SEPARATOR + "sourceimage" + Constants.SF_FILE_SEPARATOR;// 图片原地址
public static final String DEST_IMAGE_PATH = Constants.SF_FILE_SEPARATOR + "statics" + Constants.SF_FILE_SEPARATOR
+ "destimage" + Constants.SF_FILE_SEPARATOR;// 图片生成地址
/**
* 返回参数规范
*/
/** 区分类型 1 -- 无错误,Code重复 */
public static final String CODE_DUPLICATE = "1";
/** 区分类型 2 -- 无错误,名称重复 */
public static final String NAME_DUPLICATE = "2";
/** 区分类型 3 -- 数量超出 */
public static final String NUMBER_OVER = "3";
/** 区分类型 0 -- 无错误,程序正常执行 */
public static final String NO_ERROR = "0";
/** 区分类型 -1 -- 无错误,返回结果为空 */
public static final String NULL_POINTER = "-1";
/** 区分类型 -2 -- 错误,参数不正确 */
public static final String INCORRECT_PARAMETER = "-2";
/** 区分类型 -3 -- 错误,程序执行错误 */
public static final String PROGRAM_EXECUTION_ERROR = "-3";
/** 区分类型 -5 -- 错误,数据已删除 */
public static final String DATA_DELETED = "-5";
/** 区分类型 -6 -- 错误,参数不一致(验证码) */
public static final String DATA_NOT_SAME = "-6";
/**json文件缺失 */
public static final String NO_JSON_FILE = "-7";
/**
* 分页中可能用到的常量
*/
public static final Integer PAGE_SIZE=10;//一页共有十条内容
}
package com.acts.opencv.common.utils;
import org.opencv.core.Rect;
public class RectComp implements Comparable<Object> {
private Rect rm;
public Rect getRm() {
return rm;
}
public void setRm(Rect rm) {
this.rm = rm;
}
public RectComp() {
super();
}
public RectComp(Rect rm) {
super();
this.rm = rm;
}
// @Override
// public int compareTo(Object object) {
// if(this == object){
// return 0;
// } else if (object != null && object instanceof RectComp) {
// RectComp rect = (RectComp) object;
// if (rm.x <= rect.rm.x) {
// return -1;
// }else{
// return 1;
// }
// }else{
// return -1;
// }
// }
@Override
// 按面积排序,最大的放第一个
public int compareTo(Object object) {
if(this == object){
return 0;
} else if (object != null && object instanceof RectComp) {
RectComp rect = (RectComp) object;
if (rm.area() >= rect.rm.area()) {
return -1;
} else {
return 1;
}
} else {
return -1;
}
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)