├── README.md └── src └── org └── easypr ├── Main.java ├── cheshi2.java ├── cheshi3.java ├── core ├── CharsIdentify.java ├── CharsRecognise.java ├── CharsSegment.java ├── CoreFunc.java ├── Features.java ├── PlateDetect.java ├── PlateJudge.java ├── PlateLocate.java ├── PlateRecognize.java └── SVMCallback.java ├── test ├── EasyPrTest.java └── Test.java ├── train ├── ANNTrain.java └── SVMTrain.java └── util ├── Convert.java └── Util.java /README.md: -------------------------------------------------------------------------------- 1 | # Easypr_JavaFrame 2 | by wyq.
增加视频捕捉的车牌识别,基于ezpr的完善。通过捕捉摄像头的帧画面,解决数据转化的难点,进而由Easypr开源库进行一系列识别处理。具体的运行效率可进一步自行优化。 3 | 4 | 5 |
6 | 7 | 8 | 9 | 在一下环境测试完成:(友情提示:只能在该版本的包下正常运行,因为ezpr就是基于此基础开发的,不同版本存在兼容问题,没有后续更新) 10 | javacv-0.1.0 11 | javacpp-0.1.0 12 | opencv-2.4.13 13 | 14 | 由于对应的依赖包很难找,我当初也花了很大功夫,尝试了很多版本 15 | 在此附上依赖包百度云地址,有需要的可以下载。 16 | 同时,此依赖包支持原easypr。 17 | 链接:http://pan.baidu.com/s/1dEJmExb 密码:6bzs 18 | -------------------------------------------------------------------------------- /src/org/easypr/Main.java: -------------------------------------------------------------------------------- 1 | package org.easypr; 2 | 3 | 4 | import java.io.*; 5 | import java.util.Vector; 6 | 7 | import javax.swing.JFrame; 8 | 9 | import static org.bytedeco.javacpp.opencv_core.*; 10 | 11 | import org.bytedeco.javacpp.opencv_core.Mat; 12 | import org.bytedeco.javacv.CanvasFrame; 13 | //import org.bytedeco.javacv.OpenCVFrameConverter; 14 | import org.bytedeco.javacv.OpenCVFrameGrabber; 15 | import org.easypr.core.CharsRecognise; 16 | import org.easypr.core.PlateDetect; 17 | import org.easypr.core.PlateLocate; 18 | import org.easypr.test.*; 19 | import org.easypr.util.Convert; 20 | 21 | import static org.bytedeco.javacpp.opencv_highgui.*; 22 | public class Main { 23 | 24 | 25 | // import static org.bytedeco.javacpp.opencv_highgui.imread; 26 | 27 | // import java.util.Vector; 28 | 29 | //import javax.swing.JFrame; 30 | 31 | //import org.bytedeco.javacv.OpenCVFrameGrabber; 32 | //import org.bytedeco.javacpp.opencv_core.Mat; 33 | //import org.bytedeco.javacv.CanvasFrame; 34 | //import org.bytedeco.javacv.Frame; 35 | //import org.bytedeco.javacv.OpenCVFrameConverter; 36 | //import org.bytedeco.javacv.FrameGrabber.Exception; 37 | //import org.easypr.core.CharsRecognise; 38 | //import org.easypr.core.PlateDetect; 39 | 40 | 41 | /** 42 | * 调用本地摄像头窗口视频 43 | * @author wyq 44 | * @version 2016年6月13日 45 | * @see javavcCameraTest `` 46 | */ 47 | 48 | 49 | public static void main(String[] args) throws Exception, InterruptedException 50 | { 51 | OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0); 52 | grabber.start(); //开始获取摄像头数据 53 | CanvasFrame canvas = new CanvasFrame("摄像头");//新建一个窗口 54 | canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 55 | canvas.setAlwaysOnTop(true); 56 | 57 | CanvasFrame lunkuo = new CanvasFrame("轮廓");//新建一个窗口 58 | lunkuo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 59 | lunkuo.setAlwaysOnTop(true); 60 | 61 | CanvasFrame chepai = new CanvasFrame("车牌");//新建一个窗口 62 | chepai.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 63 | chepai.setAlwaysOnTop(true); 64 | 65 | IplImage grabbedImage = grabber.grab(); 66 | Mat matImage = new Mat(grabbedImage); 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | while(true) 75 | { canvas.showImage(grabber.grab()); 76 | if(!canvas.isDisplayable()) 77 | {//窗口是否关闭 78 | grabber.stop();//停止抓取 79 | System.exit(2);//退出 80 | } 81 | PlateDetect plateDetect = new PlateDetect(); 82 | plateDetect.setPDLifemode(true); 83 | Vector matVector = new Vector(); 84 | Vector SVector = new Vector(); 85 | if (0 == plateDetect.plateDetect(matImage, matVector)) { 86 | lunkuo.showImage(new Mat(imread("tmp/debug_result.jpg"))); 87 | 88 | CharsRecognise cr = new CharsRecognise(); 89 | 90 | 91 | for (int i = 0; i < matVector.size(); ++i) { 92 | 93 | chepai.showImage(matVector.get(i)); 94 | 95 | String result = cr.charsRecognise(matVector.get(i)); 96 | String plateType = cr.getPlateType(matVector.get(i)); 97 | System.out.println("车牌识别结果: " +plateType+"--"+result); 98 | 99 | } 100 | } 101 | 102 | 103 | canvas.showImage(grabber.grab());//获取摄像头图像并放到窗口上显示, 这里的Frame frame=grabber.grab(); frame是一帧视频图像 104 | 105 | 106 | Thread.sleep(30);//50毫秒刷新一次图像 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/org/easypr/cheshi2.java: -------------------------------------------------------------------------------- 1 | package org.easypr; 2 | 3 | import static org.bytedeco.javacpp.opencv_highgui.imread; 4 | 5 | import java.util.Vector; 6 | 7 | import javax.swing.JFrame; 8 | 9 | import org.easypr.test.*; 10 | import org.bytedeco.javacpp.opencv_core.IplImage; 11 | import org.bytedeco.javacpp.opencv_core.Mat; 12 | import org.bytedeco.javacv.CanvasFrame; 13 | import org.bytedeco.javacv.OpenCVFrameGrabber; 14 | import org.easypr.core.CharsIdentify; 15 | import org.easypr.core.CharsRecognise; 16 | import org.easypr.core.PlateDetect; 17 | import org.easypr.core.PlateRecognize; 18 | public class cheshi2 { 19 | public static void main(String[] args) throws Exception, InterruptedException 20 | { 21 | String imgPath = "tmp/debug_char_auxRoi_5.jpg"; 22 | 23 | Mat src = imread(imgPath); 24 | CharsIdentify charsIdentify = new CharsIdentify(); 25 | String result = charsIdentify.charsIdentify(src, false, true); 26 | System.out.println(result); 27 | }} 28 | -------------------------------------------------------------------------------- /src/org/easypr/cheshi3.java: -------------------------------------------------------------------------------- 1 | package org.easypr; 2 | 3 | import static org.bytedeco.javacpp.opencv_highgui.imread; 4 | 5 | import org.bytedeco.javacpp.opencv_core.Mat; 6 | import org.easypr.core.CharsRecognise; 7 | import org.easypr.train.ANNTrain; 8 | import org.easypr.train.SVMTrain; 9 | public class cheshi3 { 10 | public static void main(String[] args) throws Exception, InterruptedException 11 | { 12 | // ANNTrain a = new ANNTrain(); 13 | //a.annMain(); 14 | SVMTrain test2 = new SVMTrain(); 15 | test2.svmTrain(false,false); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/org/easypr/core/CharsIdentify.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.CV_32FC1; 4 | import static org.easypr.core.CoreFunc.features; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.bytedeco.javacpp.opencv_core.Mat; 10 | import org.bytedeco.javacpp.opencv_ml.CvANN_MLP; 11 | import org.easypr.util.Convert; 12 | 13 | /** 14 | * @author Created by fanwenjie 15 | * @author lin.yao 16 | * 17 | */ 18 | public class CharsIdentify { 19 | 20 | public CharsIdentify() { 21 | loadModel(); 22 | 23 | if (this.map.isEmpty()) { 24 | map.put("zh_cuan", "川"); 25 | map.put("zh_e", "鄂"); 26 | map.put("zh_gan", "赣"); 27 | map.put("zh_gan1", "甘"); 28 | map.put("zh_gui", "贵"); 29 | map.put("zh_gui1", "桂"); 30 | map.put("zh_hei", "黑"); 31 | map.put("zh_hu", "沪"); 32 | map.put("zh_ji", "冀"); 33 | map.put("zh_jin", "津"); 34 | map.put("zh_jing", "京"); 35 | map.put("zh_jl", "吉"); 36 | map.put("zh_liao", "辽"); 37 | map.put("zh_lu", "鲁"); 38 | map.put("zh_meng", "蒙"); 39 | map.put("zh_min", "闽"); 40 | map.put("zh_ning", "宁"); 41 | map.put("zh_qing", "青"); 42 | map.put("zh_qiong", "琼"); 43 | map.put("zh_shan", "陕"); 44 | map.put("zh_su", "苏"); 45 | map.put("zh_sx", "晋"); 46 | map.put("zh_wan", "皖"); 47 | map.put("zh_xiang", "湘"); 48 | map.put("zh_xin", "新"); 49 | map.put("zh_yu", "豫"); 50 | map.put("zh_yu1", "渝"); 51 | map.put("zh_yue", "粤"); 52 | map.put("zh_yun", "云"); 53 | map.put("zh_zang", "藏"); 54 | map.put("zh_zhe", "浙"); 55 | } 56 | } 57 | 58 | /** 59 | * @param input 60 | * @param isChinese 61 | * @return 62 | */ 63 | public String charsIdentify(final Mat input, final Boolean isChinese, final Boolean isSpeci) { 64 | String result = ""; 65 | 66 | Mat f = features(input, this.predictSize); 67 | 68 | int index = classify(f, isChinese, isSpeci); 69 | 70 | if (!isChinese) { 71 | result = String.valueOf(strCharacters[index]); 72 | } else { 73 | String s = strChinese[index - numCharacter]; 74 | result = map.get(s); 75 | } 76 | return result; 77 | } 78 | 79 | private int classify(final Mat f, final Boolean isChinses, final Boolean isSpeci) { 80 | int result = -1; 81 | Mat output = new Mat(1, numAll, CV_32FC1); 82 | 83 | ann.predict(f, output); 84 | 85 | int ann_min = (!isChinses) ? ((isSpeci) ? 10 : 0) : numCharacter; 86 | int ann_max = (!isChinses) ? numCharacter : numAll; 87 | 88 | float maxVal = -2; 89 | 90 | for (int j = ann_min; j < ann_max; j++) { 91 | float val = Convert.toFloat(output.ptr(0, j)); 92 | 93 | if (val > maxVal) { 94 | maxVal = val; 95 | result = j; 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | 102 | private void loadModel() { 103 | loadModel(this.path); 104 | } 105 | 106 | public void loadModel(String s) { 107 | this.ann.clear(); 108 | this.ann.load(s, "ann"); 109 | } 110 | 111 | static boolean hasPrint = false; 112 | 113 | public final void setModelPath(String path) { 114 | this.path = path; 115 | } 116 | 117 | public final String getModelPath() { 118 | return this.path; 119 | } 120 | 121 | private CvANN_MLP ann = new CvANN_MLP(); 122 | 123 | private String path = "res/model/ann.xml"; 124 | 125 | private int predictSize = 10; 126 | 127 | private Map map = new HashMap(); 128 | 129 | private final char strCharacters[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 130 | 'F', 'G', 'H', /* 没有I */'J', 'K', 'L', 'M', 'N', /* 没有O */'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 131 | 'Z' }; 132 | private final static int numCharacter = 34; // 没有I和0,10个数字与24个英文字符之和 133 | 134 | private final String strChinese[] = { "zh_cuan" /* 川 */, "zh_e" /* 鄂 */, "zh_gan" /* 赣 */, "zh_gan1"/* 甘 */, 135 | "zh_gui"/* 贵 */, "zh_gui1"/* 桂 */, "zh_hei" /* 黑 */, "zh_hu" /* 沪 */, "zh_ji" /* 冀 */, "zh_jin" /* 津 */, 136 | "zh_jing" /* 京 */, "zh_jl" /* 吉 */, "zh_liao" /* 辽 */, "zh_lu" /* 鲁 */, "zh_meng" /* 蒙 */, 137 | "zh_min" /* 闽 */, "zh_ning" /* 宁 */, "zh_qing" /* 青 */, "zh_qiong" /* 琼 */, "zh_shan" /* 陕 */, 138 | "zh_su" /* 苏 */, "zh_sx" /* 晋 */, "zh_wan" /* 皖 */, "zh_xiang" /* 湘 */, "zh_xin" /* 新 */, "zh_yu" /* 豫 */, 139 | "zh_yu1" /* 渝 */, "zh_yue" /* 粤 */, "zh_yun" /* 云 */, "zh_zang" /* 藏 */, "zh_zhe" /* 浙 */}; 140 | @SuppressWarnings("unused") 141 | private final static int numChinese = 31; 142 | 143 | private final static int numAll = 65; /* 34+31=65 */ 144 | } 145 | -------------------------------------------------------------------------------- /src/org/easypr/core/CharsRecognise.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import java.util.Vector; 4 | 5 | import org.bytedeco.javacpp.opencv_core.Mat; 6 | import org.easypr.core.CoreFunc.Color; 7 | 8 | /** 9 | * @author lin.yao 10 | * 11 | */ 12 | public class CharsRecognise { 13 | 14 | public void loadANN(final String s) { 15 | charsIdentify.loadModel(s); 16 | } 17 | 18 | /** 19 | * Chars segment and identify 字符分割与识别 20 | * 21 | * @param plate 22 | * the input plate 23 | * @return the result of plate recognition 24 | */ 25 | public String charsRecognise(final Mat plate) { 26 | 27 | // the set of plate character after segment 车牌字符方块集合 28 | Vector matVec = new Vector(); 29 | // the result of plate recognition 30 | String plateIdentify = ""; 31 | 32 | int result = charsSegment.charsSegment(plate, matVec); 33 | if (0 == result) { 34 | for (int j = 0; j < matVec.size(); j++) { 35 | Mat charMat = matVec.get(j); 36 | // the first is Chinese char as default 默认首个字符块是中文字符 37 | String charcater = charsIdentify.charsIdentify(charMat, (0 == j), (1 == j)); 38 | plateIdentify = plateIdentify + charcater; 39 | } 40 | 41 | } 42 | return plateIdentify; 43 | } 44 | 45 | /** 46 | * 是否开启调试模式 47 | * 48 | * @param isDebug 49 | */ 50 | public void setCRDebug(final boolean isDebug) { 51 | charsSegment.setDebug(isDebug); 52 | } 53 | 54 | /** 55 | * 获取调试模式状态 56 | * 57 | * @return 58 | */ 59 | public boolean getCRDebug() { 60 | return charsSegment.getDebug(); 61 | } 62 | 63 | /** 64 | * 获得车牌颜色 65 | * 66 | * @param input 67 | * @return 68 | */ 69 | public final String getPlateType(final Mat input) { 70 | String color = "未知"; 71 | Color result = CoreFunc.getPlateType(input, true); 72 | if (Color.BLUE == result) 73 | color = "蓝牌"; 74 | if (Color.YELLOW == result) 75 | color = "黄牌"; 76 | return color; 77 | } 78 | 79 | /** 80 | * 设置柳丁大小变量 81 | * 82 | * @param param 83 | */ 84 | public void setLiuDingSize(final int param) { 85 | charsSegment.setLiuDingSize(param); 86 | } 87 | 88 | /** 89 | * 设置颜色阈值 90 | * 91 | * @param param 92 | */ 93 | public void setColorThreshold(final int param) { 94 | charsSegment.setColorThreshold(param); 95 | } 96 | 97 | /** 98 | * 设置蓝色百分比 99 | * 100 | * @param param 101 | */ 102 | public void setBluePercent(final float param) { 103 | charsSegment.setBluePercent(param); 104 | } 105 | 106 | /** 107 | * 得到蓝色百分比 108 | * 109 | * @param param 110 | */ 111 | public final float getBluePercent() { 112 | return charsSegment.getBluePercent(); 113 | } 114 | 115 | /** 116 | * 设置白色百分比 117 | * 118 | * @param param 119 | */ 120 | public void setWhitePercent(final float param) { 121 | charsSegment.setWhitePercent(param); 122 | } 123 | 124 | /** 125 | * 得到白色百分比 126 | * 127 | * @param param 128 | */ 129 | public final float getWhitePercent() { 130 | return charsSegment.getWhitePercent(); 131 | } 132 | 133 | private CharsSegment charsSegment = new CharsSegment(); 134 | 135 | private CharsIdentify charsIdentify = new CharsIdentify(); 136 | } 137 | -------------------------------------------------------------------------------- /src/org/easypr/core/CharsSegment.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.CV_32F; 4 | import static org.bytedeco.javacpp.opencv_core.countNonZero; 5 | import static org.bytedeco.javacpp.opencv_highgui.imwrite; 6 | import static org.bytedeco.javacpp.opencv_imgproc.BORDER_CONSTANT; 7 | import static org.bytedeco.javacpp.opencv_imgproc.CV_CHAIN_APPROX_NONE; 8 | import static org.bytedeco.javacpp.opencv_imgproc.CV_RETR_EXTERNAL; 9 | import static org.bytedeco.javacpp.opencv_imgproc.CV_RGB2GRAY; 10 | import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_BINARY; 11 | import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_BINARY_INV; 12 | import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_OTSU; 13 | import static org.bytedeco.javacpp.opencv_imgproc.INTER_LINEAR; 14 | import static org.bytedeco.javacpp.opencv_imgproc.boundingRect; 15 | import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; 16 | import static org.bytedeco.javacpp.opencv_imgproc.findContours; 17 | import static org.bytedeco.javacpp.opencv_imgproc.resize; 18 | import static org.bytedeco.javacpp.opencv_imgproc.threshold; 19 | import static org.bytedeco.javacpp.opencv_imgproc.warpAffine; 20 | import static org.easypr.core.CoreFunc.getPlateType; 21 | 22 | import java.util.Vector; 23 | 24 | import org.bytedeco.javacpp.opencv_core.Mat; 25 | import org.bytedeco.javacpp.opencv_core.MatVector; 26 | import org.bytedeco.javacpp.opencv_core.Rect; 27 | import org.bytedeco.javacpp.opencv_core.Scalar; 28 | import org.bytedeco.javacpp.opencv_core.Size; 29 | import org.easypr.util.Convert; 30 | 31 | /** 32 | * @author lin.yao 33 | * 34 | */ 35 | public class CharsSegment { 36 | 37 | /** 38 | * 字符分割 39 | * 40 | * @param input 41 | * @param resultVec 42 | * @return
    43 | *
  • more than zero: the number of chars; 44 | *
  • -3: null; 45 | *
46 | */ 47 | public int charsSegment(final Mat input, Vector resultVec) { 48 | if (input.data().isNull()) 49 | return -3; 50 | 51 | // 判断车牌颜色以此确认threshold方法 52 | 53 | Mat img_threshold = new Mat(); 54 | 55 | Mat input_grey = new Mat(); 56 | cvtColor(input, input_grey, CV_RGB2GRAY); 57 | 58 | int w = input.cols(); 59 | int h = input.rows(); 60 | Mat tmpMat = new Mat(input, new Rect((int) (w * 0.1), (int) (h * 0.1), (int) (w * 0.8), (int) (h * 0.8))); 61 | 62 | switch (getPlateType(tmpMat, true)) { 63 | case BLUE: 64 | threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); 65 | break; 66 | 67 | case YELLOW: 68 | threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); 69 | break; 70 | 71 | default: 72 | return -3; 73 | } 74 | 75 | 76 | 77 | if (this.isDebug) { 78 | imwrite("tmp/debug_char_threshold.jpg", img_threshold); 79 | } 80 | 81 | // 去除车牌上方的柳钉以及下方的横线等干扰 82 | // clearLiuDing(img_threshold); 83 | 84 | // if (this.isDebug) { 85 | // String str = "tmp/debug_char_clearLiuDing.jpg"; 86 | // imwrite(str, img_threshold); 87 | // 88 | // } 89 | 90 | // 找轮廓 91 | Mat img_contours = new Mat(); 92 | img_threshold.copyTo(img_contours); 93 | 94 | MatVector contours = new MatVector(); 95 | 96 | findContours(img_contours, contours, // a vector of contours 97 | CV_RETR_EXTERNAL, // retrieve the external contours 98 | CV_CHAIN_APPROX_NONE); // all pixels of each contours 99 | 100 | // Start to iterate to each contour founded 101 | 102 | // Remove patch that are no inside limits of aspect ratio and area. 103 | // 将不符合特定尺寸的图块排除出去 104 | Vector vecRect = new Vector(); 105 | for (int i = 0; i < contours.size(); ++i) { 106 | Rect mr = boundingRect(contours.get(i)); 107 | if (verifySizes(new Mat(img_threshold, mr))) 108 | vecRect.add(mr); 109 | } 110 | 111 | if (vecRect.size() == 0) 112 | return -3; 113 | 114 | Vector sortedRect = new Vector(); 115 | // 对符合尺寸的图块按照从左到右进行排序 116 | SortRect(vecRect, sortedRect); 117 | 118 | // 获得指示城市的特定Rect,如苏A的"A" 119 | int specIndex = GetSpecificRect(sortedRect); 120 | 121 | if (this.isDebug) { 122 | if (specIndex < sortedRect.size()) { 123 | Mat specMat = new Mat(img_threshold, sortedRect.get(specIndex)); 124 | String str = "tmp/debug_specMat.jpg"; 125 | imwrite(str, specMat); 126 | } 127 | } 128 | 129 | // 根据特定Rect向左反推出中文字符 130 | // 这样做的主要原因是根据findContours方法很难捕捉到中文字符的准确Rect,因此仅能 131 | // 退过特定算法来指定 132 | Rect chineseRect = new Rect(); 133 | if (specIndex < sortedRect.size()) 134 | chineseRect = GetChineseRect(sortedRect.get(specIndex)); 135 | else 136 | return -3; 137 | 138 | if (this.isDebug) { 139 | Mat chineseMat = new Mat(img_threshold, chineseRect); 140 | String str = "tmp/debug_chineseMat.jpg"; 141 | imwrite(str, chineseMat); 142 | } 143 | 144 | // 新建一个全新的排序Rect 145 | // 将中文字符Rect第一个加进来,因为它肯定是最左边的 146 | // 其余的Rect只按照顺序去6个,车牌只可能是7个字符!这样可以避免阴影导致的“1”字符 147 | Vector newSortedRect = new Vector(); 148 | newSortedRect.add(chineseRect); 149 | RebuildRect(sortedRect, newSortedRect, specIndex); 150 | 151 | if (newSortedRect.size() == 0) 152 | return -3; 153 | 154 | for (int i = 0; i < newSortedRect.size(); i++) { 155 | Rect mr = newSortedRect.get(i); 156 | Mat auxRoi = new Mat(img_threshold, mr); 157 | 158 | auxRoi = preprocessChar(auxRoi); 159 | if (this.isDebug) { 160 | String str = "tmp/debug_char_auxRoi_" + Integer.valueOf(i).toString() + ".jpg"; 161 | imwrite(str, auxRoi); 162 | } 163 | resultVec.add(auxRoi); 164 | } 165 | return 0; 166 | } 167 | 168 | /** 169 | * 字符尺寸验证 170 | * 171 | * @param r 172 | * @return 173 | */ 174 | private Boolean verifySizes(Mat r) { 175 | float aspect = 45.0f / 90.0f; 176 | float charAspect = (float) r.cols() / (float) r.rows(); 177 | float error = 0.7f; 178 | float minHeight = 10f; 179 | float maxHeight = 35f; 180 | // We have a different aspect ratio for number 1, and it can be ~0.2 181 | float minAspect = 0.05f; 182 | float maxAspect = aspect + aspect * error; 183 | // area of pixels 184 | float area = countNonZero(r); 185 | // bb area 186 | float bbArea = r.cols() * r.rows(); 187 | // % of pixel in area 188 | float percPixels = area / bbArea; 189 | 190 | return percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && r.rows() >= minHeight 191 | && r.rows() < maxHeight; 192 | } 193 | 194 | /** 195 | * 字符预处理: 统一每个字符的大小 196 | * 197 | * @param in 198 | * @return 199 | */ 200 | private Mat preprocessChar(Mat in) { 201 | int h = in.rows(); 202 | int w = in.cols(); 203 | int charSize = CHAR_SIZE; 204 | Mat transformMat = Mat.eye(2, 3, CV_32F).asMat(); 205 | int m = Math.max(w, h); 206 | transformMat.ptr(0, 2).put(Convert.getBytes(((m - w) / 2f))); 207 | transformMat.ptr(1, 2).put(Convert.getBytes((m - h) / 2f)); 208 | 209 | Mat warpImage = new Mat(m, m, in.type()); 210 | warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, new Scalar(0)); 211 | 212 | Mat out = new Mat(); 213 | resize(warpImage, out, new Size(charSize, charSize)); 214 | 215 | return out; 216 | } 217 | 218 | /** 219 | * 去除车牌上方的钮钉 220 | *

221 | * 计算每行元素的阶跃数,如果小于X认为是柳丁,将此行全部填0(涂黑), X可根据实际调整 222 | * 223 | * @param img 224 | * @return 225 | */ 226 | private Mat clearLiuDing(Mat img) { 227 | final int x = this.liuDingSize; 228 | 229 | Mat jump = Mat.zeros(1, img.rows(), CV_32F).asMat(); 230 | for (int i = 0; i < img.rows(); i++) { 231 | int jumpCount = 0; 232 | for (int j = 0; j < img.cols() - 1; j++) { 233 | if (img.ptr(i, j).get() != img.ptr(i, j + 1).get()) 234 | jumpCount++; 235 | } 236 | jump.ptr(i).put(Convert.getBytes((float) jumpCount)); 237 | } 238 | for (int i = 0; i < img.rows(); i++) { 239 | if (Convert.toFloat(jump.ptr(i)) <= x) { 240 | for (int j = 0; j < img.cols(); j++) { 241 | img.ptr(i, j).put((byte) 0); 242 | } 243 | } 244 | } 245 | return img; 246 | } 247 | 248 | /** 249 | * 根据特殊车牌来构造猜测中文字符的位置和大小 250 | * 251 | * @param rectSpe 252 | * @return 253 | */ 254 | private Rect GetChineseRect(final Rect rectSpe) { 255 | int height = rectSpe.height(); 256 | float newwidth = rectSpe.width() * 1.15f; 257 | int x = rectSpe.x(); 258 | int y = rectSpe.y(); 259 | 260 | int newx = x - (int) (newwidth * 1.15); 261 | newx = Math.max(newx, 0); 262 | Rect a = new Rect(newx, y, (int) newwidth, height); 263 | return a; 264 | } 265 | 266 | /** 267 | * 找出指示城市的字符的Rect,例如苏A7003X,就是A的位置 268 | * 269 | * @param vecRect 270 | * @return 271 | */ 272 | private int GetSpecificRect(final Vector vecRect) { 273 | Vector xpositions = new Vector(); 274 | int maxHeight = 0; 275 | int maxWidth = 0; 276 | for (int i = 0; i < vecRect.size(); i++) { 277 | xpositions.add(vecRect.get(i).x()); 278 | 279 | if (vecRect.get(i).height() > maxHeight) { 280 | maxHeight = vecRect.get(i).height(); 281 | } 282 | if (vecRect.get(i).width() > maxWidth) { 283 | maxWidth = vecRect.get(i).width(); 284 | } 285 | } 286 | 287 | int specIndex = 0; 288 | for (int i = 0; i < vecRect.size(); i++) { 289 | Rect mr = vecRect.get(i); 290 | int midx = mr.x() + mr.width() / 2; 291 | 292 | // 如果一个字符有一定的大小,并且在整个车牌的1/7到2/7之间,则是我们要找的特殊车牌 293 | if ((mr.width() > maxWidth * 0.8 || mr.height() > maxHeight * 0.8) 294 | && (midx < this.theMatWidth * 2 / 7 && midx > this.theMatWidth / 7)) { 295 | specIndex = i; 296 | } 297 | } 298 | 299 | return specIndex; 300 | } 301 | 302 | /** 303 | * 这个函数做两个事情 304 | *

    305 | *
  • 把特殊字符Rect左边的全部Rect去掉,后面再重建中文字符的位置; 306 | *
  • 从特殊字符Rect开始,依次选择6个Rect,多余的舍去。 307 | *
      308 | * 309 | * @param vecRect 310 | * @param outRect 311 | * @param specIndex 312 | * @return 313 | */ 314 | private int RebuildRect(final Vector vecRect, Vector outRect, int specIndex) { 315 | // 最大只能有7个Rect,减去中文的就只有6个Rect 316 | int count = 6; 317 | for (int i = 0; i < vecRect.size(); i++) { 318 | // 将特殊字符左边的Rect去掉,这个可能会去掉中文Rect,不过没关系,我们后面会重建。 319 | if (i < specIndex) 320 | continue; 321 | 322 | outRect.add(vecRect.get(i)); 323 | if (--count == 0) 324 | break; 325 | } 326 | 327 | return 0; 328 | } 329 | 330 | /** 331 | * 将Rect按位置从左到右进行排序 332 | * 333 | * @param vecRect 334 | * @param out 335 | * @return 336 | */ 337 | private void SortRect(final Vector vecRect, Vector out) { 338 | Vector orderIndex = new Vector(); 339 | Vector xpositions = new Vector(); 340 | for (int i = 0; i < vecRect.size(); ++i) { 341 | orderIndex.add(i); 342 | xpositions.add(vecRect.get(i).x()); 343 | } 344 | 345 | float min = xpositions.get(0); 346 | int minIdx; 347 | for (int i = 0; i < xpositions.size(); ++i) { 348 | min = xpositions.get(i); 349 | minIdx = i; 350 | for (int j = i; j < xpositions.size(); ++j) { 351 | if (xpositions.get(j) < min) { 352 | min = xpositions.get(j); 353 | minIdx = j; 354 | } 355 | } 356 | int aux_i = orderIndex.get(i); 357 | int aux_min = orderIndex.get(minIdx); 358 | orderIndex.remove(i); 359 | orderIndex.insertElementAt(aux_min, i); 360 | orderIndex.remove(minIdx); 361 | orderIndex.insertElementAt(aux_i, minIdx); 362 | 363 | float aux_xi = xpositions.get(i); 364 | float aux_xmin = xpositions.get(minIdx); 365 | xpositions.remove(i); 366 | xpositions.insertElementAt((int) aux_xmin, i); 367 | xpositions.remove(minIdx); 368 | xpositions.insertElementAt((int) aux_xi, minIdx); 369 | } 370 | 371 | for (int i = 0; i < orderIndex.size(); i++) 372 | out.add(vecRect.get(orderIndex.get(i))); 373 | 374 | return; 375 | } 376 | 377 | public void setLiuDingSize(int param) { 378 | this.liuDingSize = param; 379 | } 380 | 381 | public void setColorThreshold(int param) { 382 | this.colorThreshold = param; 383 | } 384 | 385 | public void setBluePercent(float param) { 386 | this.bluePercent = param; 387 | } 388 | 389 | public final float getBluePercent() { 390 | return this.bluePercent; 391 | } 392 | 393 | public void setWhitePercent(float param) { 394 | this.whitePercent = param; 395 | } 396 | 397 | public final float getWhitePercent() { 398 | return this.whitePercent; 399 | } 400 | 401 | public boolean getDebug() { 402 | return this.isDebug; 403 | } 404 | 405 | public void setDebug(boolean isDebug) { 406 | this.isDebug = isDebug; 407 | } 408 | 409 | // 是否开启调试模式常量,默认false代表关闭 410 | final static boolean DEFAULT_DEBUG = true; 411 | 412 | // preprocessChar所用常量 413 | final static int CHAR_SIZE = 20; 414 | final static int HORIZONTAL = 1; 415 | final static int VERTICAL = 0; 416 | 417 | final static int DEFAULT_LIUDING_SIZE = 7; 418 | final static int DEFAULT_MAT_WIDTH = 136; 419 | 420 | final static int DEFAULT_COLORTHRESHOLD = 150; 421 | final static float DEFAULT_BLUEPERCEMT = 0.3f; 422 | final static float DEFAULT_WHITEPERCEMT = 0.1f; 423 | 424 | private int liuDingSize = DEFAULT_LIUDING_SIZE; 425 | private int theMatWidth = DEFAULT_MAT_WIDTH; 426 | 427 | private int colorThreshold = DEFAULT_COLORTHRESHOLD; 428 | private float bluePercent = DEFAULT_BLUEPERCEMT; 429 | private float whitePercent = DEFAULT_WHITEPERCEMT; 430 | 431 | private boolean isDebug = DEFAULT_DEBUG; 432 | } 433 | -------------------------------------------------------------------------------- /src/org/easypr/core/CoreFunc.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.*; 4 | import static org.bytedeco.javacpp.opencv_highgui.cvShowImage; 5 | import static org.bytedeco.javacpp.opencv_highgui.cvWaitKey; 6 | import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2HSV; 7 | import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; 8 | import static org.bytedeco.javacpp.opencv_imgproc.equalizeHist; 9 | import static org.bytedeco.javacpp.opencv_imgproc.resize; 10 | 11 | import org.bytedeco.javacpp.BytePointer; 12 | import org.bytedeco.javacpp.opencv_core.IplImage; 13 | import org.bytedeco.javacpp.opencv_core.Mat; 14 | import org.bytedeco.javacpp.opencv_core.MatVector; 15 | import org.bytedeco.javacpp.opencv_core.Size; 16 | import org.bytedeco.javacpp.indexer.FloatIndexer; 17 | 18 | /** 19 | * @author lin.yao 20 | * 21 | */ 22 | public class CoreFunc { 23 | public enum Color { 24 | UNKNOWN, BLUE, YELLOW 25 | }; 26 | 27 | public enum Direction { 28 | UNKNOWN, VERTICAL, HORIZONTAL 29 | } 30 | 31 | /** 32 | * 鏍规嵁涓�骞呭浘鍍忎笌棰滆壊妯℃澘鑾峰彇瀵瑰簲鐨勪簩鍊煎浘 33 | * 34 | * @param src 35 | * 杈撳叆RGB鍥惧儚 36 | * @param r 37 | * 棰滆壊妯℃澘锛堣摑鑹层�侀粍鑹诧級 38 | * @param adaptive_minsv 39 | * S鍜孷鐨勬渶灏忓�肩敱adaptive_minsv杩欎釜bool鍊煎垽鏂� 40 | *
        41 | *
      • 濡傛灉涓簍rue锛屽垯鏈�灏忓�煎彇鍐充簬H鍊硷紝鎸夋瘮渚嬭“鍑� 42 | *
      • 濡傛灉涓篺alse锛屽垯涓嶅啀鑷�傚簲锛屼娇鐢ㄥ浐瀹氱殑鏈�灏忓�糾inabs_sv 43 | *
      44 | * @return 杈撳嚭鐏板害鍥撅紙鍙湁0鍜�255涓や釜鍊硷紝255浠h〃鍖归厤锛�0浠h〃涓嶅尮閰嶏級 45 | */ 46 | public static Mat colorMatch(final Mat src, final Color r, final boolean adaptive_minsv) { 47 | final float max_sv = 255; 48 | final float minref_sv = 64; 49 | final float minabs_sv = 95; 50 | 51 | // blue鐨凥鑼冨洿 52 | final int min_blue = 100; 53 | final int max_blue = 140; 54 | 55 | // yellow鐨凥鑼冨洿 56 | final int min_yellow = 15; 57 | final int max_yellow = 40; 58 | 59 | // 杞埌HSV绌洪棿杩涜澶勭悊锛岄鑹叉悳绱富瑕佷娇鐢ㄧ殑鏄疕鍒嗛噺杩涜钃濊壊涓庨粍鑹茬殑鍖归厤宸ヤ綔 60 | Mat src_hsv = new Mat(); 61 | cvtColor(src, src_hsv, CV_BGR2HSV); 62 | MatVector hsvSplit = new MatVector(); 63 | split(src_hsv, hsvSplit); 64 | equalizeHist(hsvSplit.get(2), hsvSplit.get(2)); 65 | merge(hsvSplit, src_hsv); 66 | 67 | // 鍖归厤妯℃澘鍩鸿壊,鍒囨崲浠ユ煡鎵炬兂瑕佺殑鍩鸿壊 68 | int min_h = 0; 69 | int max_h = 0; 70 | switch (r) { 71 | case BLUE: 72 | min_h = min_blue; 73 | max_h = max_blue; 74 | break; 75 | case YELLOW: 76 | min_h = min_yellow; 77 | max_h = max_yellow; 78 | break; 79 | default: 80 | break; 81 | } 82 | 83 | float diff_h = (float) ((max_h - min_h) / 2); 84 | int avg_h = (int) (min_h + diff_h); 85 | 86 | int channels = src_hsv.channels(); 87 | int nRows = src_hsv.rows(); 88 | // 鍥惧儚鏁版嵁鍒楅渶瑕佽�冭檻閫氶亾鏁扮殑褰卞搷锛� 89 | int nCols = src_hsv.cols() * channels; 90 | 91 | // 杩炵画瀛樺偍鐨勬暟鎹紝鎸変竴琛屽鐞� 92 | if (src_hsv.isContinuous()) { 93 | nCols *= nRows; 94 | nRows = 1; 95 | } 96 | 97 | for (int i = 0; i < nRows; ++i) { 98 | BytePointer p = src_hsv.ptr(i); 99 | for (int j = 0; j < nCols; j += 3) { 100 | int H = p.get(j) & 0xFF; 101 | int S = p.get(j + 1) & 0xFF; 102 | int V = p.get(j + 2) & 0xFF; 103 | 104 | boolean colorMatched = false; 105 | 106 | if (H > min_h && H < max_h) { 107 | int Hdiff = 0; 108 | if (H > avg_h) 109 | Hdiff = H - avg_h; 110 | else 111 | Hdiff = avg_h - H; 112 | 113 | float Hdiff_p = Hdiff / diff_h; 114 | 115 | float min_sv = 0; 116 | if (true == adaptive_minsv) 117 | min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p); 118 | else 119 | min_sv = minabs_sv; 120 | 121 | if ((S > min_sv && S <= max_sv) && (V > min_sv && V <= max_sv)) 122 | colorMatched = true; 123 | } 124 | 125 | if (colorMatched == true) { 126 | p.put(j, (byte) 0); 127 | p.put(j + 1, (byte) 0); 128 | p.put(j + 2, (byte) 255); 129 | } else { 130 | p.put(j, (byte) 0); 131 | p.put(j + 1, (byte) 0); 132 | p.put(j + 2, (byte) 0); 133 | } 134 | } 135 | } 136 | 137 | // 鑾峰彇棰滆壊鍖归厤鍚庣殑浜屽�肩伆搴﹀浘 138 | MatVector hsvSplit_done = new MatVector(); 139 | split(src_hsv, hsvSplit_done); 140 | Mat src_grey = hsvSplit_done.get(2); 141 | 142 | return src_grey; 143 | } 144 | 145 | /** 146 | * 鍒ゆ柇涓�涓溅鐗岀殑棰滆壊 147 | * 148 | * @param src 149 | * 杞︾墝mat 150 | * @param r 151 | * 棰滆壊妯℃澘 152 | * @param adaptive_minsv 153 | * S鍜孷鐨勬渶灏忓�肩敱adaptive_minsv杩欎釜bool鍊煎垽鏂� 154 | *
        155 | *
      • 濡傛灉涓簍rue锛屽垯鏈�灏忓�煎彇鍐充簬H鍊硷紝鎸夋瘮渚嬭“鍑� 156 | *
      • 濡傛灉涓篺alse锛屽垯涓嶅啀鑷�傚簲锛屼娇鐢ㄥ浐瀹氱殑鏈�灏忓�糾inabs_sv 157 | *
      158 | * @return 159 | */ 160 | public static boolean plateColorJudge(final Mat src, final Color color, final boolean adaptive_minsv) { 161 | // 鍒ゆ柇闃堝�� 162 | final float thresh = 0.49f; 163 | 164 | Mat gray = colorMatch(src, color, adaptive_minsv); 165 | 166 | float percent = (float) countNonZero(gray) / (gray.rows() * gray.cols()); 167 | 168 | return (percent > thresh) ? true : false; 169 | } 170 | 171 | /** 172 | * getPlateType 鍒ゆ柇杞︾墝鐨勭被鍨� 173 | * 174 | * @param src 175 | * @param adaptive_minsv 176 | * S鍜孷鐨勬渶灏忓�肩敱adaptive_minsv杩欎釜bool鍊煎垽鏂� 177 | *
        178 | *
      • 濡傛灉涓簍rue锛屽垯鏈�灏忓�煎彇鍐充簬H鍊硷紝鎸夋瘮渚嬭“鍑� 179 | *
      • 濡傛灉涓篺alse锛屽垯涓嶅啀鑷�傚簲锛屼娇鐢ㄥ浐瀹氱殑鏈�灏忓�糾inabs_sv 180 | *
      181 | * @return 182 | */ 183 | public static Color getPlateType(final Mat src, final boolean adaptive_minsv) { 184 | if (plateColorJudge(src, Color.BLUE, adaptive_minsv) == true) { 185 | return Color.BLUE; 186 | } else if (plateColorJudge(src, Color.YELLOW, adaptive_minsv) == true) { 187 | return Color.YELLOW; 188 | } else { 189 | return Color.UNKNOWN; 190 | } 191 | } 192 | 193 | /** 194 | * 鑾峰彇鍨傜洿鎴栨按骞虫柟鍚戠洿鏂瑰浘 195 | * 196 | * @param img 197 | * @param direction 198 | * @return 199 | */ 200 | public static float[] projectedHistogram(final Mat img, Direction direction) { 201 | int sz = 0; 202 | switch (direction) { 203 | case HORIZONTAL: 204 | sz = img.rows(); 205 | break; 206 | 207 | case VERTICAL: 208 | sz = img.cols(); 209 | break; 210 | 211 | default: 212 | break; 213 | } 214 | 215 | // 缁熻杩欎竴琛屾垨涓�鍒椾腑锛岄潪闆跺厓绱犵殑涓暟锛屽苟淇濆瓨鍒皀onZeroMat涓� 216 | float[] nonZeroMat = new float[sz]; 217 | extractChannel(img, img, 0); 218 | for (int j = 0; j < sz; j++) { 219 | Mat data = (direction == Direction.HORIZONTAL) ? img.row(j) : img.col(j); 220 | int count = countNonZero(data); 221 | nonZeroMat[j] = count; 222 | } 223 | 224 | // Normalize histogram 225 | float max = 0; 226 | for (int j = 0; j < nonZeroMat.length; ++j) { 227 | max = Math.max(max, nonZeroMat[j]); 228 | } 229 | 230 | if (max > 0) { 231 | for (int j = 0; j < nonZeroMat.length; ++j) { 232 | nonZeroMat[j] /= max; 233 | } 234 | } 235 | 236 | return nonZeroMat; 237 | } 238 | 239 | /** 240 | * Assign values to feature 241 | *

      242 | * 鏍锋湰鐗瑰緛涓烘按骞炽�佸瀭鐩寸洿鏂瑰浘鍜屼綆鍒嗚鲸鐜囧浘鍍忔墍缁勬垚鐨勭煝閲� 243 | * 244 | * @param in 245 | * @param sizeData 246 | * 浣庡垎杈ㄧ巼鍥惧儚size = sizeData*sizeData, 鍙互涓�0 247 | * @return 248 | */ 249 | public static Mat features(final Mat in, final int sizeData) { 250 | 251 | float[] vhist = projectedHistogram(in, Direction.VERTICAL); 252 | float[] hhist = projectedHistogram(in, Direction.HORIZONTAL); 253 | 254 | Mat lowData = new Mat(); 255 | if (sizeData > 0) { 256 | resize(in, lowData, new Size(sizeData, sizeData)); 257 | } 258 | 259 | int numCols = vhist.length + hhist.length + lowData.cols() * lowData.rows(); 260 | Mat out = Mat.zeros(1, numCols, CV_32F).asMat(); 261 | FloatIndexer idx = out.createIndexer(); 262 | 263 | int j = 0; 264 | for (int i = 0; i < vhist.length; ++i, ++j) { 265 | idx.put(0, j, vhist[i]); 266 | } 267 | for (int i = 0; i < hhist.length; ++i, ++j) { 268 | idx.put(0, j, hhist[i]); 269 | } 270 | for (int x = 0; x < lowData.cols(); x++) { 271 | for (int y = 0; y < lowData.rows(); y++, ++j) { 272 | float val = lowData.ptr(x, y).get() & 0xFF; 273 | idx.put(0, j, val); 274 | } 275 | } 276 | 277 | return out; 278 | } 279 | 280 | /** 281 | * Show image 282 | * 283 | * @param title 284 | * @param src 285 | */ 286 | public static void showImage(final String title, final Mat src) { 287 | try { 288 | IplImage image = src.asIplImage(); 289 | if (image != null) { 290 | cvShowImage(title, image); 291 | cvWaitKey(0); 292 | } 293 | } catch (Exception ex) { 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/org/easypr/core/Features.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.merge; 4 | import static org.bytedeco.javacpp.opencv_core.split; 5 | import static org.bytedeco.javacpp.opencv_imgproc.*; 6 | import static org.easypr.core.CoreFunc.features; 7 | 8 | import org.bytedeco.javacpp.opencv_core.Mat; 9 | import org.bytedeco.javacpp.opencv_core.MatVector; 10 | 11 | /** 12 | * 13 | * @author Created by fanwenjie 14 | * @author lin.yao 15 | * 16 | */ 17 | public class Features implements SVMCallback { 18 | 19 | /* 20 | * (non-Javadoc) 21 | * 22 | * @see org.easypr.core.SVMCallback#getHisteqFeatures(org.bytedeco.javacpp. 23 | * opencv_core.Mat) 24 | */ 25 | // @Override 26 | public Mat getHisteqFeatures(final Mat image) { 27 | return histeq(image); 28 | } 29 | 30 | /* 31 | * (non-Javadoc) 32 | * 33 | * @see 34 | * org.easypr.core.SVMCallback#getHistogramFeatures(org.bytedeco.javacpp 35 | * .opencv_core.Mat) 36 | */ 37 | public Mat getHistogramFeatures(Mat image) { 38 | Mat grayImage = new Mat(); 39 | cvtColor(image, grayImage, CV_RGB2GRAY); 40 | 41 | Mat img_threshold = new Mat(); 42 | threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); 43 | 44 | return features(img_threshold, 0); 45 | } 46 | 47 | /* 48 | * (non-Javadoc) 49 | * 50 | * @see 51 | * org.easypr.core.SVMCallback#getSIFTFeatures(org.bytedeco.javacpp.opencv_core 52 | * .Mat) 53 | */ 54 | public Mat getSIFTFeatures(final Mat image) { 55 | // TODO: 待完善 56 | return null; 57 | } 58 | 59 | /* 60 | * (non-Javadoc) 61 | * 62 | * @see 63 | * org.easypr.core.SVMCallback#getHOGFeatures(org.bytedeco.javacpp.opencv_core 64 | * .Mat) 65 | */ 66 | public Mat getHOGFeatures(final Mat image) { 67 | // TODO: 待完善 68 | return null; 69 | } 70 | 71 | private Mat histeq(Mat in) { 72 | Mat out = new Mat(in.size(), in.type()); 73 | if (in.channels() == 3) { 74 | Mat hsv = new Mat(); 75 | MatVector hsvSplit = new MatVector(); 76 | cvtColor(in, hsv, CV_BGR2HSV); 77 | split(hsv, hsvSplit); 78 | equalizeHist(hsvSplit.get(2), hsvSplit.get(2)); 79 | merge(hsvSplit, hsv); 80 | cvtColor(hsv, out, CV_HSV2BGR); 81 | hsv = null; 82 | hsvSplit = null; 83 | System.gc(); 84 | } else if (in.channels() == 1) { 85 | equalizeHist(in, out); 86 | } 87 | return out; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/org/easypr/core/PlateDetect.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_highgui.imwrite; 4 | 5 | import java.util.Vector; 6 | 7 | import org.bytedeco.javacpp.opencv_core.Mat; 8 | 9 | /** 10 | * @author Created by fanwenjie 11 | * @author lin.yao 12 | * 13 | */ 14 | public class PlateDetect { 15 | 16 | /** 17 | * @param src 18 | * @param resultVec 19 | * 可能是车牌的图块集合 20 | * @return the error number 21 | *

        22 | *
      • 0: plate detected successfully; 23 | *
      • -1: source Mat is empty; 24 | *
      • -2: plate not detected. 25 | *
      26 | */ 27 | public int plateDetect(final Mat src, Vector resultVec) { 28 | Vector matVec = plateLocate.plateLocate(src); 29 | 30 | if (0 == matVec.size()) { 31 | return -1; 32 | } 33 | 34 | if (0 != plateJudge.plateJudge(matVec, resultVec)) { 35 | return -2; 36 | } 37 | 38 | if (getPDDebug()) { 39 | int size = (int) resultVec.size(); 40 | for (int i = 0; i < size; i++) { 41 | Mat img = resultVec.get(i); 42 | String str = "tmp/plate_judge_result_" + Integer.valueOf(i).toString() + ".jpg"; 43 | imwrite(str, img); 44 | } 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | /** 51 | * 生活模式与工业模式切换 52 | * 53 | * @param pdLifemode 54 | */ 55 | public void setPDLifemode(boolean pdLifemode) { 56 | plateLocate.setLifemode(pdLifemode); 57 | } 58 | 59 | /** 60 | * 是否开启调试模式 61 | * 62 | * @param pdDebug 63 | */ 64 | public void setPDDebug(boolean pdDebug) { 65 | plateLocate.setDebug(pdDebug); 66 | } 67 | 68 | /** 69 | * 获取调试模式状态 70 | * 71 | * @return 72 | */ 73 | public boolean getPDDebug() { 74 | return plateLocate.getDebug(); 75 | } 76 | 77 | public void setGaussianBlurSize(int gaussianBlurSize) { 78 | plateLocate.setGaussianBlurSize(gaussianBlurSize); 79 | } 80 | 81 | public final int getGaussianBlurSize() { 82 | return plateLocate.getGaussianBlurSize(); 83 | } 84 | 85 | public void setMorphSizeWidth(int morphSizeWidth) { 86 | plateLocate.setMorphSizeWidth(morphSizeWidth); 87 | } 88 | 89 | public final int getMorphSizeWidth() { 90 | return plateLocate.getMorphSizeWidth(); 91 | } 92 | 93 | public void setMorphSizeHeight(int morphSizeHeight) { 94 | plateLocate.setMorphSizeHeight(morphSizeHeight); 95 | } 96 | 97 | public final int getMorphSizeHeight() { 98 | return plateLocate.getMorphSizeHeight(); 99 | } 100 | 101 | public void setVerifyError(float verifyError) { 102 | plateLocate.setVerifyError(verifyError); 103 | } 104 | 105 | public final float getVerifyError() { 106 | return plateLocate.getVerifyError(); 107 | } 108 | 109 | public void setVerifyAspect(float verifyAspect) { 110 | plateLocate.setVerifyAspect(verifyAspect); 111 | } 112 | 113 | public final float getVerifyAspect() { 114 | return plateLocate.getVerifyAspect(); 115 | } 116 | 117 | public void setVerifyMin(int verifyMin) { 118 | plateLocate.setVerifyMin(verifyMin); 119 | } 120 | 121 | public void setVerifyMax(int verifyMax) { 122 | plateLocate.setVerifyMax(verifyMax); 123 | } 124 | 125 | public void setJudgeAngle(int judgeAngle) { 126 | plateLocate.setJudgeAngle(judgeAngle); 127 | } 128 | 129 | private PlateLocate plateLocate = new PlateLocate(); 130 | 131 | private PlateJudge plateJudge = new PlateJudge(); 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/org/easypr/core/PlateJudge.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.CV_32FC1; 4 | import static org.bytedeco.javacpp.opencv_imgproc.resize; 5 | 6 | import java.util.Vector; 7 | 8 | import org.bytedeco.javacpp.opencv_core.Mat; 9 | import org.bytedeco.javacpp.opencv_core.Rect; 10 | import org.bytedeco.javacpp.opencv_core.Size; 11 | import org.bytedeco.javacpp.opencv_ml.CvSVM; 12 | 13 | /** 14 | * @author Created by fanwenjie 15 | * @author lin.yao 16 | * 17 | */ 18 | public class PlateJudge { 19 | 20 | public PlateJudge() { 21 | loadModel(); 22 | } 23 | 24 | public void loadModel() { 25 | loadModel(path); 26 | } 27 | 28 | public void loadModel(String s) { 29 | svm.clear(); 30 | svm.load(s, "svm"); 31 | } 32 | 33 | /** 34 | * 对单幅图像进行SVM判断 35 | * 36 | * @param inMat 37 | * @return 38 | */ 39 | public int plateJudge(final Mat inMat) { 40 | Mat features = this.features.getHistogramFeatures(inMat); 41 | 42 | // 通过直方图均衡化后的彩色图进行预测 43 | Mat p = features.reshape(1, 1); 44 | p.convertTo(p, CV_32FC1); 45 | float ret = svm.predict(features); 46 | 47 | return (int) ret; 48 | } 49 | 50 | /** 51 | * 对多幅图像进行SVM判断 52 | * 53 | * @param inVec 54 | * @param resultVec 55 | * @return 56 | */ 57 | public int plateJudge(Vector inVec, Vector resultVec) { 58 | 59 | for (int j = 0; j < inVec.size(); j++) { 60 | Mat inMat = inVec.get(j); 61 | 62 | if (1 == plateJudge(inMat)) { 63 | resultVec.add(inMat); 64 | } else { // 再取中间部分判断一次 65 | int w = inMat.cols(); 66 | int h = inMat.rows(); 67 | 68 | Mat tmpDes = inMat.clone(); 69 | Mat tmpMat = new Mat(inMat, new Rect((int) (w * 0.05), (int) (h * 0.1), (int) (w * 0.9), 70 | (int) (h * 0.8))); 71 | resize(tmpMat, tmpDes, new Size(inMat.size())); 72 | 73 | if (plateJudge(tmpDes) == 1) { 74 | resultVec.add(inMat); 75 | } 76 | } 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | public void setModelPath(String path) { 83 | this.path = path; 84 | } 85 | 86 | public final String getModelPath() { 87 | return path; 88 | } 89 | 90 | private CvSVM svm = new CvSVM(); 91 | 92 | /** 93 | * EasyPR的getFeatures回调函数, 用于从车牌的image生成svm的训练特征features 94 | */ 95 | private SVMCallback features = new Features(); 96 | 97 | /** 98 | * 模型存储路径 99 | */ 100 | private String path = "res/model/svm.xml"; 101 | } 102 | -------------------------------------------------------------------------------- /src/org/easypr/core/PlateLocate.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import static org.bytedeco.javacpp.opencv_core.*; 4 | import static org.bytedeco.javacpp.opencv_highgui.imwrite; 5 | import static org.bytedeco.javacpp.opencv_imgproc.*; 6 | import org.easypr.Main; 7 | import java.util.Vector; 8 | 9 | import javax.swing.JFrame; 10 | 11 | import org.bytedeco.javacpp.opencv_core.Mat; 12 | import org.bytedeco.javacpp.opencv_core.MatVector; 13 | import org.bytedeco.javacpp.opencv_core.Point; 14 | import org.bytedeco.javacpp.opencv_core.Point2f; 15 | import org.bytedeco.javacpp.opencv_core.RotatedRect; 16 | import org.bytedeco.javacpp.opencv_core.Scalar; 17 | import org.bytedeco.javacpp.opencv_core.Size; 18 | import org.bytedeco.javacv.CanvasFrame; 19 | 20 | /** 21 | * @author Created by fanwenjie 22 | * @author lin.yao 23 | * 24 | */ 25 | public class PlateLocate { 26 | 27 | /** 28 | * 生活模式与工业模式切换 29 | * 30 | * @param islifemode 31 | * 如果为真,则设置各项参数为定位生活场景照片(如百度图片)的参数,否则恢复默认值。 32 | * 33 | */ 34 | public void setLifemode(boolean islifemode) { 35 | if (islifemode) { 36 | setGaussianBlurSize(5); 37 | setMorphSizeWidth(9); 38 | setMorphSizeHeight(3); 39 | setVerifyError(0.9f); 40 | setVerifyAspect(4); 41 | setVerifyMin(1); 42 | setVerifyMax(30); 43 | } else { 44 | setGaussianBlurSize(DEFAULT_GAUSSIANBLUR_SIZE); 45 | setMorphSizeWidth(DEFAULT_MORPH_SIZE_WIDTH); 46 | setMorphSizeHeight(DEFAULT_MORPH_SIZE_HEIGHT); 47 | setVerifyError(DEFAULT_ERROR); 48 | setVerifyAspect(DEFAULT_ASPECT); 49 | setVerifyMin(DEFAULT_VERIFY_MIN); 50 | setVerifyMax(DEFAULT_VERIFY_MAX); 51 | } 52 | } 53 | 54 | /** 55 | * 定位车牌图像 56 | * 57 | * @param src 58 | * 原始图像 59 | * @return 一个Mat的向量,存储所有抓取到的图像 60 | */ 61 | public Vector plateLocate(Mat src) { 62 | Vector resultVec = new Vector(); 63 | 64 | Mat src_blur = new Mat(); 65 | Mat src_gray = new Mat(); 66 | Mat grad = new Mat(); 67 | 68 | int scale = SOBEL_SCALE; 69 | int delta = SOBEL_DELTA; 70 | int ddepth = SOBEL_DDEPTH; 71 | 72 | // 高斯模糊。Size中的数字影响车牌定位的效果。 73 | GaussianBlur(src, src_blur, new Size(gaussianBlurSize, gaussianBlurSize), 0, 0, BORDER_DEFAULT); 74 | if (debug) { 75 | imwrite("tmp/debug_GaussianBlur.jpg", src_blur); 76 | } 77 | 78 | // Convert it to gray 将图像进行灰度化 79 | cvtColor(src_blur, src_gray, CV_RGB2GRAY); 80 | if (debug) { 81 | imwrite("tmp/debug_gray.jpg", src_gray); 82 | } 83 | 84 | // 对图像进行Sobel 运算,得到的是图像的一阶水平方向导数。 85 | 86 | // Generate grad_x and grad_y 87 | Mat grad_x = new Mat(); 88 | Mat grad_y = new Mat(); 89 | Mat abs_grad_x = new Mat(); 90 | Mat abs_grad_y = new Mat(); 91 | 92 | Sobel(src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT); 93 | convertScaleAbs(grad_x, abs_grad_x); 94 | 95 | Sobel(src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT); 96 | convertScaleAbs(grad_y, abs_grad_y); 97 | 98 | // Total Gradient (approximate) 99 | addWeighted(abs_grad_x, SOBEL_X_WEIGHT, abs_grad_y, SOBEL_Y_WEIGHT, 0, grad); 100 | 101 | if (debug) { 102 | imwrite("tmp/debug_Sobel.jpg", grad); 103 | } 104 | 105 | // 对图像进行二值化。将灰度图像(每个像素点有256 个取值可能)转化为二值图像(每个像素点仅有1 和0 两个取值可能)。 106 | 107 | Mat img_threshold = new Mat(); 108 | threshold(grad, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); 109 | 110 | if (debug) { 111 | imwrite("tmp/debug_threshold.jpg", img_threshold); 112 | } 113 | 114 | // 使用闭操作。对图像进行闭操作以后,可以看到车牌区域被连接成一个矩形装的区域。 115 | 116 | Mat element = getStructuringElement(MORPH_RECT, new Size(morphSizeWidth, morphSizeHeight)); 117 | morphologyEx(img_threshold, img_threshold, MORPH_CLOSE, element); 118 | 119 | if (debug) { 120 | imwrite("tmp/debug_morphology.jpg", img_threshold); 121 | } 122 | 123 | // Find 轮廓 of possibles plates 求轮廓。求出图中所有的轮廓。这个算法会把全图的轮廓都计算出来,因此要进行筛选。 124 | 125 | MatVector contours = new MatVector(); 126 | findContours(img_threshold, contours, // a vector of contours 127 | CV_RETR_EXTERNAL, // 提取外部轮廓 128 | CV_CHAIN_APPROX_NONE); // all pixels of each contours 129 | 130 | Mat result = new Mat(); 131 | if (debug) { 132 | // Draw red contours on the source image 133 | src.copyTo(result); 134 | drawContours(result, contours, -1, new Scalar(0, 0, 255, 255)); 135 | imwrite("tmp/debug_Contours.jpg", result); 136 | } 137 | 138 | // Start to iterate to each contour founded 139 | // 筛选。对轮廓求最小外接矩形,然后验证,不满足条件的淘汰。 140 | 141 | Vector rects = new Vector(); 142 | 143 | for (int i = 0; i < contours.size(); ++i) { 144 | RotatedRect mr = minAreaRect(contours.get(i)); 145 | if (verifySizes(mr)) 146 | rects.add(mr); 147 | } 148 | 149 | int k = 1; 150 | for (int i = 0; i < rects.size(); i++) { 151 | RotatedRect minRect = rects.get(i); 152 | if (verifySizes(minRect)) { 153 | 154 | if (debug) { 155 | Point2f rect_points = new Point2f(4); 156 | minRect.points(rect_points); 157 | 158 | for (int j = 0; j < 4; j++) { 159 | Point pt1 = new Point(rect_points.position(j).asCvPoint2D32f()); 160 | Point pt2 = new Point(rect_points.position((j + 1) % 4).asCvPoint2D32f()); 161 | 162 | line(result, pt1, pt2, new Scalar(0, 255, 255, 255), 1, 8, 0); 163 | } 164 | } 165 | 166 | // rotated rectangle drawing 167 | // 旋转这部分代码确实可以将某些倾斜的车牌调整正,但是它也会误将更多正的车牌搞成倾斜!所以综合考虑,还是不使用这段代码。 168 | // 2014-08-14,由于新到的一批图片中发现有很多车牌是倾斜的,因此决定再次尝试这段代码。 169 | 170 | float r = minRect.size().width() / minRect.size().height(); 171 | float angle = minRect.angle(); 172 | Size rect_size = new Size((int) minRect.size().width(), (int) minRect.size().height()); 173 | if (r < 1) { 174 | angle = 90 + angle; 175 | rect_size = new Size(rect_size.height(), rect_size.width()); 176 | } 177 | // 如果抓取的方块旋转超过m_angle角度,则不是车牌,放弃处理 178 | if (angle - this.angle < 0 && angle + this.angle > 0) { 179 | // Create and rotate image 180 | Mat rotmat = getRotationMatrix2D(minRect.center(), angle, 1); 181 | Mat img_rotated = new Mat(); 182 | warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC 183 | 184 | Mat resultMat = showResultMat(img_rotated, rect_size, minRect.center(), k++); 185 | resultVec.add(resultMat); 186 | 187 | 188 | 189 | } 190 | } 191 | } 192 | if (debug) { 193 | imwrite("tmp/debug_result.jpg", result); 194 | } 195 | 196 | return resultVec; 197 | } 198 | 199 | // 设置与读取变量 200 | 201 | public void setGaussianBlurSize(int gaussianBlurSize) { 202 | this.gaussianBlurSize = gaussianBlurSize; 203 | } 204 | 205 | public final int getGaussianBlurSize() { 206 | return this.gaussianBlurSize; 207 | } 208 | 209 | public void setMorphSizeWidth(int morphSizeWidth) { 210 | this.morphSizeWidth = morphSizeWidth; 211 | } 212 | 213 | public final int getMorphSizeWidth() { 214 | return this.morphSizeWidth; 215 | } 216 | 217 | public void setMorphSizeHeight(int morphSizeHeight) { 218 | this.morphSizeHeight = morphSizeHeight; 219 | } 220 | 221 | public final int getMorphSizeHeight() { 222 | return this.morphSizeHeight; 223 | } 224 | 225 | public void setVerifyError(float error) { 226 | this.error = error; 227 | } 228 | 229 | public final float getVerifyError() { 230 | return this.error; 231 | } 232 | 233 | public void setVerifyAspect(float aspect) { 234 | this.aspect = aspect; 235 | } 236 | 237 | public final float getVerifyAspect() { 238 | return this.aspect; 239 | } 240 | 241 | public void setVerifyMin(int verifyMin) { 242 | this.verifyMin = verifyMin; 243 | } 244 | 245 | public void setVerifyMax(int verifyMax) { 246 | this.verifyMax = verifyMax; 247 | } 248 | 249 | public void setJudgeAngle(int angle) { 250 | this.angle = angle; 251 | } 252 | 253 | /** 254 | * 是否开启调试模式 255 | * 256 | * @param debug 257 | */ 258 | public void setDebug(boolean debug) { 259 | this.debug = debug; 260 | } 261 | 262 | /** 263 | * 获取调试模式状态 264 | * 265 | * @return 266 | */ 267 | public boolean getDebug() { 268 | return debug; 269 | } 270 | 271 | /** 272 | * 对minAreaRect获得的最小外接矩形,用纵横比进行判断 273 | * 274 | * @param mr 275 | * @return 276 | */ 277 | private boolean verifySizes(RotatedRect mr) { 278 | float error = this.error; 279 | 280 | // China car plate size: 440mm*140mm,aspect 3.142857 281 | float aspect = this.aspect; 282 | int min = 44 * 14 * verifyMin; // minimum area 283 | int max = 44 * 14 * verifyMax; // maximum area 284 | 285 | // Get only patchs that match to a respect ratio. 286 | float rmin = aspect - aspect * error; 287 | float rmax = aspect + aspect * error; 288 | 289 | int area = (int) (mr.size().height() * mr.size().width()); 290 | float r = mr.size().width() / mr.size().height(); 291 | if (r < 1) 292 | r = mr.size().height() / mr.size().width(); 293 | 294 | return area >= min && area <= max && r >= rmin && r <= rmax; 295 | } 296 | 297 | /** 298 | * 显示最终生成的车牌图像,便于判断是否成功进行了旋转。 299 | * 300 | * @param src 301 | * @param rect_size 302 | * @param center 303 | * @param index 304 | * @return 305 | */ 306 | private Mat showResultMat(Mat src, Size rect_size, Point2f center, int index) { 307 | Mat img_crop = new Mat(); 308 | getRectSubPix(src, rect_size, center, img_crop); 309 | 310 | if (debug) { 311 | imwrite("tmp/debug_crop_" + index + ".jpg", img_crop); 312 | } 313 | 314 | Mat resultResized = new Mat(); 315 | resultResized.create(HEIGHT, WIDTH, TYPE); 316 | resize(img_crop, resultResized, resultResized.size(), 0, 0, INTER_CUBIC); 317 | if (debug) { 318 | imwrite("tmp/debug_resize_" + index + ".jpg", resultResized); 319 | } 320 | return resultResized; 321 | } 322 | 323 | // PlateLocate所用常量 324 | public static final int DEFAULT_GAUSSIANBLUR_SIZE = 5; 325 | public static final int SOBEL_SCALE = 1; 326 | public static final int SOBEL_DELTA = 0; 327 | public static final int SOBEL_DDEPTH = CV_16S; 328 | public static final int SOBEL_X_WEIGHT = 1; 329 | public static final int SOBEL_Y_WEIGHT = 0; 330 | public static final int DEFAULT_MORPH_SIZE_WIDTH = 17; 331 | public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3; 332 | 333 | // showResultMat所用常量 334 | public static final int WIDTH = 136; 335 | public static final int HEIGHT = 36; 336 | public static final int TYPE = CV_8UC3; 337 | 338 | // verifySize所用常量 339 | public static final int DEFAULT_VERIFY_MIN = 3; 340 | public static final int DEFAULT_VERIFY_MAX = 20; 341 | 342 | final float DEFAULT_ERROR = 0.6f; 343 | final float DEFAULT_ASPECT = 3.75f; 344 | // 角度判断所用常量 345 | public static final int DEFAULT_ANGLE = 30; 346 | 347 | // 是否开启调试模式常量 348 | public static final boolean DEFAULT_DEBUG = true; 349 | 350 | // 高斯模糊所用变量 351 | protected int gaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE; 352 | 353 | // 连接操作所用变量 354 | protected int morphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH; 355 | protected int morphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT; 356 | 357 | // verifySize所用变量 358 | protected float error = DEFAULT_ERROR; 359 | protected float aspect = DEFAULT_ASPECT; 360 | protected int verifyMin = DEFAULT_VERIFY_MIN; 361 | protected int verifyMax = DEFAULT_VERIFY_MAX; 362 | 363 | // 角度判断所用变量 364 | protected int angle = DEFAULT_ANGLE; 365 | 366 | // 是否开启调试模式,0关闭,非0开启 367 | protected boolean debug = DEFAULT_DEBUG; 368 | } 369 | -------------------------------------------------------------------------------- /src/org/easypr/core/PlateRecognize.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import org.bytedeco.javacpp.opencv_core.*; 4 | 5 | import java.util.Vector; 6 | 7 | 8 | /** 9 | * @author Created by fanwenjie 10 | * @author lin.yao 11 | * 12 | */ 13 | public class PlateRecognize { 14 | 15 | public int plateRecognize(Mat src, Vector licenseVec) { 16 | //车牌方块集合 17 | Vector plateVec = new Vector(); 18 | int resultPD = plateDetect.plateDetect(src, plateVec); 19 | if (resultPD == 0) { 20 | int num = (int) plateVec.size(); 21 | for (int j = 0; j < num; j++) { 22 | Mat plate = plateVec.get(j); 23 | //获取车牌颜色 24 | String plateType = charsRecognise.getPlateType(plate); 25 | //获取车牌号 26 | String plateIdentify = charsRecognise.charsRecognise(plate); 27 | String license = plateType + ":" + plateIdentify; 28 | licenseVec.add(license); 29 | System.out.println("车牌识别结果: " + plateType +" "+ plateIdentify); 30 | } 31 | } 32 | return resultPD; 33 | } 34 | 35 | /** 36 | * 设置是否开启生活模式 37 | * @param lifemode 38 | */ 39 | public void setLifemode(boolean lifemode) { 40 | plateDetect.setPDLifemode(lifemode); 41 | } 42 | 43 | /** 44 | * 是否开启调试模式 45 | * @param debug 46 | */ 47 | public void setDebug(boolean debug) { 48 | plateDetect.setPDDebug(debug); 49 | charsRecognise.setCRDebug(debug); 50 | } 51 | 52 | private PlateDetect plateDetect = new PlateDetect(); 53 | private CharsRecognise charsRecognise = new CharsRecognise(); 54 | } 55 | -------------------------------------------------------------------------------- /src/org/easypr/core/SVMCallback.java: -------------------------------------------------------------------------------- 1 | package org.easypr.core; 2 | 3 | import org.bytedeco.javacpp.opencv_core.Mat; 4 | 5 | 6 | /** 7 | * @author Created by fanwenjie 8 | * @author lin.yao 9 | * 10 | */ 11 | public interface SVMCallback { 12 | 13 | /*** 14 | * EasyPR的getFeatures回调函数,本函数是生成直方图均衡特征的回调函数 15 | * 16 | * @param image 17 | * @return 18 | */ 19 | public abstract Mat getHisteqFeatures(final Mat image); 20 | 21 | /** 22 | * EasyPR的getFeatures回调函数, 本函数是获取垂直和水平的直方图图值 23 | * 24 | * @param image 25 | * @return 26 | */ 27 | public abstract Mat getHistogramFeatures(final Mat image); 28 | 29 | /** 30 | * 本函数是获取SITF特征子的回调函数 31 | * 32 | * @param image 33 | * @return 34 | */ 35 | public abstract Mat getSIFTFeatures(final Mat image); 36 | 37 | /** 38 | * 本函数是获取HOG特征子的回调函数 39 | * 40 | * @param image 41 | * @return 42 | */ 43 | public abstract Mat getHOGFeatures(final Mat image); 44 | } 45 | -------------------------------------------------------------------------------- /src/org/easypr/test/EasyPrTest.java: -------------------------------------------------------------------------------- 1 | package org.easypr.test; 2 | 3 | 4 | import static org.bytedeco.javacpp.opencv_highgui.imread; 5 | import static org.easypr.core.CoreFunc.getPlateType; 6 | import static org.easypr.core.CoreFunc.projectedHistogram; 7 | import static org.easypr.core.CoreFunc.showImage; 8 | 9 | import java.util.Vector; 10 | 11 | import org.bytedeco.javacpp.opencv_core.IplImage; 12 | import org.bytedeco.javacpp.opencv_core.Mat; 13 | import org.easypr.core.CharsIdentify; 14 | import org.easypr.core.CharsRecognise; 15 | import org.easypr.core.CoreFunc; 16 | import org.easypr.core.PlateDetect; 17 | import org.easypr.core.PlateLocate; 18 | import org.junit.Test; 19 | 20 | /** 21 | * @author lin.yao 22 | * 23 | */ 24 | public class EasyPrTest { 25 | 26 | @Test 27 | public static void testPlateRecognise(Mat matImage) { 28 | //String imgPath = "res/image/test_image/test.jpg"; 29 | String imgPath = "res/image/test_image/plate_recognize.jpg"; 30 | 31 | Mat src = imread(imgPath); 32 | PlateDetect plateDetect = new PlateDetect(); 33 | plateDetect.setPDLifemode(true); 34 | Vector matVector = new Vector(); 35 | if (0 == plateDetect.plateDetect(matImage, matVector)) { 36 | CharsRecognise cr = new CharsRecognise(); 37 | 38 | for (int i = 0; i < matVector.size(); ++i) { 39 | String result = cr.charsRecognise(matVector.get(i)); 40 | System.out.println("Chars Recognised: " + result); 41 | } 42 | } 43 | } 44 | 45 | @Test 46 | public void testPlateDetect() { 47 | String imgPath = "res/image/test_image/test.jpg"; 48 | 49 | Mat src = imread(imgPath); 50 | PlateDetect plateDetect = new PlateDetect(); 51 | plateDetect.setPDLifemode(true); 52 | Vector matVector = new Vector(); 53 | if (0 == plateDetect.plateDetect(src, matVector)) { 54 | for (int i = 0; i < matVector.size(); ++i) { 55 | showImage("Plate Detected", matVector.get(i)); 56 | } 57 | } 58 | } 59 | 60 | @Test 61 | public void testPlateLocate() { 62 | String imgPath = "res/image/test_image/test.jpg"; 63 | 64 | Mat src = imread(imgPath); 65 | 66 | PlateLocate plate = new PlateLocate(); 67 | plate.setDebug(true); 68 | plate.setLifemode(true); 69 | 70 | Vector resultVec = plate.plateLocate(src); 71 | 72 | int num = resultVec.size(); 73 | for (int j = 0; j < num; j++) { 74 | showImage("Plate Located " + j, resultVec.get(j)); 75 | } 76 | 77 | return; 78 | } 79 | 80 | @Test 81 | public void testCharsRecognise() { 82 | String imgPath = "res/image/test_image/chars_recognise_huAGH092.jpg"; 83 | 84 | Mat src = imread(imgPath); 85 | CharsRecognise cr = new CharsRecognise(); 86 | cr.setCRDebug(true); 87 | String result = cr.charsRecognise(src); 88 | System.out.println("Chars Recognised: " + result); 89 | } 90 | 91 | @Test 92 | public void testColorDetect() { 93 | String imgPath = "res/image/test_image/core_func_yellow.jpg"; 94 | 95 | Mat src = imread(imgPath); 96 | 97 | CoreFunc.Color color = getPlateType(src, true); 98 | System.out.println("Color Deteted: " + color); 99 | } 100 | 101 | @Test 102 | public void testProjectedHistogram() { 103 | String imgPath = "res/image/test_image/chars_identify_E.jpg"; 104 | 105 | Mat src = imread(imgPath); 106 | projectedHistogram(src, CoreFunc.Direction.HORIZONTAL); 107 | } 108 | 109 | @Test 110 | public void testCharsIdentify() { 111 | String imgPath = "res/image/test_image/chars_identify_E.jpg"; 112 | 113 | Mat src = imread(imgPath); 114 | CharsIdentify charsIdentify = new CharsIdentify(); 115 | String result = charsIdentify.charsIdentify(src, false, true); 116 | System.out.println(result); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/org/easypr/test/Test.java: -------------------------------------------------------------------------------- 1 | package org.easypr.test; 2 | 3 | import org.easypr.core.CharsRecognise; 4 | import org.easypr.core.PlateDetect; 5 | 6 | import java.util.Vector; 7 | 8 | import static org.bytedeco.javacpp.opencv_core.*; 9 | import static org.bytedeco.javacpp.opencv_highgui.*; 10 | 11 | /* 12 | * Created by fanwenjie 13 | * @version 1.1 14 | */ 15 | 16 | public class Test { 17 | 18 | public static void plateDetect(){ 19 | String imgPath = "res/image/baidu_image/test2.jpg"; 20 | System.out.println("Plate Detect Test"); 21 | Mat src = imread(imgPath); 22 | PlateDetect plateDetect = new PlateDetect(); 23 | plateDetect.setPDLifemode(true); 24 | Vector matVector = new Vector(); 25 | plateDetect.plateDetect(src, matVector); 26 | if(0==plateDetect.plateDetect(src,matVector)){ 27 | long num = matVector.size(); 28 | for(int i=0;i trainingLabels = new Vector(); 115 | String path = "res/train/data/chars_recognise_ann/chars2/"; 116 | 117 | for (int i = 0; i < numCharacter; i++) { 118 | System.out.println("Character: " + strCharacters[i]); 119 | String str = path + '/' + strCharacters[i]; 120 | Vector files = new Vector(); 121 | Util.getFiles(str, files); 122 | 123 | int size = (int) files.size(); 124 | for (int j = 0; j < size; j++) { 125 | System.out.println(files.get(j)); 126 | Mat img = imread(files.get(j), 0); 127 | Mat f5 = features(img, 5); 128 | Mat f10 = features(img, 10); 129 | Mat f15 = features(img, 15); 130 | Mat f20 = features(img, 20); 131 | 132 | trainingDataf5.push_back(f5); 133 | trainingDataf10.push_back(f10); 134 | trainingDataf15.push_back(f15); 135 | trainingDataf20.push_back(f20); 136 | trainingLabels.add(i); // 每一幅字符图片所对应的字符类别索引下标 137 | } 138 | } 139 | 140 | path = "res/train/data/chars_recognise_ann/charsChinese"; 141 | 142 | for (int i = 0; i < strChinese.length; i++) { 143 | System.out.println("Character: " + strChinese[i]); 144 | String str = path + '/' + strChinese[i]; 145 | Vector files = new Vector(); 146 | Util.getFiles(str, files); 147 | 148 | int size = (int) files.size(); 149 | for (int j = 0; j < size; j++) { 150 | System.out.println(files.get(j)); 151 | Mat img = imread(files.get(j), 0); 152 | Mat f5 = features(img, 5); 153 | Mat f10 = features(img, 10); 154 | Mat f15 = features(img, 15); 155 | Mat f20 = features(img, 20); 156 | 157 | trainingDataf5.push_back(f5); 158 | trainingDataf10.push_back(f10); 159 | trainingDataf15.push_back(f15); 160 | trainingDataf20.push_back(f20); 161 | trainingLabels.add(i + numCharacter); 162 | } 163 | } 164 | 165 | trainingDataf5.convertTo(trainingDataf5, CV_32FC1); 166 | trainingDataf10.convertTo(trainingDataf10, CV_32FC1); 167 | trainingDataf15.convertTo(trainingDataf15, CV_32FC1); 168 | trainingDataf20.convertTo(trainingDataf20, CV_32FC1); 169 | int[] labels = new int[trainingLabels.size()]; 170 | for (int i = 0; i < labels.length; ++i) 171 | labels[i] = trainingLabels.get(i).intValue(); 172 | new Mat(labels).copyTo(classes); 173 | 174 | FileStorage fs = new FileStorage("res/train/ann_data.xml", FileStorage.WRITE); 175 | fs.writeObj("TrainingDataF5", trainingDataf5.data()); 176 | fs.writeObj("TrainingDataF10", trainingDataf10.data()); 177 | fs.writeObj("TrainingDataF15", trainingDataf15.data()); 178 | fs.writeObj("TrainingDataF20", trainingDataf20.data()); 179 | fs.writeObj("classes", classes.data()); 180 | fs.release(); 181 | 182 | System.out.println("End saveTrainData"); 183 | return 0; 184 | } 185 | 186 | public void saveModel(int _predictsize, int _neurons) { 187 | FileStorage fs = new FileStorage("res/train/ann_data.xml", FileStorage.READ); 188 | String training = "TrainingDataF" + _predictsize; 189 | Mat TrainingData = new Mat(fs.get(training).readObj()); 190 | Mat Classes = new Mat(fs.get("classes")); 191 | 192 | // train the Ann 193 | System.out.println("Begin to saveModelChar predictSize:" + Integer.valueOf(_predictsize).toString()); 194 | System.out.println(" neurons:" + Integer.valueOf(_neurons).toString()); 195 | 196 | long start = getTickCount(); 197 | annTrain(TrainingData, Classes, _neurons); 198 | long end = getTickCount(); 199 | System.out.println("GetTickCount:" + Long.valueOf((end - start) / 1000).toString()); 200 | 201 | System.out.println("End the saveModelChar"); 202 | 203 | String model_name = "res/train/ann.xml"; 204 | 205 | // if(1) 206 | // { 207 | // String str = 208 | // String.format("ann_prd:%d\tneu:%d",_predictsize,_neurons); 209 | // model_name = str; 210 | // } 211 | 212 | CvFileStorage fsto = CvFileStorage.open(model_name, CvMemStorage.create(), CV_STORAGE_WRITE); 213 | ann.write(fsto, "ann"); 214 | } 215 | 216 | public int annMain() { 217 | System.out.println("To be begin."); 218 | 219 | saveTrainData(); 220 | 221 | // 可根据需要训练不同的predictSize或者neurons的ANN模型 222 | // for (int i = 2; i <= 2; i ++) 223 | // { 224 | // int size = i * 5; 225 | // for (int j = 5; j <= 10; j++) 226 | // { 227 | // int neurons = j * 10; 228 | // saveModel(size, neurons); 229 | // } 230 | // } 231 | 232 | // 这里演示只训练model文件夹下的ann.xml,此模型是一个predictSize=10,neurons=40的ANN模型。 233 | // 根据机器的不同,训练时间不一样,但一般需要10分钟左右,所以慢慢等一会吧。 234 | saveModel(10, 40); 235 | 236 | System.out.println("To be end."); 237 | return 0; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/org/easypr/train/SVMTrain.java: -------------------------------------------------------------------------------- 1 | package org.easypr.train; 2 | 3 | 4 | import org.easypr.core.Features; 5 | import org.easypr.util.Convert; 6 | import org.easypr.core.SVMCallback; 7 | 8 | import static org.bytedeco.javacpp.opencv_core.*; 9 | import static org.bytedeco.javacpp.opencv_highgui.*; 10 | import static org.bytedeco.javacpp.opencv_ml.*; 11 | 12 | import org.easypr.util.Util; 13 | 14 | import java.util.*; 15 | 16 | /* 17 | * Created by fanwenjie 18 | * @version 1.1 19 | */ 20 | public class SVMTrain { 21 | 22 | private SVMCallback callback = new Features(); 23 | private static final String hasPlate = "HasPlate"; 24 | private static final String noPlate = "NoPlate"; 25 | 26 | public SVMTrain(SVMCallback callback){ 27 | this.callback = callback; 28 | } 29 | 30 | public SVMTrain(){ 31 | 32 | this.learn2HasPlate(); 33 | this.learn2NoPlate(); 34 | } 35 | 36 | 37 | private void learn2Plate(float bound, final String name) { 38 | final String filePath = "res/train/data/plate_detect_svm/learn/" + name; 39 | Vector files = new Vector(); 40 | ////获取该路径下的所有文件 41 | Util.getFiles(filePath, files); 42 | int size = files.size(); 43 | if (0 == size) { 44 | System.out.println("File not found in " + filePath); 45 | return; 46 | } 47 | Collections.shuffle(files, new Random(new Date().getTime())); 48 | ////随机选取70%作为训练数据,30%作为测试数据 49 | int boundry = (int) (bound * size); 50 | 51 | Util.recreateDir("res/train/data/plate_detect_svm/train/" + name); 52 | Util.recreateDir("res/train/data/plate_detect_svm/test/" + name); 53 | 54 | System.out.println("Save " + name + " train!"); 55 | for (int i = 0; i < boundry; i++) { 56 | System.out.println(files.get(i)); 57 | Mat img = imread(files.get(i)); 58 | String str = "res/train/data/plate_detect_svm/train/" + name + "/" + name + "_" + Integer.valueOf(i).toString() + ".jpg"; 59 | imwrite(str, img); 60 | } 61 | 62 | System.out.println("Save " + name + " test!"); 63 | for (int i = boundry; i < size; i++) { 64 | System.out.println(files.get(i)); 65 | Mat img = imread(files.get(i)); 66 | String str = "res/train/data/plate_detect_svm/test/" + name + "/" + name + "_" + Integer.valueOf(i).toString() + ".jpg"; 67 | imwrite(str, img); 68 | } 69 | } 70 | 71 | private void getPlateTrain(Mat trainingImages, Vector trainingLabels, final String name) { 72 | int label = 1; 73 | final String filePath = "res/train/data/plate_detect_svm/train/" + name; 74 | Vector files = new Vector(); 75 | 76 | ////获取该路径下的所有文件 77 | Util.getFiles(filePath, files); 78 | 79 | int size = files.size(); 80 | if (0 == size) { 81 | System.out.println("File not found in " + filePath); 82 | return; 83 | } 84 | System.out.println("get " + name + " train!"); 85 | for (int i = 0; i < size; i++) { 86 | //System.out.println(files[i].c_str()).toString()); 87 | Mat img = imread(files.get(i)); 88 | 89 | //调用回调函数决定特征 90 | Mat features = this.callback.getHisteqFeatures(img); 91 | features = features.reshape(0,0); 92 | trainingImages.push_back(features); 93 | trainingLabels.add(label); 94 | } 95 | } 96 | 97 | private void getPlateTest(MatVector testingImages,Vector testingLabels,final String name){ 98 | int label = 1; 99 | final String filePath = "res/train/data/plate_detect_svm/test/"+name; 100 | Vector files = new Vector(); 101 | Util.getFiles(filePath, files); 102 | 103 | int size = files.size(); 104 | if (0 == size) { 105 | System.out.println("File not found in " + filePath); 106 | return; 107 | } 108 | System.out.println("get "+name+" test!"); 109 | for (int i = 0; i < size; i++) 110 | { 111 | Mat img = imread(files.get(i)); 112 | testingImages.put(img); 113 | testingLabels.add(label); 114 | } 115 | } 116 | 117 | public void learn2HasPlate() { 118 | learn2HasPlate(0.7f); 119 | } 120 | 121 | public void learn2HasPlate(float bound) { 122 | learn2Plate(bound, hasPlate); 123 | } 124 | 125 | public void learn2NoPlate() { 126 | learn2NoPlate(0.7f); 127 | } 128 | 129 | public void learn2NoPlate(float bound) { 130 | learn2Plate(bound, noPlate); 131 | } 132 | 133 | 134 | public void getNoPlateTrain(Mat trainingImages, Vector trainingLabels) { 135 | getPlateTrain(trainingImages, trainingLabels, noPlate); 136 | } 137 | 138 | public void getHasPlateTrain(Mat trainingImages, Vector trainingLabels) { 139 | getPlateTrain(trainingImages, trainingLabels, hasPlate); 140 | } 141 | 142 | 143 | public void getHasPlateTest(MatVector testingImages,Vector testingLabels) 144 | { 145 | getPlateTest(testingImages,testingLabels,hasPlate); 146 | } 147 | 148 | public void getNoPlateTest(MatVector testingImages,Vector testingLabels) 149 | { 150 | getPlateTest(testingImages,testingLabels,noPlate); 151 | } 152 | 153 | 154 | 155 | //! 测试SVM的准确率,回归率以及FScore 156 | public void getAccuracy(Mat testingclasses_preditc, Mat testingclasses_real) 157 | { 158 | int channels = testingclasses_preditc.channels(); 159 | System.out.println("channels: "+Integer.valueOf(channels).toString()); 160 | int nRows = testingclasses_preditc.rows(); 161 | System.out.println("nRows: "+Integer.valueOf(nRows).toString()); 162 | int nCols = testingclasses_preditc.cols() * channels; 163 | System.out.println("nCols: "+Integer.valueOf(nCols).toString()); 164 | int channels_real = testingclasses_real.channels(); 165 | System.out.println("channels_real: "+Integer.valueOf(channels_real).toString()); 166 | int nRows_real = testingclasses_real.rows(); 167 | System.out.println("nRows_real: " + Integer.valueOf(nRows_real).toString()); 168 | int nCols_real = testingclasses_real.cols() * channels; 169 | System.out.println("nCols_real: "+Integer.valueOf(nCols_real).toString()); 170 | 171 | double count_all = 0; 172 | double ptrue_rtrue = 0; 173 | double ptrue_rfalse = 0; 174 | double pfalse_rtrue = 0; 175 | double pfalse_rfalse = 0; 176 | 177 | for (int i = 0; i < nRows; i++) 178 | { 179 | 180 | final float predict = Convert.toFloat(testingclasses_preditc.ptr(i)); 181 | final float real = Convert.toFloat(testingclasses_real.ptr(i)); 182 | 183 | count_all ++; 184 | 185 | //System.out.println("predict:" << predict).toString()); 186 | //System.out.println("real:" << real).toString()); 187 | 188 | if (predict == 1.0 && real == 1.0) 189 | ptrue_rtrue ++; 190 | if (predict == 1.0 && real == 0) 191 | ptrue_rfalse ++; 192 | if (predict == 0 && real == 1.0) 193 | pfalse_rtrue ++; 194 | if (predict == 0 && real == 0) 195 | pfalse_rfalse ++; 196 | } 197 | 198 | System.out.println("count_all: "+Double.valueOf(count_all).toString()); 199 | System.out.println("ptrue_rtrue: "+Double.valueOf(ptrue_rtrue).toString()); 200 | System.out.println("ptrue_rfalse: "+Double.valueOf(ptrue_rfalse).toString()); 201 | System.out.println("pfalse_rtrue: "+Double.valueOf(pfalse_rtrue).toString()); 202 | System.out.println("pfalse_rfalse: "+Double.valueOf(pfalse_rfalse).toString()); 203 | 204 | double precise = 0; 205 | if (ptrue_rtrue + ptrue_rfalse != 0) 206 | { 207 | precise = ptrue_rtrue/(ptrue_rtrue + ptrue_rfalse); 208 | System.out.println("precise: "+Double.valueOf(precise).toString()); 209 | } 210 | else 211 | { 212 | System.out.println("precise: NA"); 213 | } 214 | 215 | double recall = 0; 216 | if (ptrue_rtrue + pfalse_rtrue != 0) 217 | { 218 | recall = ptrue_rtrue/(ptrue_rtrue + pfalse_rtrue); 219 | System.out.println("recall: "+Double.valueOf(recall).toString()); 220 | } 221 | else 222 | { 223 | System.out.println("recall: NA"); 224 | } 225 | 226 | if (precise + recall != 0) 227 | { 228 | double F = (precise * recall)/(precise + recall); 229 | System.out.println("F: "+Double.valueOf(F).toString()); 230 | } 231 | else 232 | { 233 | System.out.println("F: NA"); 234 | } 235 | } 236 | 237 | 238 | public int svmTrain(boolean dividePrepared, boolean trainPrepared) 239 | { 240 | 241 | Mat classes = new Mat(); 242 | Mat trainingData = new Mat(); 243 | 244 | Mat trainingImages = new Mat(); 245 | Vector trainingLabels = new Vector(); 246 | 247 | 248 | if (!dividePrepared) 249 | { 250 | //分割learn里的数据到train和test里 251 | System.out.println("Divide learn to train and test"); 252 | learn2HasPlate(); 253 | learn2NoPlate(); 254 | } 255 | 256 | //将训练数据加载入内存 257 | if (!trainPrepared) 258 | { 259 | System.out.print("Begin to get train data to memory"); 260 | getHasPlateTrain(trainingImages, trainingLabels); 261 | getNoPlateTrain(trainingImages, trainingLabels); 262 | 263 | 264 | trainingImages.copyTo(trainingData); 265 | trainingData.convertTo(trainingData, CV_32FC1); 266 | 267 | int []labels = new int[trainingLabels.size()]; 268 | for(int i=0;i testingLabels_real = new Vector(); 276 | 277 | //将测试数据加载入内存 278 | System.out.println("Begin to get test data to memory"); 279 | getHasPlateTest(testingImages, testingLabels_real); 280 | getNoPlateTest(testingImages, testingLabels_real); 281 | 282 | CvSVM svm = new CvSVM(); 283 | if (!trainPrepared && !classes.empty() && !trainingData.empty()) 284 | { 285 | CvSVMParams SVM_params = new CvSVMParams(CvSVM.C_SVC,CvSVM.RBF,0.1,1,0.1,1,0.1,0.1, 286 | new CvMat(),new CvTermCriteria().type(CV_TERMCRIT_ITER).max_iter(100000).epsilon(0.0001)); 287 | 288 | //Train SVM 289 | System.out.println("Begin to generate svm"); 290 | 291 | try { 292 | //CvSVM svm(trainingData, classes, Mat(), Mat(), SVM_params); 293 | svm.train_auto(trainingData, classes, new Mat(), new Mat(), SVM_params, 10, 294 | CvSVM.get_default_grid(CvSVM.C), 295 | CvSVM.get_default_grid(CvSVM.GAMMA), 296 | CvSVM.get_default_grid(CvSVM.P), 297 | CvSVM.get_default_grid(CvSVM.NU), 298 | CvSVM.get_default_grid(CvSVM.COEF), 299 | CvSVM.get_default_grid(CvSVM.DEGREE), 300 | true); 301 | } catch (Exception err) { 302 | System.out.println(err.getMessage()); 303 | } 304 | 305 | System.out.println("Svm generate done!"); 306 | 307 | CvFileStorage fsTo = CvFileStorage.open("res/rain/svm.xml", CvMemStorage.create(),CV_STORAGE_WRITE); 308 | svm.write(fsTo, "svm"); 309 | } 310 | else 311 | { 312 | try { 313 | String path = "res/train/svm.xml"; 314 | svm.load(path, "svm"); 315 | } catch (Exception err) { 316 | System.out.println(err.getMessage()); 317 | return 0; //next predict requires svm 318 | } 319 | } 320 | 321 | System.out.println("Begin to predict"); 322 | 323 | double count_all = 0; 324 | double ptrue_rtrue = 0; 325 | double ptrue_rfalse = 0; 326 | double pfalse_rtrue = 0; 327 | double pfalse_rfalse = 0; 328 | 329 | int size = (int)testingImages.size(); 330 | for (int i = 0; i < size; i++) 331 | { 332 | //System.out.println(files[i].c_str()); 333 | Mat p = testingImages.get(i); 334 | 335 | //调用回调函数决定特征 336 | Mat features = callback.getHistogramFeatures(p); 337 | features = features.reshape(1, 1); 338 | features.convertTo(features, CV_32FC1); 339 | 340 | int predict = (int)svm.predict(features); 341 | int real = testingLabels_real.get(i); 342 | 343 | if (predict == 1 && real == 1) 344 | ptrue_rtrue ++; 345 | if (predict == 1 && real == 0) 346 | ptrue_rfalse ++; 347 | if (predict == 0 && real == 1) 348 | pfalse_rtrue ++; 349 | if (predict == 0 && real == 0) 350 | pfalse_rfalse ++; 351 | } 352 | 353 | count_all = size; 354 | 355 | System.out.println("Get the Accuracy!"); 356 | 357 | System.out.println("count_all: "+Double.valueOf(count_all).toString()); 358 | System.out.println("ptrue_rtrue: "+Double.valueOf(ptrue_rtrue).toString()); 359 | System.out.println("ptrue_rfalse: "+Double.valueOf(ptrue_rfalse).toString()); 360 | System.out.println("pfalse_rtrue: "+Double.valueOf(pfalse_rtrue).toString()); 361 | System.out.println("pfalse_rfalse: "+Double.valueOf(pfalse_rfalse).toString()); 362 | 363 | double precise = 0; 364 | if (ptrue_rtrue + ptrue_rfalse != 0) 365 | { 366 | precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse); 367 | System.out.println("precise: "+Double.valueOf(precise).toString()); 368 | } 369 | else 370 | System.out.println("precise: NA"); 371 | 372 | double recall = 0; 373 | if (ptrue_rtrue + pfalse_rtrue != 0) 374 | { 375 | recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue); 376 | System.out.println("recall: "+Double.valueOf(recall).toString()); 377 | } 378 | else 379 | System.out.println("recall: NA"); 380 | 381 | double Fsocre = 0; 382 | if (precise + recall != 0) 383 | { 384 | Fsocre = 2 * (precise * recall) / (precise + recall); 385 | System.out.println("Fsocre: "+Double.valueOf(Fsocre).toString()); 386 | } 387 | else 388 | System.out.println("Fsocre: NA"); 389 | return 0; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/org/easypr/util/Convert.java: -------------------------------------------------------------------------------- 1 | package org.easypr.util; 2 | 3 | import org.bytedeco.javacpp.BytePointer; 4 | 5 | /** 6 | * There are 3 kinds of convert functions: 7 | * 1. [float|double|int|long] to[Float|Double|Int|Long](BytePointer pointer) 8 | * 2. byte[] getBytes([float|double|int|long] value) 9 | * 3. [float|double|int|long] to[Float|Double|Int|Long](byte[] value) 10 | * 11 | * @author lin.yao 12 | * 13 | */ 14 | public class Convert { 15 | 16 | public static float toFloat(BytePointer pointer) { 17 | byte[] buffer = new byte[4]; 18 | pointer.get(buffer); 19 | return toFloat(buffer); 20 | } 21 | 22 | public static double toDouble(BytePointer pointer) { 23 | byte[] buffer = new byte[8]; 24 | pointer.get(buffer); 25 | return toDouble(buffer); 26 | } 27 | 28 | public static int toInt(BytePointer pointer) { 29 | byte[] buffer = new byte[4]; 30 | pointer.get(buffer); 31 | return toInt(buffer); 32 | } 33 | 34 | public static long toLong(BytePointer pointer) { 35 | byte[] buffer = new byte[8]; 36 | pointer.get(buffer); 37 | return toLong(buffer); 38 | } 39 | 40 | public static byte[] getBytes(float value) { 41 | return getBytes(Float.floatToIntBits(value)); 42 | } 43 | 44 | public static byte[] getBytes(double value) { 45 | return getBytes(Double.doubleToLongBits(value)); 46 | } 47 | 48 | public static byte[] getBytes(int value) { 49 | final int length = 4; 50 | byte[] buffer = new byte[length]; 51 | for (int i = 0; i < length; ++i) 52 | buffer[i] = (byte) ((value >> (i * 8)) & 0xFF); 53 | return buffer; 54 | } 55 | 56 | public static byte[] getBytes(long value) { 57 | final int length = 8; 58 | byte[] buffer = new byte[length]; 59 | for (int i = 0; i < length; ++i) 60 | buffer[i] = (byte) ((value >> (i * 8)) & 0xFF); 61 | return buffer; 62 | } 63 | 64 | public static int toInt(byte[] value) { 65 | final int length = 4; 66 | int n = 0; 67 | for (int i = 0; i < length; ++i) 68 | n += (value[i] & 0xFF) << (i * 8); 69 | return n; 70 | } 71 | 72 | public static long toLong(byte[] value) { 73 | final int length = 8; 74 | long n = 0; 75 | for (int i = 0; i < length; ++i) 76 | n += ((long) (value[i] & 0xFF)) << (i * 8); 77 | return n; 78 | } 79 | 80 | public static double toDouble(byte[] value) { 81 | return Double.longBitsToDouble(toLong(value)); 82 | } 83 | 84 | public static float toFloat(byte[] value) { 85 | return Float.intBitsToFloat(toInt(value)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/org/easypr/util/Util.java: -------------------------------------------------------------------------------- 1 | package org.easypr.util; 2 | 3 | import java.io.File; 4 | import java.util.Vector; 5 | 6 | 7 | /** 8 | * @author lin.yao 9 | * 10 | */ 11 | public class Util { 12 | 13 | /** 14 | * get all files under the directory path 15 | * 16 | * @param path 17 | * @param files 18 | */ 19 | public static void getFiles(final String path, Vector files) { 20 | getFiles(new File(path), files); 21 | } 22 | 23 | /** 24 | * delete and create a new directory with the same name 25 | * 26 | * @param dir 27 | */ 28 | public static void recreateDir(final String dir) { 29 | new File(dir).delete(); 30 | new File(dir).mkdir(); 31 | } 32 | 33 | private static void getFiles(final File dir, Vector files) { 34 | File[] filelist = dir.listFiles(); 35 | for (File file : filelist) { 36 | if (file.isDirectory()) { 37 | getFiles(file, files); 38 | } else { 39 | files.add(file.getAbsolutePath()); 40 | } 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------