diff --git a/src/main/java/com/yuxue/train/SVMTrain.java b/src/main/java/com/yuxue/train/SVMTrain.java index abdc66e0..050fd976 100644 --- a/src/main/java/com/yuxue/train/SVMTrain.java +++ b/src/main/java/com/yuxue/train/SVMTrain.java @@ -4,6 +4,7 @@ import java.io.File; import java.util.List; import org.opencv.core.Core; +import org.opencv.core.Core.MinMaxLocResult; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.TermCriteria; @@ -13,7 +14,9 @@ import org.opencv.ml.Ml; import org.opencv.ml.SVM; import org.opencv.ml.TrainData; +import com.google.common.collect.Lists; import com.yuxue.constant.Constant; +import com.yuxue.enumtype.Direction; import com.yuxue.util.FileUtil; /** @@ -42,7 +45,7 @@ public class SVMTrain { private static final String DEFAULT_PATH = "D:/PlateDetect/train/plate_detect_svm/"; // 训练模型文件保存位置 - private static final String MODEL_PATH = DEFAULT_PATH + "svm.xml"; + private static final String MODEL_PATH = DEFAULT_PATH + "svm2.xml"; static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); @@ -87,17 +90,25 @@ public class SVMTrain { Mat inMat = Imgcodecs.imread(path); // 读取样本文件 // 创建一个行数为sample_num, 列数为 rows*cols 的矩阵; 用于存放样本 - if (trainingDataMat == null) { + /*if (trainingDataMat == null) { trainingDataMat = new Mat(sample_num, inMat.rows() * inMat.cols(), CvType.CV_32F); - } - + }*/ + // 样本文件处理,这里是为了过滤不需要的特征,减少训练时间 // 根据实际情况需要进行处理 - Mat greyMat = new Mat(); + /*Mat greyMat = new Mat(); Imgproc.cvtColor(inMat, greyMat, Imgproc.COLOR_BGR2GRAY); // 转成灰度图 - - Mat dst = new Mat(inMat.rows(), inMat.cols(), inMat.type()); - Imgproc.Canny(greyMat, dst, 130, 250); // 边缘检测 + Mat dst = new Mat(); + Imgproc.threshold(greyMat, dst, 100, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY); + + Mat dst = new Mat(inMat.rows(), inMat.cols(), inMat.type()); + Imgproc.Canny(greyMat, dst, 130, 250);*/ + + Mat dst = getFeature(inMat); + if (trainingDataMat == null) { + trainingDataMat = new Mat(sample_num, dst.rows() * dst.cols(), CvType.CV_32F); + } + // 将样本矩阵转换成只有一行的矩阵,保存为float数组 float[] arr = new float[dst.rows() * dst.cols()]; int l = 0; @@ -108,10 +119,11 @@ public class SVMTrain { l++; } } - + trainingDataMat.put(i, 0, arr); // 多张图合并到一张 + } - + // Imgcodecs.imwrite(DEFAULT_PATH + "trainingDataMat.jpg", trainingDataMat); // 配置SVM训练器参数 @@ -146,27 +158,24 @@ public class SVMTrain { doPridect(svm, DEFAULT_PATH + "test/debug_resize_3.jpg"); doPridect(svm, DEFAULT_PATH + "test/S22_KG2187_3.jpg"); doPridect(svm, DEFAULT_PATH + "test/S22_KG2187_5.jpg"); - doPridect(svm, DEFAULT_PATH + "test/result_0.png"); - doPridect(svm, DEFAULT_PATH + "test/result_1.png"); - doPridect(svm, DEFAULT_PATH + "test/result_2.png"); - doPridect(svm, DEFAULT_PATH + "test/result_3.png"); - doPridect(svm, DEFAULT_PATH + "test/result_4.png"); - doPridect(svm, DEFAULT_PATH + "test/result_5.png"); - doPridect(svm, DEFAULT_PATH + "test/result_6.png"); - doPridect(svm, DEFAULT_PATH + "test/result_7.png"); - doPridect(svm, DEFAULT_PATH + "test/result_8.png"); } public static void doPridect(SVM svm, String imgPath) { - + Mat src = Imgcodecs.imread(imgPath);// 图片大小要和样本一致 - Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY); + /*Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY); + + Mat dst = new Mat(); + Imgproc.threshold(src, dst, 100, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY); + Mat dst = new Mat(); Imgproc.Canny(src, dst, 130, 250); Mat samples = dst.reshape(1, 1); - samples.convertTo(samples, CvType.CV_32F); + samples.convertTo(samples, CvType.CV_32F);*/ + + Mat dst = getFeature(src); // 等价于上面两行代码 /*Mat samples = new Mat(1, dst.cols() * dst.rows(), CvType.CV_32F); @@ -180,14 +189,14 @@ public class SVMTrain { } } samples.put(0, 0, arr);*/ - + // Imgcodecs.imwrite(DEFAULT_PATH + "test_1.jpg", samples); // 如果训练时使用这个标识,那么符合的图像会返回9.0 - float flag = svm.predict(samples); + float flag = svm.predict(dst); + + // System.err.println(flag); - System.err.println(flag); - if (flag == 0) { System.err.println(imgPath + ": 目标符合"); } @@ -209,4 +218,111 @@ public class SVMTrain { return labels; } + + public static Mat getFeature(Mat inMat) { + + Mat histogram = getHistogramFeatures(inMat); + Mat color = getColorFeatures(inMat); + + List list = Lists.newArrayList(); + list.add(histogram); + list.add(color); + + Mat dst = new Mat(); + // hconcat 水平拼接 // vconcat 垂直拼接 + Core.hconcat(list, dst); + return dst; + } + + + public static Mat getHistogramFeatures(Mat src) { + Mat img_grey = new Mat(); + Imgproc.cvtColor(src, img_grey, Imgproc.COLOR_BGR2GRAY); + + Mat img_threshold = new Mat(); + Imgproc.threshold(img_grey, img_threshold, 0, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY); + + // Histogram features + float[] vhist = projectedHistogram(img_threshold, Direction.VERTICAL); + float[] hhist = projectedHistogram(img_threshold, Direction.HORIZONTAL); + + // Last 10 is the number of moments components + int numCols = vhist.length + hhist.length; + + Mat features = Mat.zeros(1, numCols, CvType.CV_32F); + int j = 0; + for (int i = 0; i < vhist.length; i++) { + features.put(0, j, vhist[i]); + j++; + } + for (int i = 0; i < hhist.length; i++) { + features.put(0, j, hhist[i]); + j++; + } + return features; + } + + public static float[] projectedHistogram(Mat inMat, Direction direction){ + Mat img = new Mat(); + inMat.copyTo(img); + int sz = img.rows(); + if(Direction.VERTICAL.equals(direction)) { + sz = img.cols(); + } + // 统计这一行或一列中,非零元素的个数,并保存到nonZeroMat中 + float[] nonZeroMat = new float[sz]; + Core.extractChannel(img, img, 0); // 提取0通道 + for (int j = 0; j < sz; j++) { + Mat data = Direction.HORIZONTAL.equals(direction) ? img.row(j) : img.col(j); + int count = Core.countNonZero(data); + nonZeroMat[j] = count; + } + // Normalize histogram + float max = 1F; + for (int j = 0; j < nonZeroMat.length; j++) { + max = Math.max(max, nonZeroMat[j]); + } + for (int j = 0; j < nonZeroMat.length; j++) { + nonZeroMat[j] /= max; + } + return nonZeroMat; + } + + + public static Mat getColorFeatures(Mat src) { + Mat src_hsv = new Mat(); + Imgproc.cvtColor(src, src_hsv, Imgproc.COLOR_BGR2GRAY); + + int sz = 180; + int[] h = new int[180]; + + for (int i = 0; i < src_hsv.rows(); i++) { + for (int j = 0; j < src_hsv.cols(); j++) { + int H = (int) src_hsv.get(i, j)[0];// 0-180 + if (H > sz - 1) { + H = sz - 1; + } + if (H < 0) { + H = 0; + } + h[H]++; + } + } + // 创建黑色的图 + Mat features = Mat.zeros(1, sz, CvType.CV_32F); + + for (int j = 0; j < sz; j++) { + features.put(0, j, (float)h[j]); + } + + MinMaxLocResult m = Core.minMaxLoc(features); + double max = m.maxVal; + + if (max > 0) { + features.convertTo(features, -1, 1.0f / max, 0); + } + return features; + } + + } diff --git a/src/main/java/com/yuxue/util/ImageUtil.java b/src/main/java/com/yuxue/util/ImageUtil.java index 9cb2b07c..04fc50ef 100644 --- a/src/main/java/com/yuxue/util/ImageUtil.java +++ b/src/main/java/com/yuxue/util/ImageUtil.java @@ -21,6 +21,7 @@ import org.opencv.imgproc.Imgproc; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.yuxue.constant.Constant; /** @@ -40,14 +41,14 @@ public class ImageUtil { // 车牌定位处理步骤,该map用于表示步骤图片的顺序 private static Map debugMap = Maps.newLinkedHashMap(); static { - debugMap.put("yuantu", 0); // 高斯模糊 + debugMap.put("yuantu", 0); // 原图 debugMap.put("gaussianBlur", 1); // 高斯模糊 debugMap.put("gray", 2); // 图像灰度化 - debugMap.put("sobel", 3); // Sobel 算子 + debugMap.put("sobel", 3); // Sobel 运算,得到图像的一阶水平方向导数 debugMap.put("threshold", 4); //图像二值化 debugMap.put("morphology", 5); // 图像闭操作 debugMap.put("contours", 6); // 提取外部轮廓 - debugMap.put("screenblock", 7); // 提取外部轮廓 + debugMap.put("screenblock", 7); // 外部轮廓筛选 debugMap.put("result", 8); // 原图处理结果 debugMap.put("crop", 9); // 切图 debugMap.put("resize", 10); // 切图resize @@ -68,7 +69,7 @@ public class ImageUtil { String tempPath = DEFAULT_BASE_TEST_PATH + "test/"; String filename = tempPath + "/100_yuantu.jpg"; - filename = tempPath + "/100_yuantu2.jpg"; + filename = tempPath + "/100_yuantu1.jpg"; //filename = tempPath + "/109_crop_0.png"; Mat src = Imgcodecs.imread(filename); @@ -89,6 +90,11 @@ 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); // ImageUtil.getHSVValue(src, debug, tempPath); @@ -254,7 +260,7 @@ public class ImageUtil { 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); // 去除孔洞 @@ -263,8 +269,8 @@ public class ImageUtil { if (debug) { Imgcodecs.imwrite(tempPath + (debugMap.get("morphology") + 100) + "_morphology1.jpg", d); - } - return d; + }*/ + return dst; } diff --git a/src/main/java/com/yuxue/util/PalteUtil.java b/src/main/java/com/yuxue/util/PalteUtil.java index 22e667af..1dea384d 100644 --- a/src/main/java/com/yuxue/util/PalteUtil.java +++ b/src/main/java/com/yuxue/util/PalteUtil.java @@ -5,14 +5,14 @@ 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.imgproc.Imgproc; +import org.opencv.imgcodecs.Imgcodecs; import org.opencv.ml.ANN_MLP; import org.opencv.ml.SVM; import com.yuxue.constant.Constant; import com.yuxue.enumtype.PlateColor; +import com.yuxue.train.SVMTrain; /** @@ -23,11 +23,11 @@ import com.yuxue.enumtype.PlateColor; * @date 2020-05-28 15:11 */ public class PalteUtil { - + static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } - + private static SVM svm = SVM.create(); private static ANN_MLP ann=ANN_MLP.create(); @@ -42,17 +42,17 @@ public class PalteUtil { ann.clear(); ann = ANN_MLP.load(path); } - + public static void main(String[] args) { /*System.err.println(PalteUtil.isPlate("粤AI234K")); System.err.println(PalteUtil.isPlate("鄂CD3098"));*/ - - + + System.err.println("done!!!"); } - - + + /** * 根据正则表达式判断字符串是否是车牌 * @param str @@ -70,8 +70,8 @@ public class PalteUtil { } return bl; } - - + + /** * 输入车牌切图集合,判断是否包含车牌 * @param inMat @@ -79,39 +79,39 @@ 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) { + public static void hasPlate(Vector inMat, Vector dst, String modelPath, + Boolean debug, String tempPath) { loadSvmModel(modelPath); - inMat.stream().forEach(src -> { + int i = 0; + for (Mat src : inMat) { if(src.rows() == DEFAULT_HEIGHT && src.cols() == DEFAULT_WIDTH) { - Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY); - Imgproc.Canny(src, src, 130, 250); - Mat samples = src.reshape(1, 1); - samples.convertTo(samples, CvType.CV_32F); + Mat samples = SVMTrain.getFeature(src); float flag = svm.predict(samples); if (flag == 0) { - // System.out.println("目标符合"); + System.err.println("目标符合"); dst.add(src); + Imgcodecs.imwrite(tempPath + "199_plate_reco_" + i + ".png", src); + i++; } else { - // System.out.println("目标不符合"); + System.out.println("目标不符合"); } } - }); - + } return; } - + /** * 判断切图车牌颜色 * @param inMat * @return */ public static PlateColor getPlateColor(Mat inMat) { - - + + return PlateColor.UNKNOWN; } - + }