具体算法实现:具体算法实现——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 &copy; 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;
		}
	}

}

Logo

更多推荐