From c5d672340556e46674641bc2122dfbba80663ffb Mon Sep 17 00:00:00 2001 From: yuxue Date: Sun, 24 May 2020 22:53:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yuxue/easypr/core/PlateLocate.java | 84 ++++--- src/main/java/com/yuxue/util/ImageUtil.java | 209 ++++++++++-------- 2 files changed, 153 insertions(+), 140 deletions(-) diff --git a/src/main/java/com/yuxue/easypr/core/PlateLocate.java b/src/main/java/com/yuxue/easypr/core/PlateLocate.java index 750cd530..c0ac308c 100644 --- a/src/main/java/com/yuxue/easypr/core/PlateLocate.java +++ b/src/main/java/com/yuxue/easypr/core/PlateLocate.java @@ -24,7 +24,7 @@ import org.bytedeco.javacpp.opencv_core.Size; * @date 2020-04-24 15:33 */ public class PlateLocate { - + // PlateLocate所用常量 public static final int DEFAULT_GAUSSIANBLUR_SIZE = 5; public static final int SOBEL_SCALE = 1; @@ -46,7 +46,7 @@ public class PlateLocate { final float DEFAULT_ERROR = 0.6f; final float DEFAULT_ASPECT = 3.75f; - + // 角度判断所用常量 public static final int DEFAULT_ANGLE = 30; @@ -68,7 +68,7 @@ public class PlateLocate { // 是否开启调试模式,0关闭,非0开启 protected boolean debug = true; - + // 开启调试模式之后,切图文件保存路径 protected String tempPath = Constant.DEFAULT_TEMP_DIR + System.currentTimeMillis() + "/"; @@ -193,51 +193,45 @@ public class PlateLocate { int k = 1; for (int i = 0; i < rects.size(); i++) { RotatedRect minRect = rects.get(i); - if (verifySizes(minRect)) { - - if (debug) { - Point2f rect_points = new Point2f(4); - minRect.points(rect_points); - - for (int j = 0; j < 4; j++) { - Point pt1 = new Point(new CvPoint2D32f(rect_points.position(j))); - Point pt2 = new Point(new CvPoint2D32f(rect_points.position((j + 1) % 4))); - - line(result, pt1, pt2, new Scalar(0, 255, 255, 255), 1, 8, 0); - } - } + if (debug) { + Point2f rect_points = new Point2f(4); + minRect.points(rect_points); - // rotated rectangle drawing - // 旋转这部分代码确实可以将某些倾斜的车牌调整正,但是它也会误将更多正的车牌搞成倾斜!所以综合考虑,还是不使用这段代码。 - // 2014-08-14,由于新到的一批图片中发现有很多车牌是倾斜的,因此决定再次尝试这段代码。 + for (int j = 0; j < 4; j++) { + Point pt1 = new Point(new CvPoint2D32f(rect_points.position(j))); + Point pt2 = new Point(new CvPoint2D32f(rect_points.position((j + 1) % 4))); - float r = minRect.size().width() / minRect.size().height(); - float angle = minRect.angle(); - Size rect_size = new Size((int) minRect.size().width(), (int) minRect.size().height()); - if (r < 1) { - angle = 90 + angle; - rect_size = new Size(rect_size.height(), rect_size.width()); - } - // 如果抓取的方块旋转超过m_angle角度,则不是车牌,放弃处理 - if (angle - this.angle < 0 && angle + this.angle > 0) { - // Create and rotate image - Mat rotmat = getRotationMatrix2D(minRect.center(), angle, 1); - Mat img_rotated = new Mat(); - warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC - - Mat resultMat = showResultMat(img_rotated, rect_size, minRect.center(), k++); - resultVec.add(resultMat); + line(result, pt1, pt2, new Scalar(0, 255, 255, 255), 1, 8, 0); } } - } - if (debug) { - opencv_imgcodecs.imwrite(tempPath + "debug_result.jpg", result); + + // rotated rectangle drawing + // 旋转这部分代码确实可以将某些倾斜的车牌调整正,但是它也会误将更多正的车牌搞成倾斜!所以综合考虑,还是不使用这段代码。 + // 2014-08-14,由于新到的一批图片中发现有很多车牌是倾斜的,因此决定再次尝试这段代码。 + + float r = minRect.size().width() / minRect.size().height(); + float angle = minRect.angle(); + Size rect_size = new Size((int) minRect.size().width(), (int) minRect.size().height()); + if (r < 1) { + angle = 90 + angle; + rect_size = new Size(rect_size.height(), rect_size.width()); + } + // 如果抓取的方块旋转超过m_angle角度,则不是车牌,放弃处理 + if (angle - this.angle < 0 && angle + this.angle > 0) { + // Create and rotate image + Mat rotmat = getRotationMatrix2D(minRect.center(), angle, 1); + Mat img_rotated = new Mat(); + warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC + + Mat resultMat = showResultMat(img_rotated, rect_size, minRect.center(), k++); + resultVec.add(resultMat); + } } return resultVec; } - + /** * 对minAreaRect获得的最小外接矩形,用纵横比进行判断 * @@ -251,7 +245,7 @@ public class PlateLocate { float aspect = this.aspect; int min = 44 * 14 * verifyMin; // minimum area int max = 44 * 14 * verifyMax; // maximum area - + // Get only patchs that match to a respect ratio. float rmin = aspect - aspect * error; float rmax = aspect + aspect * error; @@ -260,7 +254,7 @@ public class PlateLocate { float r = mr.size().width() / mr.size().height(); if (r < 1) r = mr.size().height() / mr.size().width(); - + return area >= min && area <= max && r >= rmin && r <= rmax; } @@ -289,8 +283,8 @@ public class PlateLocate { return resultResized; } - - + + public String getTempPath() { return tempPath; } @@ -298,7 +292,7 @@ public class PlateLocate { public void setTempPath(String tempPath) { this.tempPath = tempPath; } - + public void setGaussianBlurSize(int gaussianBlurSize) { this.gaussianBlurSize = gaussianBlurSize; } @@ -357,5 +351,5 @@ public class PlateLocate { public boolean getDebug() { return debug; } - + } diff --git a/src/main/java/com/yuxue/util/ImageUtil.java b/src/main/java/com/yuxue/util/ImageUtil.java index 9d4c0c99..a197e435 100644 --- a/src/main/java/com/yuxue/util/ImageUtil.java +++ b/src/main/java/com/yuxue/util/ImageUtil.java @@ -1,6 +1,5 @@ package com.yuxue.util; - import java.util.Arrays; import java.util.Map; import java.util.Set; @@ -19,7 +18,6 @@ import org.bytedeco.javacpp.opencv_imgcodecs; import org.bytedeco.javacpp.opencv_imgproc; import com.google.common.collect.Maps; -import com.yuxue.constant.Constant; /** @@ -30,23 +28,23 @@ import com.yuxue.constant.Constant; public class ImageUtil { private static SVM svm = SVM.create(); - + private static ANN_MLP ann=ANN_MLP.create(); - - + + private static String DEFAULT_BASE_TEST_PATH = "D:/PlateDetect/temp/"; - - public static void loadSVM(String path) { + + public static void loadSvmModel(String path) { svm.clear(); svm=SVM.load(path); } - + // 加载ann配置文件 图像转文字的训练库文件 - public static void loadModel(String path) { + public static void loadAnnModel(String path) { ann.clear(); ann = ANN_MLP.load(path); } - + // 车牌定位处理步骤,该map用于表示步骤图片的顺序 private static Map debugMap = Maps.newLinkedHashMap(); static { @@ -67,36 +65,40 @@ public class ImageUtil { // debugMap.put("specMat", 11); // debugMap.put("chineseMat", 12); // debugMap.put("char_auxRoi", 13); - + // 加载训练库文件 - //loadModel(Constant.DEFAULT_ANN_PATH); - //loadSVM(Constant.DEFAULT_SVM_PATH); + //loadAnnModel(Constant.DEFAULT_ANN_PATH); + //loadSvmModel(Constant.DEFAULT_SVM_PATH); } public static void main(String[] args) { - + String tempPath = DEFAULT_BASE_TEST_PATH + "test/"; String filename = tempPath + "/100_yuantu.jpg"; - - Mat inMat = opencv_imgcodecs.imread(filename); + + Mat src = opencv_imgcodecs.imread(filename); Boolean debug = true; - Mat gsMat = ImageUtil.gaussianBlur(inMat, debug, tempPath); + Mat gsMat = ImageUtil.gaussianBlur(src, debug, tempPath); Mat grey = ImageUtil.grey(gsMat, debug, tempPath); Mat sobel = ImageUtil.sobel(grey, debug, tempPath); - + Mat threshold = ImageUtil.threshold(sobel, debug, tempPath); - + Mat morphology = ImageUtil.morphology(threshold, debug, tempPath); - - MatVector contours = ImageUtil.contours(inMat, morphology, debug, tempPath); + MatVector contours = ImageUtil.contours(src, morphology, debug, tempPath); + + Vector rects = ImageUtil.screenBlock(src, contours, debug, tempPath); // ImageUtil.rgb2Hsv(inMat, debug, tempPath); + + + System.err.println("done!!!"); } @@ -117,7 +119,7 @@ public class ImageUtil { return dst; } - + /** * 将图像进行灰度化 * @param inMat @@ -200,7 +202,7 @@ public class ImageUtil { public static Mat morphology(Mat inMat, Boolean debug, String tempPath) { Mat dst = new Mat(); Size size = new Size(DEFAULT_MORPH_SIZE_WIDTH, DEFAULT_MORPH_SIZE_HEIGHT); - + Mat element = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_RECT, size); opencv_imgproc.morphologyEx(inMat, dst, opencv_imgproc.MORPH_CLOSE, element); @@ -209,8 +211,8 @@ public class ImageUtil { } return dst; } - - + + /** * Find 轮廓 of possibles plates 求轮廓。求出图中所有的轮廓。 * 这个算法会把全图的轮廓都计算出来,因此要进行筛选。 @@ -222,20 +224,21 @@ public class ImageUtil { */ public static MatVector contours(Mat src, Mat inMat, Boolean debug, String tempPath) { MatVector contours = new MatVector(); - opencv_imgproc.findContours(inMat, contours, // a vector of contours - opencv_imgproc.CV_RETR_EXTERNAL, // 提取外部轮廓 - opencv_imgproc.CV_CHAIN_APPROX_NONE); // all pixels of each contours - + // 提取外部轮廓 + opencv_imgproc.findContours(inMat, contours, opencv_imgproc.CV_RETR_EXTERNAL, opencv_imgproc.CV_CHAIN_APPROX_NONE); + if (debug) { + Mat result = new Mat(); + src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图 // 将轮廓描绘到原图 - opencv_imgproc.drawContours(src, contours, -1, new Scalar(0, 0, 255, 255)); + opencv_imgproc.drawContours(result, contours, -1, new Scalar(0, 0, 255, 255)); // 输出带轮廓的原图 - opencv_imgcodecs.imwrite(tempPath + (debugMap.get("contours") + 100) + "_contours.jpg", src); + opencv_imgcodecs.imwrite(tempPath + (debugMap.get("contours") + 100) + "_contours.jpg", result); } return contours; } - - + + /** * 根据轮廓, 筛选出可能是车牌的图块 * @param src @@ -249,78 +252,94 @@ public class ImageUtil { public static final int DEFAULT_VERIFY_MIN = 3; public static final int DEFAULT_VERIFY_MAX = 20; public static final int DEFAULT_ANGLE = 30; // 角度判断所用常量 + public static final int WIDTH = 136; + public static final int HEIGHT = 36; + public static final int TYPE = opencv_core.CV_8UC3; + @SuppressWarnings("resource") public static Vector screenBlock(Mat src, MatVector contours, Boolean debug, String tempPath){ - MatVector rects = new MatVector(); - // Vector rects = new Vector(); + + Vector dst = new Vector(); + MatVector mv = new MatVector(); for (int i = 0; i < contours.size(); ++i) { // RotatedRect 该类表示平面上的旋转矩形,有三个属性: 矩形中心点(质心); 边长(长和宽); 旋转角度 RotatedRect mr = opencv_imgproc.minAreaRect(contours.get(i)); - + float angle = Math.abs(mr.angle()); - - if (verifySizes(mr)) { - // rects.add(mr); - // 判断旋转角度 ±30° - if (angle <= DEFAULT_ANGLE) { - rects.put(mr); - // 旋转角度 - Mat rotmat = opencv_imgproc.getRotationMatrix2D(mr.center(), angle, 1); - Mat img_rotated = new Mat(); - opencv_imgproc.warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC - + + if (verifySizes(mr) && angle <= DEFAULT_ANGLE) { // 判断尺寸及旋转角度 ±30°,排除不合法的图块 + + if (debug) { // 描绘出筛选后的轮廓 + mv.put(contours.get(i)); + Mat result = new Mat(); + src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图 + // 将轮廓描绘到原图 + opencv_imgproc.drawContours(result, mv, -1, new Scalar(0, 0, 255, 255)); + // 输出带轮廓的原图 + opencv_imgcodecs.imwrite(tempPath + (debugMap.get("screenblock") + 100) + "_screenblock.jpg", result); + } + + // 旋转角度,根据需要是否进行角度旋转 + Size rect_size = new Size((int) mr.size().width(), (int) mr.size().height()); + if (mr.size().width() / mr.size().height() < 1) { // 宽度小于高度 + angle = 90 + angle; // 旋转90° + rect_size = new Size(rect_size.height(), rect_size.width()); } + Mat rotmat = opencv_imgproc.getRotationMatrix2D(mr.center(), angle, 1); + Mat img_rotated = new Mat(); + opencv_imgproc.warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC + + // 切图 + Mat img_crop = new Mat(); + opencv_imgproc.getRectSubPix(src, rect_size, mr.center(), img_crop); + + if (debug) { + opencv_imgcodecs.imwrite(tempPath + (debugMap.get("crop") + 100) + "_crop_" + i + ".jpg", img_crop); + } + + // 处理切图,调整为指定大小 + Mat resized = new Mat(HEIGHT, WIDTH, TYPE); + opencv_imgproc.resize(img_crop, resized, resized.size(), 0, 0, opencv_imgproc.INTER_CUBIC); + if (debug) { + opencv_imgcodecs.imwrite(tempPath + (debugMap.get("resize") + 100) + "_resize_" + i + ".jpg", resized); + } + dst.add(resized); } } - - if (debug) { - // 将轮廓描绘到原图 - opencv_imgproc.drawContours(src, rects, -1, new Scalar(0, 0, 255, 255)); - // 输出带轮廓的原图 - opencv_imgcodecs.imwrite(tempPath + (debugMap.get("screenblock") + 100) + "_screenblock.jpg", src); - } - - return null; + + return dst; } - + /** - * 对minAreaRect获得的最小外接矩形,用纵横比进行判断 - * @param mr - * @return - */ - private static boolean verifySizes(RotatedRect mr) { - - // China car plate size: 440mm*140mm,aspect 3.142857 - int min = 44 * 14 * DEFAULT_VERIFY_MIN; - int max = 44 * 14 * DEFAULT_VERIFY_MAX; - - // Get only patchs that match to a respect ratio. - float rmin = DEFAULT_ASPECT - DEFAULT_ASPECT * DEFAULT_ERROR; - float rmax = DEFAULT_ASPECT + DEFAULT_ASPECT * DEFAULT_ERROR; - - // 计算面积 - int area = (int) (mr.size().height() * mr.size().width()); - // 计算纵横比 - float r = mr.size().width() / mr.size().height(); - if (r < 1) { - r = mr.size().height() / mr.size().width(); - } - return min <= area && area <= max && rmin <= r && r <= rmax; - } - - - - - - - - - - - - - - - + * 对minAreaRect获得的最小外接矩形,用纵横比进行判断 + * @param mr + * @return + */ + private static boolean verifySizes(RotatedRect mr) { + + // China car plate size: 440mm*140mm,aspect 3.142857 + int min = 44 * 14 * DEFAULT_VERIFY_MIN; + int max = 44 * 14 * DEFAULT_VERIFY_MAX; + + // Get only patchs that match to a respect ratio. + float rmin = DEFAULT_ASPECT - DEFAULT_ASPECT * DEFAULT_ERROR; + float rmax = DEFAULT_ASPECT + DEFAULT_ASPECT * DEFAULT_ERROR; + + // 计算面积 + int area = (int) (mr.size().height() * mr.size().width()); + // 计算纵横比 + float r = mr.size().width() / mr.size().height(); + if (r < 1) { + r = mr.size().height() / mr.size().width(); + } + return min <= area && area <= max && rmin <= r && r <= rmax; + } + + + + + + + /** * rgb图像转换为hsv图像