diff --git a/src/main/java/com/yuxue/util/ImageUtil.java b/src/main/java/com/yuxue/util/ImageUtil.java index 26cc0ec4..65b29d09 100644 --- a/src/main/java/com/yuxue/util/ImageUtil.java +++ b/src/main/java/com/yuxue/util/ImageUtil.java @@ -24,6 +24,7 @@ import org.opencv.imgproc.Imgproc; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.yuxue.enumtype.PlateColor; /** @@ -69,8 +70,8 @@ public class ImageUtil { public static void main(String[] args) { Instant start = Instant.now(); String tempPath = DEFAULT_BASE_TEST_PATH + "test/"; - String filename = tempPath + "/100_yuantu.jpg"; - filename = tempPath + "/100_yuantu3.jpg"; + String filename = tempPath + "/100_yuantu.jpg"; + filename = tempPath + "/100_yuantu1.jpg"; // filename = tempPath + "/109_crop_0.png"; Mat src = Imgcodecs.imread(filename); @@ -79,10 +80,10 @@ public class ImageUtil { Mat gsMat = ImageUtil.gaussianBlur(src, debug, tempPath); - Mat grey = ImageUtil.grey(gsMat, debug, tempPath); + Mat gray = ImageUtil.gray(gsMat, debug, tempPath); - Mat sobel = ImageUtil.sobel(grey, debug, tempPath); - // Mat sobel = ImageUtil.scharr(grey, debug, tempPath); + Mat sobel = ImageUtil.sobel(gray, debug, tempPath); + // Mat sobel = ImageUtil.scharr(gray, debug, tempPath); Mat threshold = ImageUtil.threshold(sobel, debug, tempPath); @@ -92,17 +93,32 @@ public class ImageUtil { Vector rects = ImageUtil.screenBlock(src, contours, debug, tempPath); + PlateUtil.loadSvmModel("D:/PlateDetect/train/plate_detect_svm/svm2.xml"); + PlateUtil.loadAnnModel("D:/PlateDetect/train/chars_recognise_ann/ann.xml"); + Vector dst = new Vector(); - PalteUtil.hasPlate(rects, dst, "D:/PlateDetect/train/plate_detect_svm/svm2.xml", debug, tempPath); - + PlateUtil.hasPlate(rects, dst, debug, tempPath); + System.err.println("识别到的车牌数量:" + dst.size()); + dst.stream().forEach(inMat -> { + PlateColor color = PlateUtil.getPlateColor(inMat, true, debug, tempPath); + System.err.println(color.desc); + + Vector charMat = new Vector(); + PlateUtil.charsSegment(inMat, color, charMat, debug, tempPath); + + }); + + /*String filename = tempPath + "/hsvMat_1590994270425.jpg"; + Mat src = Imgcodecs.imread(filename); + Vector charMat = new Vector(); + PlateUtil.charsSegment(src, PlateColor.BLUE, charMat, true, tempPath);*/ // ImageUtil.rgb2Hsv(src, debug, tempPath); // ImageUtil.getHSVValue(src, debug, tempPath); Instant end = Instant.now(); System.err.println("总耗时:" + Duration.between(start, end).toMillis()); - } @@ -131,7 +147,7 @@ public class ImageUtil { * @param tempPath * @return */ - public static Mat grey(Mat inMat, Boolean debug, String tempPath) { + public static Mat gray(Mat inMat, Boolean debug, String tempPath) { Mat dst = new Mat(); Imgproc.cvtColor(inMat, dst, Imgproc.COLOR_BGR2GRAY); if (debug) { @@ -291,7 +307,6 @@ public class ImageUtil { // CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内 Imgproc.findContours(inMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE); - // 在小连接处分割轮廓 if (debug) { Mat result = new Mat(); src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图 @@ -346,7 +361,7 @@ public class ImageUtil { Mat img_crop = new Mat(); Imgproc.getRectSubPix(src, rect_size, mr.center, img_crop); if (debug) { - Imgcodecs.imwrite(tempPath + debugMap.get("crop") + "_crop_" + j + ".png", img_crop); + // Imgcodecs.imwrite(tempPath + debugMap.get("crop") + "_crop_" + j + ".png", img_crop); } // 处理切图,调整为指定大小 Mat resized = new Mat(HEIGHT, WIDTH, TYPE); @@ -424,7 +439,7 @@ public class ImageUtil { Core.merge(hsvSplit, dst); if (debug) { - Imgcodecs.imwrite(tempPath + "hsvMat_"+System.currentTimeMillis()+".jpg", dst); + // Imgcodecs.imwrite(tempPath + "hsvMat_"+System.currentTimeMillis()+".jpg", dst); } return dst; } diff --git a/src/main/java/com/yuxue/util/PalteUtil.java b/src/main/java/com/yuxue/util/PlateUtil.java similarity index 51% rename from src/main/java/com/yuxue/util/PalteUtil.java rename to src/main/java/com/yuxue/util/PlateUtil.java index c2c60414..ff3f5eb4 100644 --- a/src/main/java/com/yuxue/util/PalteUtil.java +++ b/src/main/java/com/yuxue/util/PlateUtil.java @@ -1,15 +1,22 @@ package com.yuxue.util; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Vector; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.opencv.core.Core; +import org.opencv.core.CvType; import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.Rect; +import org.opencv.core.Scalar; import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; import org.opencv.ml.ANN_MLP; import org.opencv.ml.SVM; @@ -27,15 +34,18 @@ import com.yuxue.train.SVMTrain; * @author yuxue * @date 2020-05-28 15:11 */ -public class PalteUtil { - +public class PlateUtil { + // 车牌定位处理步骤,该map用于表示步骤图片的顺序 private static Map debugMap = Maps.newLinkedHashMap(); static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); - + + debugMap.put("platePredict", 0); debugMap.put("colorMatch", 0); - debugMap.put("char_threshold", 0); + debugMap.put("plateThreshold", 0); + debugMap.put("plateContours", 0); + debugMap.put("plateCrop", 0); debugMap.put("char_clearLiuDing", 0); // 去除柳钉 debugMap.put("specMat", 0); debugMap.put("chineseMat", 0); @@ -47,6 +57,9 @@ public class PalteUtil { entry.setValue(index); index ++; } + + /*loadSvmModel("D:/PlateDetect/train/plate_detect_svm/svm2.xml"); + loadAnnModel("D:/PlateDetect/train/chars_recognise_ann/ann.xml");*/ } private static SVM svm = SVM.create(); @@ -97,22 +110,17 @@ public class PalteUtil { */ public static final int DEFAULT_WIDTH = 136; public static final int DEFAULT_HEIGHT = 36; - public static void hasPlate(Vector inMat, Vector dst, String modelPath, - Boolean debug, String tempPath) { - loadSvmModel(modelPath); - + public static void hasPlate(Vector inMat, Vector dst, Boolean debug, String tempPath) { int i = 0; for (Mat src : inMat) { if(src.rows() == DEFAULT_HEIGHT && src.cols() == DEFAULT_WIDTH) { Mat samples = SVMTrain.getFeature(src); - float flag = svm.predict(samples); - if (flag == 0) { dst.add(src); if(debug) { System.err.println("目标符合"); - Imgcodecs.imwrite(tempPath + "199_plate_reco_" + i + ".png", src); + Imgcodecs.imwrite(tempPath + debugMap.get("platePredict") + "_platePredict" + i + ".png", src); } i++; } else { @@ -122,8 +130,8 @@ public class PalteUtil { } return; } - - + + /** * 判断切图车牌颜色 * @param inMat @@ -132,7 +140,7 @@ public class PalteUtil { public static PlateColor getPlateColor(Mat inMat, Boolean adaptive_minsv, Boolean debug, String tempPath) { // 判断阈值 final float thresh = 0.49f; - + if(colorMatch(inMat, PlateColor.BLUE, adaptive_minsv, debug, tempPath) > thresh) { return PlateColor.BLUE; } @@ -145,7 +153,7 @@ public class PalteUtil { return PlateColor.UNKNOWN; } - + /** * 颜色匹配计算 * @param inMat @@ -197,18 +205,161 @@ public class PalteUtil { } } } - + // 获取颜色匹配后的二值灰度图 List hsvSplit = Lists.newArrayList(); Core.split(hsvMat, hsvSplit); - Mat grey = hsvSplit.get(2); - - float percent = (float) Core.countNonZero(grey) / (grey.rows() * grey.cols()); + Mat gray = hsvSplit.get(2); + + float percent = (float) Core.countNonZero(gray) / (gray.rows() * gray.cols()); if (debug) { - Imgcodecs.imwrite(tempPath + debugMap.get("colorMatch") + "_colorMatch.jpg", grey); + Imgcodecs.imwrite(tempPath + debugMap.get("colorMatch") + "_colorMatch.jpg", gray); } - return percent; } + + + /** + * 车牌切图,分割成单个字符切图 + * @param inMat 输入原始图像 + * @param charMat 返回字符切图vector + * @param debug + * @param tempPath + */ + public static final int DEFAULT_ANGLE = 30; // 角度判断所用常量 + public static void charsSegment(Mat inMat, PlateColor color, Vector charMat, Boolean debug, String tempPath) { + Mat gray = new Mat(); + Imgproc.cvtColor(inMat, gray, Imgproc.COLOR_BGR2GRAY); + + Mat threshold = new Mat(); + switch (color) { + case BLUE: + Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY); + break; + + case YELLOW: + Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY_INV); + break; + + case GREEN: + Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY_INV); + break; + + default: + return; + } + + // 图片处理,降噪等 + if (debug) { + Imgcodecs.imwrite(tempPath + debugMap.get("plateThreshold") + "_plateThreshold.jpg", threshold); + } + + // 获取轮廓 + Mat contour = new Mat(); + threshold.copyTo(contour); + + List contours = Lists.newArrayList(); + // 提取外部轮廓 + Imgproc.findContours(contour, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE); + + if (debug) { + Mat result = new Mat(); + inMat.copyTo(result); + Imgproc.drawContours(result, contours, -1, new Scalar(0, 0, 255, 255)); + Imgcodecs.imwrite(tempPath + debugMap.get("plateContours") + "_plateContours.jpg", result); + } + + + Vector rt = new Vector(); + for (int i = 0; i < contours.size(); i++) { + Rect mr = Imgproc.boundingRect(contours.get(i)); + if (checkCharSizes(mr)) { + rt.add(mr); + } + } + if(null == rt || rt.size() <= 0) { + return; + } + Vector sorted = new Vector(); + sortRect(rt, sorted); + + Vector dst = new Vector(); + for (int i = 0; i < sorted.size(); i++) { + Mat img_crop = new Mat(threshold, sorted.get(i)); + img_crop = preprocessChar(img_crop); + dst.add(img_crop); + Imgcodecs.imwrite(tempPath + debugMap.get("plateCrop") + "_plateCrop_" + i + ".jpg", img_crop); + } + + + + return; + } + + /** + * 字符预处理: 统一每个字符的大小 + * + * @param in + * @return + */ + final static int CHAR_SIZE = 20; + private static Mat preprocessChar(Mat in) { + int h = in.rows(); + int w = in.cols(); + Mat transformMat = Mat.eye(2, 3, CvType.CV_32F); + int m = Math.max(w, h); + transformMat.put(0, 2, (m - w) / 2f); + transformMat.put(1, 2, (m - h) / 2f); + + Mat warpImage = new Mat(m, m, in.type()); + Imgproc.warpAffine(in, warpImage, transformMat, warpImage.size(), Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, new Scalar(0)); + + Mat resized = new Mat(CHAR_SIZE, CHAR_SIZE, CvType.CV_8UC3); + Imgproc.resize(warpImage, resized, resized.size(), 0, 0, Imgproc.INTER_CUBIC); + + return resized; + } + + + + /** + * 字符尺寸验证;去掉尺寸不符合的图块 + * 此处计算宽高比意义不大,因为字符 1 的宽高比干扰就已经很大了 + * @param r + * @return + */ + public static Boolean checkCharSizes(Rect r) { + float minHeight = 15f; + float maxHeight = 35f; + double charAspect = r.size().width / r.size().height; + return charAspect <1 && minHeight <= r.size().height && r.size().height < maxHeight; + } + + + + /** + * 将Rect按位置从左到右进行排序 + * @param vecRect + * @param out + * @return + */ + public static void sortRect(Vector vecRect, Vector out) { + Map map = Maps.newHashMap(); + for (int i = 0; i < vecRect.size(); ++i) { + map.put(vecRect.get(i).x, i); + } + Set set = map.keySet(); + Object[] arr = set.toArray(); + Arrays.sort(arr); + for (Object key : arr) { + out.add(vecRect.get(map.get(key))); + } + return; + } + + + + + }