diff --git a/src/main/java/com/yuxue/easypr/core/CoreFunc.java b/src/main/java/com/yuxue/easypr/core/CoreFunc.java index c3d368a0..e0f8c226 100644 --- a/src/main/java/com/yuxue/easypr/core/CoreFunc.java +++ b/src/main/java/com/yuxue/easypr/core/CoreFunc.java @@ -40,19 +40,6 @@ public class CoreFunc { final float minref_sv = 64; final float minabs_sv = 95; - // opencv颜色识别的HSV中各个颜色所对应的H的范围: Orange 0-22 Yellow 22- 38 Green 38-75 Blue 75-130 - // blue的H范围 - final int min_blue = 100; - final int max_blue = 140; - - // yellow的H范围 - final int min_yellow = 15; - final int max_yellow = 40; - - // green的H范围 - final int min_green = 8; - final int max_green = 150; - // 转到HSV空间进行处理,颜色搜索主要使用的是H分量进行蓝色与黄色的匹配工作 Mat src_hsv = new Mat(); opencv_imgproc.cvtColor(src, src_hsv, opencv_imgproc.CV_BGR2HSV); @@ -62,24 +49,8 @@ public class CoreFunc { opencv_core.merge(hsvSplit, src_hsv); // 匹配模板基色,切换以查找想要的基色 - int min_h = 0; - int max_h = 0; - switch (r) { - case BLUE: - min_h = min_blue; - max_h = max_blue; - break; - case YELLOW: - min_h = min_yellow; - max_h = max_yellow; - break; - case GREEN: - min_h = min_green; - max_h = max_green; - break; - default: - break; - } + int min_h = r.minH; + int max_h = r.maxH; float diff_h = (float) ((max_h - min_h) / 2); int avg_h = (int) (min_h + diff_h); diff --git a/src/main/java/com/yuxue/enumtype/PlateColor.java b/src/main/java/com/yuxue/enumtype/PlateColor.java index 1b13f799..69d89704 100644 --- a/src/main/java/com/yuxue/enumtype/PlateColor.java +++ b/src/main/java/com/yuxue/enumtype/PlateColor.java @@ -8,17 +8,23 @@ package com.yuxue.enumtype; */ public enum PlateColor { - BLUE("BLUE","蓝牌"), - GREEN("GREEN","绿牌"), - YELLOW("YELLOW","黄牌"), - UNKNOWN("UNKNOWN","未知"); + BLUE("BLUE","蓝牌", 100, 140), + GREEN("GREEN","绿牌", 8, 150), + YELLOW("YELLOW","黄牌", 15, 40), + UNKNOWN("UNKNOWN","未知", 0, 0); public final String code; public final String desc; - PlateColor(String code, String desc) { + // opencv颜色识别的HSV中各个颜色所对应的H的范围: Orange 0-22 Yellow 22- 38 Green 38-75 Blue 75-130 + public final int minH; + public final int maxH; + + PlateColor(String code, String desc, int minH, int maxH) { this.code = code; this.desc = desc; + this.minH = minH; + this.maxH = maxH; } public static String getDesc(String code) { @@ -40,8 +46,8 @@ public enum PlateColor { } return null; } - - + + public String code() { return this.code; } @@ -49,5 +55,5 @@ public enum PlateColor { public String desc() { return this.desc; } - + } diff --git a/src/main/java/com/yuxue/util/ImageUtil.java b/src/main/java/com/yuxue/util/ImageUtil.java index 5ce29dee..f84e3cb8 100644 --- a/src/main/java/com/yuxue/util/ImageUtil.java +++ b/src/main/java/com/yuxue/util/ImageUtil.java @@ -32,11 +32,11 @@ import com.google.common.collect.Maps; public class ImageUtil { private static String DEFAULT_BASE_TEST_PATH = "D:/PlateDetect/temp/"; - + static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } - + // 车牌定位处理步骤,该map用于表示步骤图片的顺序 private static Map debugMap = Maps.newLinkedHashMap(); static { @@ -46,17 +46,21 @@ public class ImageUtil { debugMap.put("sobel", 3); // Sobel 运算,得到图像的一阶水平方向导数 debugMap.put("threshold", 4); //图像二值化 debugMap.put("morphology", 5); // 图像闭操作 - debugMap.put("contours", 6); // 提取外部轮廓 - debugMap.put("screenblock", 7); // 外部轮廓筛选 - debugMap.put("result", 8); // 原图处理结果 - debugMap.put("crop", 9); // 切图 - debugMap.put("resize", 10); // 切图resize - debugMap.put("char_threshold", 11); // - - // debugMap.put("char_clearLiuDing", 10); // 去除柳钉 - // debugMap.put("specMat", 11); - // debugMap.put("chineseMat", 12); - // debugMap.put("char_auxRoi", 13); + debugMap.put("clearSmallConnArea", 6); // 降噪 + debugMap.put("clearAngleConn", 7); // 降噪 + debugMap.put("clearHole", 8); // 降噪 + + + debugMap.put("contours", 9); // 提取外部轮廓 + debugMap.put("screenblock", 10); // 外部轮廓筛选 + debugMap.put("crop", 11); // 切图 + debugMap.put("resize", 12); // 切图resize + + // debugMap.put("char_threshold", 10); + // debugMap.put("char_clearLiuDing", 11); // 去除柳钉 + // debugMap.put("specMat", 12); + // debugMap.put("chineseMat", 13); + // debugMap.put("char_auxRoi", 14); } public static void main(String[] args) { @@ -64,7 +68,7 @@ public class ImageUtil { String tempPath = DEFAULT_BASE_TEST_PATH + "test/"; String filename = tempPath + "/100_yuantu.jpg"; // filename = tempPath + "/100_yuantu4.jpg"; - filename = tempPath + "/109_crop_0.png"; + // filename = tempPath + "/109_crop_0.png"; Mat src = Imgcodecs.imread(filename); @@ -84,10 +88,10 @@ public class ImageUtil { List contours = ImageUtil.contours(src, morphology, debug, tempPath); Vector rects = ImageUtil.screenBlock(src, contours, debug, tempPath); - + Vector dst = new Vector(); PalteUtil.hasPlate(rects, dst, "D:/PlateDetect/train/plate_detect_svm/svm2.xml", debug, tempPath); - + System.err.println("识别到的车牌数量:" + dst.size()); // ImageUtil.rgb2Hsv(src, debug, tempPath); @@ -247,16 +251,19 @@ public class ImageUtil { if (debug) { Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology0.jpg", dst); } - + // 去除小连通区域 - Mat a = clearSmallConnArea(dst, 3, 10, false, tempPath); - Mat b = clearSmallConnArea(a, 10, 3, false, tempPath); - // 去除孔洞 - Mat c = clearHole(b, 3, 10, false, tempPath); - Mat d = clearHole(c, 10, 3, false, tempPath); + Mat a = clearSmallConnArea(dst, 3, 10, debug, tempPath); + Mat b = clearSmallConnArea(a, 10, 3, debug, tempPath); + // 按斜边去除 + // Mat e = clearSmallConnArea(b, 2, debug, tempPath); + // 去除孔洞 + Mat c = clearHole(b, 3, 10, debug, tempPath); + Mat d = clearHole(c, 10, 3, debug, tempPath); + if (debug) { - Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology1.jpg", d); + Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology2.jpg", d); } return d; } @@ -376,7 +383,7 @@ public class ImageUtil { public static final int DEFAULT_VERIFY_MIN = 1; public static final int DEFAULT_VERIFY_MAX = 30;*/ private static boolean checkPlateSize(RotatedRect mr) { - + // 切图面积取值范围 int min = 44 * 14 * DEFAULT_VERIFY_MIN; int max = 44 * 14 * DEFAULT_VERIFY_MAX; @@ -395,7 +402,7 @@ public class ImageUtil { return min <= area && area <= max && rmin <= r && r <= rmax; } - + /** * rgb图像转换为hsv图像 * @param inMat @@ -555,7 +562,7 @@ public class ImageUtil { Mat dst = new Mat(inMat.size(), CvType.CV_8UC1); inMat.copyTo(dst); - + // 初始化的图像全部为0,未检查; 全黑图像 Mat label = new Mat(inMat.size(), CvType.CV_8UC1); @@ -564,12 +571,12 @@ public class ImageUtil { for (int j = 0; j < inMat.cols(); j++) { if (inMat.get(i, j)[0] > 10) { // 对于二值图,0代表黑色,255代表白色 label.put(i, j, white); // 中心点 - + int x1 = i - rowLimit < 0 ? 0 : i - rowLimit; int x2 = i + rowLimit >= inMat.rows() ? inMat.rows()-1 : i + rowLimit; int y1 = j - colsLimit < 0 ? 0 : j - colsLimit ; int y2 = j + colsLimit >= inMat.cols() ? inMat.cols()-1 : j + colsLimit ; - + int count = 0; if(inMat.get(x1, y1)[0] > 10) {// 左上角 count++; @@ -583,7 +590,7 @@ public class ImageUtil { if(inMat.get(x2, y2)[0] > 10) { // 右下角 count++; } - + // 根据中心点+limit,定位四个角生成一个矩形, // 将四个角都是白色的矩形,内部的黑点标记为 要被替换的对象 if(count >=4 ) { @@ -598,7 +605,7 @@ public class ImageUtil { } } } - + // 黑色替换成白色 for (int i = 0; i < inMat.rows(); i++) { for (int j = 0; j < inMat.cols(); j++) { @@ -608,13 +615,14 @@ public class ImageUtil { } } if (debug) { - Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology2.jpg", dst); + Imgcodecs.imwrite(tempPath + (debugMap.get("clearHole") + 100) + "_clearHole.jpg", dst); } return dst; } - + /** * 清除二值图像的细小连接 + * 按水平或者垂直方向清除 * @param inMat * @param rowLimit * @param colsLimit @@ -624,24 +632,24 @@ public class ImageUtil { */ public static Mat clearSmallConnArea(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) { int uncheck = 0, black = 1, white = 2; - + Mat dst = new Mat(inMat.size(), CvType.CV_8UC1); inMat.copyTo(dst); - + // 初始化的图像全部为0,未检查; 全黑图像 Mat label = new Mat(inMat.size(), CvType.CV_8UC1); - + // 标记所有的白色区域 for (int i = 0; i < inMat.rows(); i++) { for (int j = 0; j < inMat.cols(); j++) { if (inMat.get(i, j)[0] < 10) { // 对于二值图,0代表黑色,255代表白色 label.put(i, j, black); // 中心点 - + int x1 = i - rowLimit < 0 ? 0 : i - rowLimit; int x2 = i + rowLimit >= inMat.rows() ? inMat.rows()-1 : i + rowLimit; int y1 = j - colsLimit < 0 ? 0 : j - colsLimit ; int y2 = j + colsLimit >= inMat.cols() ? inMat.cols()-1 : j + colsLimit ; - + int count = 0; if(inMat.get(x1, y1)[0] < 10) {// 左上角 count++; @@ -655,7 +663,7 @@ public class ImageUtil { if(inMat.get(x2, y2)[0] < 10) { // 右下角 count++; } - + // 根据 中心点+limit,定位四个角生成一个矩形, // 将四个角都是黑色的矩形,内部的白点标记为 要被替换的对象 if(count >= 4) { @@ -670,7 +678,7 @@ public class ImageUtil { } } } - // 黑色替换成白色 + // 白色替换成黑色 for (int i = 0; i < inMat.rows(); i++) { for (int j = 0; j < inMat.cols(); j++) { if(label.get(i, j)[0] == white) { @@ -679,11 +687,89 @@ public class ImageUtil { } } if (debug) { - Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology1.jpg", dst); + Imgcodecs.imwrite(tempPath + (debugMap.get("clearSmallConnArea") + 100) + "_clearSmallConnArea.jpg", dst); } return dst; } + + /** + * 清除二值图像的细小连接 + * 按45度斜边清除 + * @param inMat + * @param limit + * @param angle + * @param debug + * @param tempPath + * @return + */ + public static Mat clearSmallConnArea(Mat inMat, int limit, Boolean debug, String tempPath) { + int uncheck = 0, black = 1, white = 2; + + Mat dst = new Mat(inMat.size(), CvType.CV_8UC1); + inMat.copyTo(dst); + + // 初始化的图像全部为0,未检查; 全黑图像 + Mat label = new Mat(inMat.size(), CvType.CV_8UC1); + + // 标记所有的白色区域 + for (int i = 0; i < inMat.rows(); i++) { + for (int j = 0; j < inMat.cols(); j++) { + if (inMat.get(i, j)[0] < 10) { // 对于二值图,0代表黑色,255代表白色 + label.put(i, j, black); // 中心点 + + int x1 = i - limit < 0 ? 0 : i - limit; + int x2 = i + limit >= inMat.rows() ? inMat.rows()-1 : i + limit; + int y1 = j - limit < 0 ? 0 : j - limit ; + int y2 = j + limit >= inMat.cols() ? inMat.cols()-1 : j + limit ; + + int count = 0; + if(inMat.get(x1, y1)[0] < 10) {// 左上角 + count++; + } + if(inMat.get(x1, y2)[0] < 10) { // 左下角 + count++; + } + if(inMat.get(x2, y1)[0] < 10) { // 右上角 + count++; + } + if(inMat.get(x2, y2)[0] < 10) { // 右下角 + count++; + } + + // 根据 中心点+limit,定位四个角生成一个矩形, + // 将2个角都是黑色的线,内部的白点标记为 要被替换的对象 + if(count == 2) { + // 【\】 斜对角线 + for (int n = x1, m = y1; n < x2; n++, m++) { + if (inMat.get(n, m)[0] > 10 && label.get(n, m)[0] == uncheck) { + label.put(n, m, white); + } + } + // 【/】 斜对角线 + for (int n = x1, m = y2; n < x2; n++, m--) { + if (inMat.get(n, m)[0] > 10 && label.get(n, m)[0] == uncheck) { + label.put(n, m, white); + } + } + } + } + } + } + // 白色替换成黑色 + for (int i = 0; i < inMat.rows(); i++) { + for (int j = 0; j < inMat.cols(); j++) { + if(label.get(i, j)[0] == white) { + dst.put(i, j, 0); + } + } + } + + if (debug) { + Imgcodecs.imwrite(tempPath + (debugMap.get("clearAngleConn") + 100) + "_clearAngleConn.jpg", dst); + } + return dst; + } diff --git a/src/main/java/com/yuxue/util/PalteUtil.java b/src/main/java/com/yuxue/util/PalteUtil.java index 1dea384d..2859f82b 100644 --- a/src/main/java/com/yuxue/util/PalteUtil.java +++ b/src/main/java/com/yuxue/util/PalteUtil.java @@ -91,9 +91,11 @@ public class PalteUtil { float flag = svm.predict(samples); if (flag == 0) { - System.err.println("目标符合"); dst.add(src); - Imgcodecs.imwrite(tempPath + "199_plate_reco_" + i + ".png", src); + if(debug) { + System.err.println("目标符合"); + Imgcodecs.imwrite(tempPath + "199_plate_reco_" + i + ".png", src); + } i++; } else { System.out.println("目标不符合");