diff --git a/README.md b/README.md index 25656f9e..c0dcd575 100644 --- a/README.md +++ b/README.md @@ -111,5 +111,5 @@ debug_char_auxRoi: - 入门级教程项目,本人目前也正在学习图片识别相关技术;大牛请绕路 - 当前项目仅实现了黄牌、蓝牌车牌识别操作,接下来会继续优化代码架构,并且加上绿牌识别、车牌识别训练等操作 - 后续会逐步加入人脸识别等功能 - +- **车牌图片来源于网络,仅用于交流学习,不得用于商业用途;如有侵权,请联系本人删除** diff --git a/res/image/test_image/result_0.png b/res/image/test_image/result_0.png new file mode 100644 index 00000000..28ee2be2 Binary files /dev/null and b/res/image/test_image/result_0.png differ diff --git a/src/main/java/com/yuxue/easypr/core/CharsRecognise.java b/src/main/java/com/yuxue/easypr/core/CharsRecognise.java index 83e0bd72..d8d0a8aa 100644 --- a/src/main/java/com/yuxue/easypr/core/CharsRecognise.java +++ b/src/main/java/com/yuxue/easypr/core/CharsRecognise.java @@ -29,14 +29,14 @@ public class CharsRecognise { * @param plate: the input plate * @return the result of plate recognition */ - public String charsRecognise(final Mat plate) { + public String charsRecognise(final Mat plate, String tempPath) { // 车牌字符方块集合 Vector matVec = new Vector(); // 车牌识别结果 String plateIdentify = ""; - int result = charsSegment.charsSegment(plate, matVec); + int result = charsSegment.charsSegment(plate, matVec, tempPath); if (0 == result) { for (int j = 0; j < matVec.size(); j++) { Mat charMat = matVec.get(j); diff --git a/src/main/java/com/yuxue/easypr/core/CharsSegment.java b/src/main/java/com/yuxue/easypr/core/CharsSegment.java index f4368bbb..71bf2e41 100644 --- a/src/main/java/com/yuxue/easypr/core/CharsSegment.java +++ b/src/main/java/com/yuxue/easypr/core/CharsSegment.java @@ -19,14 +19,15 @@ import static org.bytedeco.javacpp.opencv_imgproc.warpAffine; import java.util.Vector; +import org.bytedeco.javacpp.opencv_core; import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.MatVector; import org.bytedeco.javacpp.opencv_core.Rect; import org.bytedeco.javacpp.opencv_core.Scalar; import org.bytedeco.javacpp.opencv_core.Size; -import org.bytedeco.javacpp.opencv_core; import org.bytedeco.javacpp.opencv_imgcodecs; +import com.yuxue.enumtype.PlateColor; import com.yuxue.util.Convert; /** @@ -56,7 +57,7 @@ public class CharsSegment { private float bluePercent = DEFAULT_BLUEPERCEMT; private float whitePercent = DEFAULT_WHITEPERCEMT; - private boolean isDebug = false; + private boolean isDebug = true; /** @@ -69,7 +70,7 @@ public class CharsSegment { *
  • -3: null; * */ - public int charsSegment(final Mat input, Vector resultVec) { + public int charsSegment(final Mat input, Vector resultVec, String tempPath) { if (input.data().isNull()) { return -3; } @@ -83,8 +84,9 @@ public class CharsSegment { int w = input.cols(); int h = input.rows(); Mat tmpMat = new Mat(input, new Rect((int) (w * 0.1), (int) (h * 0.1), (int) (w * 0.8), (int) (h * 0.8))); - - switch (getPlateType(tmpMat, true)) { + + PlateColor color= getPlateType(tmpMat, true); + switch (color) { case BLUE: threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; @@ -92,22 +94,25 @@ public class CharsSegment { case YELLOW: threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; + + case GREEN: + threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); + break; default: return -3; } if (this.isDebug) { - opencv_imgcodecs.imwrite("tmp/debug_char_threshold.jpg", img_threshold); + opencv_imgcodecs.imwrite(tempPath + "debug_char_threshold.jpg", img_threshold); } // 去除车牌上方的柳钉以及下方的横线等干扰 //会导致虚拟机崩溃 // clearLiuDing(img_threshold); - - if (this.isDebug) { - String str = "tmp/debug_char_clearLiuDing.jpg"; + /*if (this.isDebug) { + String str = tempPath + "debug_char_clearLiuDing.jpg"; opencv_imgcodecs.imwrite(str, img_threshold); - } + }*/ // 找轮廓 Mat img_contours = new Mat(); @@ -126,10 +131,14 @@ public class CharsSegment { Vector vecRect = new Vector(); for (int i = 0; i < contours.size(); ++i) { Rect mr = boundingRect(contours.get(i)); - if (verifySizes(new Mat(img_threshold, mr))) + /*Mat temp = new Mat(img_threshold, mr); + String str = tempPath + "temp_"+i+".jpg"; + opencv_imgcodecs.imwrite(str, temp);*/ + + if (verifySizes(new Mat(img_threshold, mr))) { vecRect.add(mr); + } } - if (vecRect.size() == 0) { return -3; } @@ -139,19 +148,20 @@ public class CharsSegment { SortRect(vecRect, sortedRect); // 获得指示城市的特定Rect,如苏A的"A" - int specIndex = GetSpecificRect(sortedRect); + int specIndex = GetSpecificRect(sortedRect, color); + System.err.println(specIndex); if (this.isDebug) { if (specIndex < sortedRect.size()) { Mat specMat = new Mat(img_threshold, sortedRect.get(specIndex)); - String str = "tmp/debug_specMat.jpg"; + String str = tempPath + "debug_specMat.jpg"; opencv_imgcodecs.imwrite(str, specMat); } } // 根据特定Rect向左反推出中文字符 // 这样做的主要原因是根据findContours方法很难捕捉到中文字符的准确Rect,因此仅能 - // 退过特定算法来指定 + // 通过特定算法来指定 Rect chineseRect = new Rect(); if (specIndex < sortedRect.size()) { chineseRect = GetChineseRect(sortedRect.get(specIndex)); @@ -161,7 +171,7 @@ public class CharsSegment { if (this.isDebug) { Mat chineseMat = new Mat(img_threshold, chineseRect); - String str = "tmp/debug_chineseMat.jpg"; + String str = tempPath + "debug_chineseMat.jpg"; opencv_imgcodecs.imwrite(str, chineseMat); } @@ -170,7 +180,7 @@ public class CharsSegment { // 其余的Rect只按照顺序去6个,车牌只可能是7个字符!这样可以避免阴影导致的“1”字符 Vector newSortedRect = new Vector(); newSortedRect.add(chineseRect); - RebuildRect(sortedRect, newSortedRect, specIndex); + RebuildRect(sortedRect, newSortedRect, specIndex, color); if (newSortedRect.size() == 0) { return -3; @@ -182,7 +192,7 @@ public class CharsSegment { auxRoi = preprocessChar(auxRoi); if (this.isDebug) { - String str = "tmp/debug_char_auxRoi_" + Integer.valueOf(i).toString() + ".jpg"; + String str = tempPath + "debug_char_auxRoi_" + Integer.valueOf(i).toString() + ".jpg"; opencv_imgcodecs.imwrite(str, auxRoi); } resultVec.add(auxRoi); @@ -191,12 +201,11 @@ public class CharsSegment { } /** - * 字符尺寸验证 - * + * 字符尺寸验证;去掉尺寸不符合的图块 * @param r * @return */ - private Boolean verifySizes(Mat r) { + public static Boolean verifySizes(Mat r) { float aspect = 45.0f / 90.0f; float charAspect = (float) r.cols() / (float) r.rows(); float error = 0.7f; @@ -212,8 +221,7 @@ public class CharsSegment { // % of pixel in area float percPixels = area / bbArea; - return percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && r.rows() >= minHeight - && r.rows() < maxHeight; + return percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && r.rows() >= minHeight && r.rows() < maxHeight; } /** @@ -290,11 +298,10 @@ public class CharsSegment { /** * 找出指示城市的字符的Rect,例如苏A7003X,就是A的位置 - * * @param vecRect * @return */ - private int GetSpecificRect(final Vector vecRect) { + private int GetSpecificRect(final Vector vecRect, PlateColor color) { Vector xpositions = new Vector(); int maxHeight = 0; int maxWidth = 0; @@ -314,10 +321,17 @@ public class CharsSegment { Rect mr = vecRect.get(i); int midx = mr.x() + mr.width() / 2; - // 如果一个字符有一定的大小,并且在整个车牌的1/7到2/7之间,则是我们要找的特殊车牌 - if ((mr.width() > maxWidth * 0.8 || mr.height() > maxHeight * 0.8) - && (midx < this.theMatWidth * 2 / 7 && midx > this.theMatWidth / 7)) { - specIndex = i; + if(PlateColor.GREEN.equals(color)) { + if ((mr.width() > maxWidth * 0.8 || mr.height() > maxHeight * 0.8) + && (midx < this.theMatWidth * 2 / 8 && midx > this.theMatWidth / 8)) { + specIndex = i; + } + } else { + // 如果一个字符有一定的大小,并且在整个车牌的1/7到2/7之间,则是我们要找的特殊车牌 + if ((mr.width() > maxWidth * 0.8 || mr.height() > maxHeight * 0.8) + && (midx < this.theMatWidth * 2 / 7 && midx > this.theMatWidth / 7)) { + specIndex = i; + } } } @@ -336,9 +350,12 @@ public class CharsSegment { * @param specIndex * @return */ - private int RebuildRect(final Vector vecRect, Vector outRect, int specIndex) { + private int RebuildRect(final Vector vecRect, Vector outRect, int specIndex, PlateColor color) { // 最大只能有7个Rect,减去中文的就只有6个Rect int count = 6; + if(PlateColor.GREEN.equals(color)) { + count = 7; // 绿牌要多一个 + } for (int i = 0; i < vecRect.size(); i++) { // 将特殊字符左边的Rect去掉,这个可能会去掉中文Rect,不过没关系,我们后面会重建。 if (i < specIndex) @@ -359,7 +376,7 @@ public class CharsSegment { * @param out * @return */ - private void SortRect(final Vector vecRect, Vector out) { + public static void SortRect(final Vector vecRect, Vector out) { Vector orderIndex = new Vector(); Vector xpositions = new Vector(); for (int i = 0; i < vecRect.size(); ++i) { @@ -432,6 +449,4 @@ public class CharsSegment { } - - } diff --git a/src/main/java/com/yuxue/easypr/core/CoreFunc.java b/src/main/java/com/yuxue/easypr/core/CoreFunc.java index 2902a346..a6cd2f5b 100644 --- a/src/main/java/com/yuxue/easypr/core/CoreFunc.java +++ b/src/main/java/com/yuxue/easypr/core/CoreFunc.java @@ -59,8 +59,8 @@ public class CoreFunc { final int max_yellow = 40; // green的H范围 - final int min_green = 38; - final int max_green = 75; + final int min_green = 8; + final int max_green = 150; // 转到HSV空间进行处理,颜色搜索主要使用的是H分量进行蓝色与黄色的匹配工作 Mat src_hsv = new Mat(); diff --git a/src/main/java/com/yuxue/service/impl/PlateServiceImpl.java b/src/main/java/com/yuxue/service/impl/PlateServiceImpl.java index 3afb57f8..3d79bddc 100644 --- a/src/main/java/com/yuxue/service/impl/PlateServiceImpl.java +++ b/src/main/java/com/yuxue/service/impl/PlateServiceImpl.java @@ -51,12 +51,11 @@ public class PlateServiceImpl implements PlateService { debugMap.put("debug_result", 6); // 原图处理结果 debugMap.put("debug_crop", 7); // 切图 debugMap.put("debug_resize", 8); // 切图resize - /*debugMap.put("debug_char_threshold", 9); // - debugMap.put("debug_char_clearLiuDing", 10); // 去除柳钉 + debugMap.put("debug_char_threshold", 9); // + // debugMap.put("debug_char_clearLiuDing", 10); // 去除柳钉 debugMap.put("debug_specMat", 11); // debugMap.put("debug_chineseMat", 12); // debugMap.put("debug_char_auxRoi", 13); // - */ } @@ -83,7 +82,7 @@ public class PlateServiceImpl implements PlateService { if(FileUtil.checkFile(f)) { e = new PlateFileEntity(); e.setFileName(f.getName()); - e.setFilePath(f.getAbsolutePath()); + e.setFilePath(f.getAbsolutePath().replaceAll("\\\\", "/")); e.setFileType(f.getName().substring(f.getName().lastIndexOf(".") + 1)); plateFileMapper.insertSelective(e); } @@ -201,10 +200,11 @@ public class PlateServiceImpl implements PlateService { if (0 == plateDetect.plateDetect(src, matVector)) { // 定位及判断,获取到车牌图块Mat CharsRecognise cr = new CharsRecognise(); + cr.setCRDebug(true); for (int i = 0; i < matVector.size(); ++i) { // 遍历车牌图块Mat,进行识别 Mat img = matVector.get(i); - String palte = cr.charsRecognise(img); // 字符识别 + String palte = cr.charsRecognise(img, tempPath); // 字符识别 PlateColor color = CoreFunc.getPlateType(img, true); String fileName = "result_" + i + ".png"; diff --git a/src/main/resources/static/js/model/plate/plateContentMod.js b/src/main/resources/static/js/model/plate/plateContentMod.js index 02e43626..952f3fb3 100644 --- a/src/main/resources/static/js/model/plate/plateContentMod.js +++ b/src/main/resources/static/js/model/plate/plateContentMod.js @@ -11,14 +11,14 @@ define(['api', 'utils'], function(api, utils){ bindBtnEvent(); // 监听按钮事件 - $('body').keydown(function (e) { + /*$('body').keydown(function (e) { if (event.keyCode==116){ //回车键 //F5按键 e.preventDefault(); setTimeout(function () { $("#refreshPlate").trigger('click'); }, 200); } - }); + });*/ } diff --git a/src/test/java/com/yuxue/test/EasyPrTest.java b/src/test/java/com/yuxue/test/EasyPrTest.java index 3b38d795..a9f0123b 100644 --- a/src/test/java/com/yuxue/test/EasyPrTest.java +++ b/src/test/java/com/yuxue/test/EasyPrTest.java @@ -1,14 +1,22 @@ package com.yuxue.test; +import static org.bytedeco.javacpp.opencv_imgproc.CV_CHAIN_APPROX_NONE; +import static org.bytedeco.javacpp.opencv_imgproc.CV_RETR_EXTERNAL; +import static org.bytedeco.javacpp.opencv_imgproc.boundingRect; +import static org.bytedeco.javacpp.opencv_imgproc.findContours; + import java.io.File; import java.util.Vector; import org.bytedeco.javacpp.opencv_core.Mat; +import org.bytedeco.javacpp.opencv_core.MatVector; +import org.bytedeco.javacpp.opencv_core.Rect; import org.bytedeco.javacpp.opencv_imgcodecs; import org.junit.Test; import com.yuxue.easypr.core.CharsIdentify; import com.yuxue.easypr.core.CharsRecognise; +import com.yuxue.easypr.core.CharsSegment; import com.yuxue.easypr.core.CoreFunc; import com.yuxue.easypr.core.PlateDetect; import com.yuxue.easypr.core.PlateLocate; @@ -49,7 +57,7 @@ public class EasyPrTest { for (int i = 0; i < matVector.size(); ++i) { // 遍历检测返回的Mat集合,进行识别 Mat img = matVector.get(i); - String palte = cr.charsRecognise(img); // 字符识别 + String palte = cr.charsRecognise(img, "tem/"); // 字符识别 PlateColor color = CoreFunc.getPlateType(img, true); System.err.println("识别到的车牌: " + palte + "_" + color); @@ -86,7 +94,7 @@ public class EasyPrTest { for (int i = 0; i < matVector.size(); ++i) { Mat img = matVector.get(i); // 弹窗显示 - //opencv_imgcodecs.showImage("Plate Detected", img); + //opencv_highgui.imshow("Plate Detected", img); String str = "d:/test/" + i + ".png"; opencv_imgcodecs.imwrite(str, img); @@ -134,7 +142,7 @@ public class EasyPrTest { Mat src = opencv_imgcodecs.imread(imgPath); CharsRecognise cr = new CharsRecognise(); cr.setCRDebug(true); - String result = cr.charsRecognise(src); + String result = cr.charsRecognise(src, "tem/"); System.out.println("Chars Recognised: " + result); } @@ -175,6 +183,58 @@ public class EasyPrTest { System.out.println(result); } + @Test + public void testGreenPlate() { + /*String imgPath = "res/image/test_image/result_0.png"; + Mat src = opencv_imgcodecs.imread(imgPath);*/ + + // 获取绿牌的H值范围 + /*MatVector hsvSplit = new MatVector(); + split(src_hsv, hsvSplit); + equalizeHist(hsvSplit.get(2), hsvSplit.get(2)); + merge(hsvSplit, src_hsv); + + int channels = src_hsv.channels(); + int nRows = src_hsv.rows(); + // 图像数据列需要考虑通道数的影响; + int nCols = src_hsv.cols() * channels; + + // 连续存储的数据,按一行处理 + if (src_hsv.isContinuous()) { + nCols *= nRows; + nRows = 1; + } + Map map = Maps.newHashMap(); + for (int i = 0; i < nRows; ++i) { + BytePointer p = src_hsv.ptr(i); + for (int j = 0; j < nCols; j += 3) { + int H = p.get(j) & 0xFF; + int S = p.get(j + 1) & 0xFF; + int V = p.get(j + 2) & 0xFF; + + if(map.containsKey(H)) { + int count = map.get(H); + map.put(H, count+1); + } else { + map.put(H, 1); + } + + } + } + map.entrySet().forEach(n->{ + System.err.println(n.getKey() + "\t" + n.getValue()); + });*/ + + // 判断绿色车牌 + /*Mat src_hsv = new Mat(); + cvtColor(src, src_hsv, CV_BGR2HSV); + src_hsv = CoreFunc.colorMatch(src, PlateColor.GREEN, true); + System.err.println(CoreFunc.plateColorJudge(src, PlateColor.GREEN, true)); + String str = "d:/PlateDetect/src_hsv.png"; + opencv_imgcodecs.imwrite(str, src_hsv);*/ + + } +