no commit message

devA
yuxue 5 years ago
parent 7d11d901a8
commit 2d0ba56295

@ -0,0 +1,159 @@
package com.yuxue.train;
import java.util.Vector;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_ml.*;
import org.bytedeco.javacpp.opencv_imgcodecs;
import org.bytedeco.javacpp.opencv_core.Mat;
import com.yuxue.constant.Constant;
import com.yuxue.easypr.core.CoreFunc;
import com.yuxue.util.FileUtil;
/**
* org.bytedeco.javacpp
*
*
*
*
* ann.xml
* 1res/model/ann.xml
* 2com.yuxue.easypr.core.CharsIdentify.charsIdentify(Mat, Boolean, Boolean)
*
* @author yuxue
* @date 2020-05-14 22:16
*/
public class ANNTrain1 {
private ANN_MLP ann = ANN_MLP.create();
// 默认的训练操作的根目录
private static final String DEFAULT_PATH = "D:/PlateDetect/train/chars_recognise_ann/";
// 训练模型文件保存位置
private static final String MODEL_PATH = "res/model/ann.xml";
public void train(int _predictsize, int _neurons) {
Mat samples = new Mat(); // 使用push_back行数列数不能赋初始值
Vector<Integer> trainingLabels = new Vector<Integer>();
// 加载数字及字母字符
for (int i = 0; i < Constant.numCharacter; i++) {
String str = DEFAULT_PATH + "learn/" + Constant.strCharacters[i];
Vector<String> files = new Vector<String>();
FileUtil.getFiles(str, files);
int size = (int) files.size();
for (int j = 0; j < size; j++) {
Mat img = opencv_imgcodecs.imread(files.get(j), 0);
// System.err.println(files.get(j)); // 文件名不能包含中文
Mat f = CoreFunc.features(img, _predictsize);
samples.push_back(f);
trainingLabels.add(i); // 每一幅字符图片所对应的字符类别索引下标
}
}
// 加载汉字字符
for (int i = 0; i < Constant.strChinese.length; i++) {
String str = DEFAULT_PATH + "learn/" + Constant.strChinese[i];
Vector<String> files = new Vector<String>();
FileUtil.getFiles(str, files);
int size = (int) files.size();
for (int j = 0; j < size; j++) {
Mat img = opencv_imgcodecs.imread(files.get(j), 0);
// System.err.println(files.get(j)); // 文件名不能包含中文
Mat f = CoreFunc.features(img, _predictsize);
samples.push_back(f);
trainingLabels.add(i + Constant.numCharacter);
}
}
//440 vhist.length + hhist.length + lowData.cols() * lowData.rows();
// CV_32FC1 CV_32SC1 CV_32F
Mat classes = new Mat(trainingLabels.size(), Constant.numAll, CV_32F);
float[] labels = new float[trainingLabels.size()];
for (int i = 0; i < labels.length; ++i) {
classes.ptr(i, trainingLabels.get(i)).putFloat(1.f);
}
// samples.type() == CV_32F || samples.type() == CV_32S
TrainData train_data = TrainData.create(samples, ROW_SAMPLE, classes);
ann.clear();
Mat layers = new Mat(1, 3, CV_32SC1);
layers.ptr(0, 0).putInt(samples.cols());
layers.ptr(0, 1).putInt(_neurons);
layers.ptr(0, 2).putInt(classes.cols());
System.out.println(layers);
ann.setLayerSizes(layers);
ann.setActivationFunction(ANN_MLP.SIGMOID_SYM, 1, 1);
ann.setTrainMethod(ANN_MLP.BACKPROP);
TermCriteria criteria = new TermCriteria(TermCriteria.EPS + TermCriteria.MAX_ITER, 30000, 0.0001);
ann.setTermCriteria(criteria);
ann.setBackpropWeightScale(0.1);
ann.setBackpropMomentumScale(0.1);
ann.train(train_data);
//FileStorage fsto = new FileStorage(MODEL_PATH, FileStorage.WRITE);
//ann.write(fsto, "ann");
ann.save(MODEL_PATH);
}
public void predict() {
ann.clear();
ann = ANN_MLP.load(MODEL_PATH);
//ann = ANN_MLP.loadANN_MLP(MODEL_PATH, "ann");
Vector<String> files = new Vector<String>();
FileUtil.getFiles(DEFAULT_PATH + "test/", files);
for (String string : files) {
Mat img = opencv_imgcodecs.imread(string);
Mat f = CoreFunc.features(img, Constant.predictSize);
// 140 predictSize = 10; vhist.length + hhist.length + lowData.cols() * lowData.rows();
// 440 predictSize = 20;
Mat output = new Mat(1, 140, CV_32F);
//ann.predict(f, output, 0); // 预测结果
// System.err.println(string + "===>" + (int) ann.predict(f, output, 0));
int index = (int) ann.predict(f, output, 0);
String result = "";
if (index < Constant.numCharacter) {
result = String.valueOf(Constant.strCharacters[index]);
} else {
String s = Constant.strChinese[index - Constant.numCharacter];
result = Constant.KEY_CHINESE_MAP.get(s); // 编码转中文
}
System.err.println(string + "===>" + result);
// ann.predict(f, output, 0);
// System.err.println(string + "===>" + output.get(0, 0)[0]);
}
}
public static void main(String[] args) {
ANNTrain1 annT = new ANNTrain1();
// 这里演示只训练model文件夹下的ann.xml此模型是一个predictSize=10,neurons=40的ANN模型
// 可根据需要训练不同的predictSize或者neurons的ANN模型
// 根据机器的不同训练时间不一样但一般需要10分钟左右所以慢慢等一会吧。
annT.train(Constant.predictSize, Constant.neurons);
annT.predict();
System.out.println("The end.");
}
}

@ -0,0 +1,83 @@
package com.yuxue.easypr.core;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_ml.ANN_MLP;
import com.yuxue.constant.Constant;
import com.yuxue.util.Convert;
/**
*
* @author yuxue
* @date 2020-04-24 15:31
*/
public class CharsIdentify {
private ANN_MLP ann=ANN_MLP.create();
public CharsIdentify() {
loadModel(Constant.DEFAULT_ANN_PATH);
}
public void loadModel(String path) {
this.ann.clear();
// 加载ann配置文件 图像转文字的训练库文件
//ann=ANN_MLP.loadANN_MLP(path, "ann");
ann = ANN_MLP.load(path);
}
/**
* @param input
* @param isChinese
* @return
*/
public String charsIdentify(final Mat input, final Boolean isChinese, final Boolean isSpeci) {
String result = "";
/*String name = "D:/PlateDetect/train/chars_recognise_ann/" + System.currentTimeMillis() + ".jpg";
opencv_imgcodecs.imwrite(name, input);
Mat img = opencv_imgcodecs.imread(name);
Mat f = CoreFunc.features(img, Constant.predictSize);*/
Mat f = CoreFunc.features(input, Constant.predictSize);
int index = this.classify(f, isChinese, isSpeci);
System.err.print(index);
if (index < Constant.numCharacter) {
result = String.valueOf(Constant.strCharacters[index]);
} else {
String s = Constant.strChinese[index - Constant.numCharacter];
result = Constant.KEY_CHINESE_MAP.get(s); // 编码转中文
}
System.err.println(result);
return result;
}
private int classify(final Mat f, final Boolean isChinses, final Boolean isSpeci) {
int result = -1;
Mat output = new Mat(1, 140, opencv_core.CV_32F);
ann.predict(f, output, 0); // 预测结果
int ann_min = (!isChinses) ? ((isSpeci) ? 10 : 0) : Constant.numCharacter;
int ann_max = (!isChinses) ? Constant.numCharacter : Constant.numAll;
float maxVal = -2;
for (int j = ann_min; j < ann_max; j++) {
float val = Convert.toFloat(output.ptr(0, j));
if (val > maxVal) {
maxVal = val;
result = j;
}
}
return result;
}
}

@ -0,0 +1,136 @@
package com.yuxue.easypr.core;
import java.util.Vector;
import org.bytedeco.javacpp.opencv_core.Mat;
import com.yuxue.enumtype.PlateColor;
/**
*
*
* @author yuxue
* @date 2020-04-24 15:31
*/
public class CharsRecognise {
private CharsSegment charsSegment = new CharsSegment();
private CharsIdentify charsIdentify = new CharsIdentify();
public void loadANN(final String s) {
charsIdentify.loadModel(s);
}
/**
* Chars segment and identify
*
* @param plate the input plate
* @return the result of plate recognition
*/
public String charsRecognise(final Mat plate, String tempPath) {
// 车牌字符方块集合
Vector<Mat> matVec = new Vector<Mat>();
// 车牌识别结果
String plateIdentify = "";
int result = charsSegment.charsSegment(plate, matVec, tempPath);
if (0 == result) {
for (int j = 0; j < matVec.size(); j++) {
Mat charMat = matVec.get(j);
// 默认首个字符块是中文字符 第二个字符块是字母
String charcater = charsIdentify.charsIdentify(charMat, (0 == j), (1 == j));
plateIdentify = plateIdentify + charcater;
}
}
return plateIdentify;
}
/**
*
*
* @param isDebug
*/
public void setCRDebug(final boolean isDebug) {
charsSegment.setDebug(isDebug);
}
/**
*
*
* @return
*/
public boolean getCRDebug() {
return charsSegment.getDebug();
}
/**
*
*
* @param input
* @return
*/
public final String getPlateType(final Mat input) {
PlateColor result = CoreFunc.getPlateType(input, true);
return result.desc;
}
/**
*
*
* @param param
*/
public void setLiuDingSize(final int param) {
charsSegment.setLiuDingSize(param);
}
/**
*
*
* @param param
*/
public void setColorThreshold(final int param) {
charsSegment.setColorThreshold(param);
}
/**
*
*
* @param param
*/
public void setBluePercent(final float param) {
charsSegment.setBluePercent(param);
}
/**
*
*
* @param param
*/
public final float getBluePercent() {
return charsSegment.getBluePercent();
}
/**
*
*
* @param param
*/
public void setWhitePercent(final float param) {
charsSegment.setWhitePercent(param);
}
/**
*
*
* @param param
*/
public final float getWhitePercent() {
return charsSegment.getWhitePercent();
}
}

@ -0,0 +1,453 @@
package com.yuxue.easypr.core;
import static com.yuxue.easypr.core.CoreFunc.getPlateType;
import static org.bytedeco.javacpp.opencv_core.CV_32F;
import static org.bytedeco.javacpp.opencv_core.countNonZero;
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.CV_RGB2GRAY;
import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_BINARY;
import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_BINARY_INV;
import static org.bytedeco.javacpp.opencv_imgproc.CV_THRESH_OTSU;
import static org.bytedeco.javacpp.opencv_imgproc.INTER_LINEAR;
import static org.bytedeco.javacpp.opencv_imgproc.boundingRect;
import static org.bytedeco.javacpp.opencv_imgproc.cvtColor;
import static org.bytedeco.javacpp.opencv_imgproc.findContours;
import static org.bytedeco.javacpp.opencv_imgproc.resize;
import static org.bytedeco.javacpp.opencv_imgproc.threshold;
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_imgcodecs;
import com.yuxue.enumtype.PlateColor;
import com.yuxue.util.Convert;
/**
*
* @author yuxue
* @date 2020-04-28 09:45
*/
public class CharsSegment {
// preprocessChar所用常量
final static int CHAR_SIZE = 20;
final static int HORIZONTAL = 1;
final static int VERTICAL = 0;
final static int DEFAULT_LIUDING_SIZE = 7;
final static int DEFAULT_MAT_WIDTH = 136;
final static int DEFAULT_COLORTHRESHOLD = 150;
final static float DEFAULT_BLUEPERCEMT = 0.3f;
final static float DEFAULT_WHITEPERCEMT = 0.1f;
private int liuDingSize = DEFAULT_LIUDING_SIZE;
private int theMatWidth = DEFAULT_MAT_WIDTH;
private int colorThreshold = DEFAULT_COLORTHRESHOLD;
private float bluePercent = DEFAULT_BLUEPERCEMT;
private float whitePercent = DEFAULT_WHITEPERCEMT;
private boolean isDebug = true;
/**
*
*
* @param input
* @param resultVec
* @return <ul>
* <li>more than zero: the number of chars;
* <li>-3: null;
* </ul>
*/
public int charsSegment(final Mat input, Vector<Mat> resultVec, String tempPath) {
if (input.data().isNull()) {
return -3;
}
// 判断车牌颜色以此确认threshold方法
Mat img_threshold = new Mat();
Mat input_grey = new Mat();
cvtColor(input, input_grey, CV_RGB2GRAY);
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)));
PlateColor color= getPlateType(tmpMat, true);
switch (color) {
case BLUE:
threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
break;
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(tempPath + "debug_char_threshold.jpg", img_threshold);
}
// 去除车牌上方的柳钉以及下方的横线等干扰 //会导致虚拟机崩溃
// clearLiuDing(img_threshold);
/*if (this.isDebug) {
String str = tempPath + "debug_char_clearLiuDing.jpg";
opencv_imgcodecs.imwrite(str, img_threshold);
}*/
// 找轮廓
Mat img_contours = new Mat();
img_threshold.copyTo(img_contours);
MatVector contours = new MatVector();
findContours(img_contours, contours, // a vector of contours
CV_RETR_EXTERNAL, // retrieve the external contours
CV_CHAIN_APPROX_NONE); // all pixels of each contours
// Remove patch that are no inside limits of aspect ratio and area.
// 将不符合特定尺寸的图块排除出去
Vector<Rect> vecRect = new Vector<Rect>();
for (int i = 0; i < contours.size(); ++i) {
Rect mr = boundingRect(contours.get(i));
Mat contour = new Mat(img_threshold, mr);
if (this.isDebug) {
String str = tempPath + "debug_char_contour"+i+".jpg";
opencv_imgcodecs.imwrite(str, contour);
}
if (verifySizes(contour)) { // 将不符合特定尺寸的图块排除出去
vecRect.add(mr);
}
}
if (vecRect.size() == 0) {
return -3;
}
Vector<Rect> sortedRect = new Vector<Rect>();
// 对符合尺寸的图块按照从左到右进行排序
SortRect(vecRect, sortedRect);
// 获得指示城市的特定Rect,如苏A的"A"
int specIndex = GetSpecificRect(sortedRect, color);
if (this.isDebug) {
if (specIndex < sortedRect.size()) {
Mat specMat = new Mat(img_threshold, sortedRect.get(specIndex));
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));
} else {
return -3;
}
if (this.isDebug) {
Mat chineseMat = new Mat(img_threshold, chineseRect);
String str = tempPath + "debug_chineseMat.jpg";
opencv_imgcodecs.imwrite(str, chineseMat);
}
// 新建一个全新的排序Rect
// 将中文字符Rect第一个加进来因为它肯定是最左边的
// 其余的Rect只按照顺序去6个车牌只可能是7个字符这样可以避免阴影导致的“1”字符
Vector<Rect> newSortedRect = new Vector<Rect>();
newSortedRect.add(chineseRect);
RebuildRect(sortedRect, newSortedRect, specIndex, color);
if (newSortedRect.size() == 0) {
return -3;
}
for (int i = 0; i < newSortedRect.size(); i++) {
Rect mr = newSortedRect.get(i);
Mat auxRoi = new Mat(img_threshold, mr);
auxRoi = preprocessChar(auxRoi);
if (this.isDebug) {
String str = tempPath + "debug_char_auxRoi_" + Integer.valueOf(i).toString() + ".jpg";
opencv_imgcodecs.imwrite(str, auxRoi);
}
resultVec.add(auxRoi);
}
return 0;
}
/**
*
* @param r
* @return
*/
public static Boolean verifySizes(Mat r) {
float aspect = 45.0f / 90.0f;
float charAspect = (float) r.cols() / (float) r.rows();
float error = 0.7f;
float minHeight = 10f;
float maxHeight = 35f;
// We have a different aspect ratio for number 1, and it can be ~0.2
float minAspect = 0.05f;
float maxAspect = aspect + aspect * error;
// area of pixels
float area = countNonZero(r);
// bb area
float bbArea = r.cols() * r.rows();
// % of pixel in area
float percPixels = area / bbArea;
return percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && r.rows() >= minHeight && r.rows() < maxHeight;
}
/**
* :
*
* @param in
* @return
*/
private Mat preprocessChar(Mat in) {
int h = in.rows();
int w = in.cols();
int charSize = CHAR_SIZE;
Mat transformMat = Mat.eye(2, 3, CV_32F).asMat();
int m = Math.max(w, h);
transformMat.ptr(0, 2).put(Convert.getBytes(((m - w) / 2f)));
transformMat.ptr(1, 2).put(Convert.getBytes((m - h) / 2f));
Mat warpImage = new Mat(m, m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, opencv_core.BORDER_CONSTANT, new Scalar(0));
Mat out = new Mat();
resize(warpImage, out, new Size(charSize, charSize));
return out;
}
/**
*
* <p>
* X0 X
*
* @param img
* @return
*/
private Mat clearLiuDing(Mat img) {
final int x = this.liuDingSize;
Mat jump = Mat.zeros(1, img.rows(), CV_32F).asMat();
for (int i = 0; i < img.rows(); i++) {
int jumpCount = 0;
for (int j = 0; j < img.cols() - 1; j++) {
if (img.ptr(i, j).get() != img.ptr(i, j + 1).get())
jumpCount++;
}
jump.ptr(i).put(Convert.getBytes((float) jumpCount));
}
for (int i = 0; i < img.rows(); i++) {
if (Convert.toFloat(jump.ptr(i)) <= x) {
for (int j = 0; j < img.cols(); j++) {
img.ptr(i, j).put((byte) 0);
}
}
}
return img;
}
/**
*
*
* @param rectSpe
* @return
*/
private Rect GetChineseRect(final Rect rectSpe) {
int height = rectSpe.height();
float newwidth = rectSpe.width() * 1.15f;
int x = rectSpe.x();
int y = rectSpe.y();
int newx = x - (int) (newwidth * 1.15);
newx = Math.max(newx, 0);
Rect a = new Rect(newx, y, (int) newwidth, height);
return a;
}
/**
* RectA7003XA
*
* @param vecRect
* @return
*/
private int GetSpecificRect(final Vector<Rect> vecRect, PlateColor color) {
Vector<Integer> xpositions = new Vector<Integer>();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < vecRect.size(); i++) {
xpositions.add(vecRect.get(i).x());
if (vecRect.get(i).height() > maxHeight) {
maxHeight = vecRect.get(i).height();
}
if (vecRect.get(i).width() > maxWidth) {
maxWidth = vecRect.get(i).width();
}
}
int specIndex = 0;
for (int i = 0; i < vecRect.size(); i++) {
Rect mr = vecRect.get(i);
int midx = mr.x() + mr.width() / 2;
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;
}
}
}
return specIndex;
}
/**
*
* <ul>
* <li>RectRect;
* <li>Rect6Rect
* <ul>
*
* @param vecRect
* @param outRect
* @param specIndex
* @return
*/
private int RebuildRect(final Vector<Rect> vecRect, Vector<Rect> 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)
continue;
outRect.add(vecRect.get(i));
if (--count == 0)
break;
}
return 0;
}
/**
* Rect
*
* @param vecRect
* @param out
* @return
*/
public static void SortRect(final Vector<Rect> vecRect, Vector<Rect> out) {
Vector<Integer> orderIndex = new Vector<Integer>();
Vector<Integer> xpositions = new Vector<Integer>();
for (int i = 0; i < vecRect.size(); ++i) {
orderIndex.add(i);
xpositions.add(vecRect.get(i).x());
}
float min = xpositions.get(0);
int minIdx;
for (int i = 0; i < xpositions.size(); ++i) {
min = xpositions.get(i);
minIdx = i;
for (int j = i; j < xpositions.size(); ++j) {
if (xpositions.get(j) < min) {
min = xpositions.get(j);
minIdx = j;
}
}
int aux_i = orderIndex.get(i);
int aux_min = orderIndex.get(minIdx);
orderIndex.remove(i);
orderIndex.insertElementAt(aux_min, i);
orderIndex.remove(minIdx);
orderIndex.insertElementAt(aux_i, minIdx);
float aux_xi = xpositions.get(i);
float aux_xmin = xpositions.get(minIdx);
xpositions.remove(i);
xpositions.insertElementAt((int) aux_xmin, i);
xpositions.remove(minIdx);
xpositions.insertElementAt((int) aux_xi, minIdx);
}
for (int i = 0; i < orderIndex.size(); i++)
out.add(vecRect.get(orderIndex.get(i)));
return;
}
public void setLiuDingSize(int param) {
this.liuDingSize = param;
}
public void setColorThreshold(int param) {
this.colorThreshold = param;
}
public void setBluePercent(float param) {
this.bluePercent = param;
}
public final float getBluePercent() {
return this.bluePercent;
}
public void setWhitePercent(float param) {
this.whitePercent = param;
}
public final float getWhitePercent() {
return this.whitePercent;
}
public boolean getDebug() {
return this.isDebug;
}
public void setDebug(boolean isDebug) {
this.isDebug = isDebug;
}
}

@ -0,0 +1,269 @@
package com.yuxue.easypr.core;
import org.bytedeco.javacpp.BytePointer;
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.Size;
import org.bytedeco.javacpp.opencv_highgui;
import org.bytedeco.javacpp.opencv_imgproc;
import org.bytedeco.javacpp.indexer.FloatIndexer;
import com.yuxue.enumtype.Direction;
import com.yuxue.enumtype.PlateColor;
/**
*
* @author yuxue
* @date 2020-05-16 21:09
*/
public class CoreFunc {
/**
*
*
* @param src
* RGB
* @param r
*
* @param adaptive_minsv
* SVadaptive_minsvbool
* <ul>
* <li>trueH
* <li>false使minabs_sv
* </ul>
* @return 02552550
*/
public static Mat colorMatch(final Mat src, final PlateColor r, final boolean adaptive_minsv) {
final float max_sv = 255;
final float minref_sv = 64;
final float minabs_sv = 95;
// 转到HSV空间进行处理颜色搜索主要使用的是H分量进行蓝色与黄色的匹配工作
Mat src_hsv = new Mat();
opencv_imgproc.cvtColor(src, src_hsv, opencv_imgproc.CV_BGR2HSV);
MatVector hsvSplit = new MatVector();
opencv_core.split(src_hsv, hsvSplit);
opencv_imgproc.equalizeHist(hsvSplit.get(2), hsvSplit.get(2));
opencv_core.merge(hsvSplit, src_hsv);
// 匹配模板基色,切换以查找想要的基色
int min_h = r.minH;
int max_h = r.maxH;
float diff_h = (float) ((max_h - min_h) / 2);
int avg_h = (int) (min_h + diff_h);
int channels = src_hsv.channels();
int nRows = src_hsv.rows();
// 图像数据列需要考虑通道数的影响;
int nCols = src_hsv.cols() * channels;
// 连续存储的数据,按一行处理
if (src_hsv.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
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;
boolean colorMatched = false;
if (H > min_h && H < max_h) {
int Hdiff = 0;
if (H > avg_h)
Hdiff = H - avg_h;
else
Hdiff = avg_h - H;
float Hdiff_p = Hdiff / diff_h;
float min_sv = 0;
if (true == adaptive_minsv)
min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p);
else
min_sv = minabs_sv;
if ((S > min_sv && S <= max_sv) && (V > min_sv && V <= max_sv))
colorMatched = true;
}
if (colorMatched == true) {
p.put(j, (byte) 0);
p.put(j + 1, (byte) 0);
p.put(j + 2, (byte) 255);
} else {
p.put(j, (byte) 0);
p.put(j + 1, (byte) 0);
p.put(j + 2, (byte) 0);
}
}
}
// 获取颜色匹配后的二值灰度图
MatVector hsvSplit_done = new MatVector();
opencv_core.split(src_hsv, hsvSplit_done);
Mat src_grey = hsvSplit_done.get(2);
return src_grey;
}
/**
*
*
* @param src
* mat
* @param r
*
* @param adaptive_minsv
* SVadaptive_minsvbool
* <ul>
* <li>trueH
* <li>false使minabs_sv
* </ul>
* @return
*/
public static boolean plateColorJudge(final Mat src, final PlateColor color, final boolean adaptive_minsv) {
// 判断阈值
final float thresh = 0.49f;
Mat gray = colorMatch(src, color, adaptive_minsv);
float percent = (float) opencv_core.countNonZero(gray) / (gray.rows() * gray.cols());
return (percent > thresh) ? true : false;
}
/**
* getPlateType
*
* @param src
* @param adaptive_minsv
* SVadaptive_minsvbool
* <ul>
* <li>trueH
* <li>false使minabs_sv
* </ul>
* @return
*/
public static PlateColor getPlateType(final Mat src, final boolean adaptive_minsv) {
if (plateColorJudge(src, PlateColor.BLUE, adaptive_minsv) == true) {
return PlateColor.BLUE;
} else if (plateColorJudge(src, PlateColor.YELLOW, adaptive_minsv) == true) {
return PlateColor.YELLOW;
} else if (plateColorJudge(src, PlateColor.GREEN, adaptive_minsv) == true) {
return PlateColor.GREEN;
} else {
return PlateColor.UNKNOWN;
}
}
/**
*
*
* @param img
* @param direction
* @return
*/
public static float[] projectedHistogram(final Mat img, Direction direction) {
int sz = 0;
switch (direction) {
case HORIZONTAL:
sz = img.rows();
break;
case VERTICAL:
sz = img.cols();
break;
default:
break;
}
// 统计这一行或一列中非零元素的个数并保存到nonZeroMat中
float[] nonZeroMat = new float[sz];
opencv_core.extractChannel(img, img, 0);
for (int j = 0; j < sz; j++) {
Mat data = (direction == Direction.HORIZONTAL) ? img.row(j) : img.col(j);
int count = opencv_core.countNonZero(data);
nonZeroMat[j] = count;
}
// Normalize histogram
float max = 0;
for (int j = 0; j < nonZeroMat.length; ++j) {
max = Math.max(max, nonZeroMat[j]);
}
if (max > 0) {
for (int j = 0; j < nonZeroMat.length; ++j) {
nonZeroMat[j] /= max;
}
}
return nonZeroMat;
}
/**
* Assign values to feature
* <p>
*
*
* @param in
* @param sizeData
* size = sizeData*sizeData, 0
* @return
*/
public static Mat features(final Mat in, final int sizeData) {
float[] vhist = projectedHistogram(in, Direction.VERTICAL);
float[] hhist = projectedHistogram(in, Direction.HORIZONTAL);
Mat lowData = new Mat();
if (sizeData > 0) {
// resize.cpp:3784: error: (-215:Assertion failed) !ssize.empty() in function 'cv::resize'
opencv_imgproc.resize(in, lowData, new Size(sizeData, sizeData));
}
int numCols = vhist.length + hhist.length + lowData.cols() * lowData.rows();
Mat out = Mat.zeros(1, numCols, opencv_core.CV_32F).asMat();
FloatIndexer idx = out.createIndexer();
int j = 0;
for (int i = 0; i < vhist.length; ++i, ++j) {
idx.put(0, j, vhist[i]);
}
for (int i = 0; i < hhist.length; ++i, ++j) {
idx.put(0, j, hhist[i]);
}
for (int x = 0; x < lowData.cols(); x++) {
for (int y = 0; y < lowData.rows(); y++, ++j) {
float val = lowData.ptr(x, y).get(0) & 0xFF;
idx.put(0, j, val);
}
}
return out;
}
/**
*
* @param title
* @param src
*/
public static void showImage(final String title, final Mat src) {
if (src != null) {
opencv_highgui.imshow(title, src);
opencv_highgui.cvWaitKey(0);
}
}
}

@ -0,0 +1,90 @@
package com.yuxue.easypr.core;
import static com.yuxue.easypr.core.CoreFunc.features;
import static org.bytedeco.javacpp.opencv_core.merge;
import static org.bytedeco.javacpp.opencv_core.split;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_core.MatVector;
import org.bytedeco.javacpp.opencv_imgproc;
/**
*
* @author yuxue
* @date 2020-05-05 08:26
*/
public class Features implements SVMCallback {
/***
* EasyPRgetFeatures
*
* @param image
* @return
*/
@Override
public Mat getHisteqFeatures(final Mat image) {
return histeq(image);
}
private Mat histeq(Mat in) {
Mat out = new Mat(in.size(), in.type());
if (in.channels() == 3) {
Mat hsv = new Mat();
MatVector hsvSplit = new MatVector();
opencv_imgproc.cvtColor(in, hsv, opencv_imgproc.CV_BGR2HSV);
split(hsv, hsvSplit);
opencv_imgproc.equalizeHist(hsvSplit.get(2), hsvSplit.get(2));
merge(hsvSplit, hsv);
opencv_imgproc.cvtColor(hsv, out, opencv_imgproc.CV_HSV2BGR);
hsv = null;
hsvSplit = null;
System.gc();
} else if (in.channels() == 1) {
opencv_imgproc.equalizeHist(in, out);
}
return out;
}
/**
* EasyPRgetFeatures
*
* @param image
* @return
*/
@Override
public Mat getHistogramFeatures(Mat image) {
Mat grayImage = new Mat();
opencv_imgproc.cvtColor(image, grayImage, opencv_imgproc.CV_RGB2GRAY);
Mat img_threshold = new Mat();
opencv_imgproc.threshold(grayImage, img_threshold, 0, 255, opencv_imgproc.CV_THRESH_OTSU + opencv_imgproc.CV_THRESH_BINARY);
return features(img_threshold, 0);
}
/**
* SITF
*
* @param image
* @return
*/
@Override
public Mat getSIFTFeatures(final Mat image) {
// TODO: 待完善
return null;
}
/**
* HOG
*
* @param image
* @return
*/
@Override
public Mat getHOGFeatures(final Mat image) {
// TODO: 待完善
return null;
}
}

@ -0,0 +1,111 @@
package com.yuxue.easypr.core;
import java.util.Vector;
import org.bytedeco.javacpp.opencv_core.Mat;
/**
*
* 1 2
* @author yuxue
* @date 2020-04-24 15:33
*/
public class PlateDetect {
// 车牌定位, 图片处理对象
private PlateLocate plateLocate = new PlateLocate();
// 切图判断对象
private PlateJudge plateJudge = new PlateJudge();
/**
* @param src
* @param resultVec
* @return the error number
* <ul>
* <li>0: plate detected successfully;
* <li>-1: source Mat is empty;
* <li>-2: plate not detected.
* </ul>
*/
public int plateDetect(final Mat src, Vector<Mat> resultVec) {
Vector<Mat> matVec = plateLocate.plateLocate(src); // 定位
if (0 == matVec.size()) {
return -1;
}
if (0 != plateJudge.plateJudge(matVec, resultVec)) { //对多幅图像进行SVM判断
return -2;
}
return 0;
}
/**
*
* @param pdLifemode
*/
public void setPDLifemode(boolean pdLifemode) {
plateLocate.setLifemode(pdLifemode);
}
public void setGaussianBlurSize(int gaussianBlurSize) {
plateLocate.setGaussianBlurSize(gaussianBlurSize);
}
public final int getGaussianBlurSize() {
return plateLocate.getGaussianBlurSize();
}
public void setMorphSizeWidth(int morphSizeWidth) {
plateLocate.setMorphSizeWidth(morphSizeWidth);
}
public final int getMorphSizeWidth() {
return plateLocate.getMorphSizeWidth();
}
public void setMorphSizeHeight(int morphSizeHeight) {
plateLocate.setMorphSizeHeight(morphSizeHeight);
}
public final int getMorphSizeHeight() {
return plateLocate.getMorphSizeHeight();
}
public void setVerifyError(float verifyError) {
plateLocate.setVerifyError(verifyError);
}
public final float getVerifyError() {
return plateLocate.getVerifyError();
}
public void setVerifyAspect(float verifyAspect) {
plateLocate.setVerifyAspect(verifyAspect);
}
public final float getVerifyAspect() {
return plateLocate.getVerifyAspect();
}
public void setVerifyMin(int verifyMin) {
plateLocate.setVerifyMin(verifyMin);
}
public void setVerifyMax(int verifyMax) {
plateLocate.setVerifyMax(verifyMax);
}
public void setJudgeAngle(int judgeAngle) {
plateLocate.setJudgeAngle(judgeAngle);
}
public void setDebug(boolean debug, String tempPath) {
plateLocate.setDebug(debug);
plateLocate.setTempPath(tempPath);
}
}

@ -0,0 +1,107 @@
package com.yuxue.easypr.core;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_imgproc;
import java.util.Vector;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_core.Rect;
import org.bytedeco.javacpp.opencv_core.Size;
import org.bytedeco.javacpp.opencv_ml.SVM;
import com.yuxue.constant.Constant;
/**
*
* @author yuxue
* @date 2020-04-26 15:21
*/
public class PlateJudge {
private SVM svm = SVM.create();
public PlateJudge() {
loadSVM(Constant.DEFAULT_SVM_PATH);
}
public void loadSVM(String path) {
svm.clear();
// svm=SVM.loadSVM(path, "svm");
svm=SVM.load(path);
}
/**
* EasyPRgetFeatures, imagesvmfeatures
*/
private SVMCallback features = new Features();
/**
* SVM
* @param inMat
* @return
*/
public int plateJudge(final Mat inMat) {
int ret = 1;
// 使用com.yuxue.train.SVMTrain 生成的训练库文件
Mat features = this.features.getHistogramFeatures(inMat);
/*Mat samples = features.reshape(1, 1);
samples.convertTo(samples, opencv_core.CV_32F);*/
Mat p = features.reshape(1, 1);
p.convertTo(p, opencv_core.CV_32FC1);
ret = (int) svm.predict(features);
return ret;
// 使用com.yuxue.train.PlateRecoTrain 生成的训练库文件
// 在使用的过程中,传入的样本切图要跟训练的时候处理切图的方法一致
/*Mat grayImage = new Mat();
opencv_imgproc.cvtColor(inMat, grayImage, opencv_imgproc.CV_RGB2GRAY);
Mat dst = new Mat();
opencv_imgproc.Canny(grayImage, dst, 130, 250);
Mat samples = dst.reshape(1, 1);
samples.convertTo(samples, opencv_core.CV_32F);*/
// 正样本为0 负样本为1
/*if(svm.predict(samples) <= 0) {
ret = 1;
}*/
/*ret = (int)svm.predict(samples);
System.err.println(ret);
return ret ;*/
}
/**
* SVM
* @param inVec
* @param resultVec
* @return
*/
public int plateJudge(Vector<Mat> inVec, Vector<Mat> resultVec) {
for (int j = 0; j < inVec.size(); j++) {
Mat inMat = inVec.get(j);
if (1 == plateJudge(inMat)) {
resultVec.add(inMat);
} else { // 再取中间部分判断一次
int w = inMat.cols();
int h = inMat.rows();
Mat tmpDes = inMat.clone();
Mat tmpMat = new Mat(inMat, new Rect((int) (w * 0.05), (int) (h * 0.1), (int) (w * 0.9), (int) (h * 0.8)));
opencv_imgproc.resize(tmpMat, tmpDes, new Size(inMat.size()));
if (plateJudge(tmpDes) == 1) {
resultVec.add(inMat);
}
}
}
return 0;
}
}

@ -0,0 +1,354 @@
package com.yuxue.easypr.core;
import java.util.Vector;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
import com.yuxue.constant.Constant;
import org.bytedeco.javacpp.opencv_imgcodecs;
import org.bytedeco.javacpp.opencv_core.CvPoint2D32f;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_core.MatVector;
import org.bytedeco.javacpp.opencv_core.Point;
import org.bytedeco.javacpp.opencv_core.Point2f;
import org.bytedeco.javacpp.opencv_core.RotatedRect;
import org.bytedeco.javacpp.opencv_core.Scalar;
import org.bytedeco.javacpp.opencv_core.Size;
/**
*
* @author yuxue
* @date 2020-04-24 15:33
*/
public class PlateLocate {
// PlateLocate所用常量
public static final int DEFAULT_GAUSSIANBLUR_SIZE = 5;
public static final int SOBEL_SCALE = 1;
public static final int SOBEL_DELTA = 0;
public static final int SOBEL_DDEPTH = CV_16S;
public static final int SOBEL_X_WEIGHT = 1;
public static final int SOBEL_Y_WEIGHT = 0;
public static final int DEFAULT_MORPH_SIZE_WIDTH = 17;
public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3;
// showResultMat所用常量
public static final int WIDTH = 136;
public static final int HEIGHT = 36;
public static final int TYPE = CV_8UC3;
// verifySize所用常量
public static final int DEFAULT_VERIFY_MIN = 3;
public static final int DEFAULT_VERIFY_MAX = 20;
final float DEFAULT_ERROR = 0.6f;
final float DEFAULT_ASPECT = 3.75f;
// 角度判断所用常量
public static final int DEFAULT_ANGLE = 30;
// 高斯模糊所用变量
protected int gaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE;
// 连接操作所用变量
protected int morphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH;
protected int morphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT;
// verifySize所用变量
protected float error = DEFAULT_ERROR;
protected float aspect = DEFAULT_ASPECT;
protected int verifyMin = DEFAULT_VERIFY_MIN;
protected int verifyMax = DEFAULT_VERIFY_MAX;
// 角度判断所用变量
protected int angle = DEFAULT_ANGLE;
// 是否开启调试模式0关闭非0开启
protected boolean debug = true;
// 开启调试模式之后,切图文件保存路径
protected String tempPath = Constant.DEFAULT_TEMP_DIR + System.currentTimeMillis() + "/";
/**
*
* @param islifemode
*
*
*/
public void setLifemode(boolean islifemode) {
if (islifemode) {
setGaussianBlurSize(5);
setMorphSizeWidth(9);
setMorphSizeHeight(3);
setVerifyError(0.9f);
setVerifyAspect(4);
setVerifyMin(1);
setVerifyMax(30);
} else {
setGaussianBlurSize(DEFAULT_GAUSSIANBLUR_SIZE);
setMorphSizeWidth(DEFAULT_MORPH_SIZE_WIDTH);
setMorphSizeHeight(DEFAULT_MORPH_SIZE_HEIGHT);
setVerifyError(DEFAULT_ERROR);
setVerifyAspect(DEFAULT_ASPECT);
setVerifyMin(DEFAULT_VERIFY_MIN);
setVerifyMax(DEFAULT_VERIFY_MAX);
}
}
/**
*
* @param src
* @return Mat
*/
public Vector<Mat> plateLocate(Mat src) {
Vector<Mat> resultVec = new Vector<Mat>();
Mat src_blur = new Mat();
Mat src_gray = new Mat();
Mat grad = new Mat();
int scale = SOBEL_SCALE;
int delta = SOBEL_DELTA;
int ddepth = SOBEL_DDEPTH;
// 高斯模糊。Size中的数字影响车牌定位的效果。
GaussianBlur(src, src_blur, new Size(gaussianBlurSize, gaussianBlurSize), 0, 0, BORDER_DEFAULT);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_GaussianBlur.jpg", src_blur);
}
// Convert it to gray 将图像进行灰度化
cvtColor(src_blur, src_gray, CV_RGB2GRAY);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_gray.jpg", src_gray);
}
// 对图像进行Sobel 运算,得到的是图像的一阶水平方向导数。
// Generate grad_x and grad_y
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
Sobel(src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
Sobel(src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
// Total Gradient (approximate)
addWeighted(abs_grad_x, SOBEL_X_WEIGHT, abs_grad_y, SOBEL_Y_WEIGHT, 0, grad);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_Sobel.jpg", grad);
}
// 对图像进行二值化。将灰度图像每个像素点有256 个取值可能转化为二值图像每个像素点仅有1 和0 两个取值可能)。
Mat img_threshold = new Mat();
threshold(grad, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_threshold.jpg", img_threshold);
}
// 使用闭操作。对图像进行闭操作以后,可以看到车牌区域被连接成一个矩形装的区域。
Mat element = getStructuringElement(MORPH_RECT, new Size(morphSizeWidth, morphSizeHeight));
morphologyEx(img_threshold, img_threshold, MORPH_CLOSE, element);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_morphology.jpg", img_threshold);
}
// Find 轮廓 of possibles plates 求轮廓。求出图中所有的轮廓。这个算法会把全图的轮廓都计算出来,因此要进行筛选。
MatVector contours = new MatVector();
findContours(img_threshold, contours, // a vector of contours
CV_RETR_EXTERNAL, // 提取外部轮廓
CV_CHAIN_APPROX_NONE); // all pixels of each contours
Mat result = new Mat();
if (debug) {
src.copyTo(result);
// 将轮廓描绘到图上输出
drawContours(result, contours, -1, new Scalar(0, 0, 255, 255));
opencv_imgcodecs.imwrite(tempPath + "debug_Contours.jpg", result);
}
// Start to iterate to each contour founded
// 筛选。对轮廓求最小外接矩形,然后验证,不满足条件的淘汰。
Vector<RotatedRect> rects = new Vector<RotatedRect>();
for (int i = 0; i < contours.size(); ++i) {
RotatedRect mr = minAreaRect(contours.get(i));
if (verifySizes(mr))
rects.add(mr);
}
int k = 1;
for (int i = 0; i < rects.size(); i++) {
RotatedRect minRect = rects.get(i);
/*if (debug) {
Point2f rect_points = new Point2f(4);
minRect.points(rect_points);
for (int j = 0; j < 4; j++) {
Point pt1 = new Point(new CvPoint2D32f(rect_points.position(j)));
Point pt2 = new Point(new CvPoint2D32f(rect_points.position((j + 1) % 4)));
line(result, pt1, pt2, new Scalar(0, 255, 255, 255), 1, 8, 0);
}
}*/
// rotated rectangle drawing
// 旋转这部分代码确实可以将某些倾斜的车牌调整正,但是它也会误将更多正的车牌搞成倾斜!所以综合考虑,还是不使用这段代码。
// 2014-08-14,由于新到的一批图片中发现有很多车牌是倾斜的,因此决定再次尝试这段代码。
float r = minRect.size().width() / minRect.size().height();
float angle = minRect.angle();
Size rect_size = new Size((int) minRect.size().width(), (int) minRect.size().height());
if (r < 1) {
angle = 90 + angle;
rect_size = new Size(rect_size.height(), rect_size.width());
}
// 如果抓取的方块旋转超过m_angle角度则不是车牌放弃处理
if (angle - this.angle < 0 && angle + this.angle > 0) {
Mat img_rotated = new Mat();
Mat rotmat = getRotationMatrix2D(minRect.center(), angle, 1);
warpAffine(src, img_rotated, rotmat, src.size()); // CV_INTER_CUBIC
Mat resultMat = showResultMat(img_rotated, rect_size, minRect.center(), k++);
resultVec.add(resultMat);
}
}
return resultVec;
}
/**
* minAreaRect
*
* @param mr
* @return
*/
private boolean verifySizes(RotatedRect mr) {
float error = this.error;
// China car plate size: 440mm*140mmaspect 3.142857
float aspect = this.aspect;
int min = 44 * 14 * verifyMin; // minimum area
int max = 44 * 14 * verifyMax; // maximum area
// Get only patchs that match to a respect ratio.
float rmin = aspect - aspect * error;
float rmax = aspect + aspect * error;
int area = (int) (mr.size().height() * mr.size().width());
float r = mr.size().width() / mr.size().height();
if (r < 1)
r = mr.size().height() / mr.size().width();
return area >= min && area <= max && r >= rmin && r <= rmax;
}
/**
* 便
* @param src
* @param rect_size
* @param center
* @param index
* @return
*/
private Mat showResultMat(Mat src, Size rect_size, Point2f center, int index) {
Mat img_crop = new Mat();
getRectSubPix(src, rect_size, center, img_crop);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_crop_" + index + ".jpg", img_crop);
}
Mat resultResized = new Mat();
resultResized.create(HEIGHT, WIDTH, TYPE);
resize(img_crop, resultResized, resultResized.size(), 0, 0, INTER_CUBIC);
if (debug) {
opencv_imgcodecs.imwrite(tempPath + "debug_resize_" + index + ".jpg", resultResized);
}
return resultResized;
}
public String getTempPath() {
return tempPath;
}
public void setTempPath(String tempPath) {
this.tempPath = tempPath;
}
public void setGaussianBlurSize(int gaussianBlurSize) {
this.gaussianBlurSize = gaussianBlurSize;
}
public final int getGaussianBlurSize() {
return this.gaussianBlurSize;
}
public void setMorphSizeWidth(int morphSizeWidth) {
this.morphSizeWidth = morphSizeWidth;
}
public final int getMorphSizeWidth() {
return this.morphSizeWidth;
}
public void setMorphSizeHeight(int morphSizeHeight) {
this.morphSizeHeight = morphSizeHeight;
}
public final int getMorphSizeHeight() {
return this.morphSizeHeight;
}
public void setVerifyError(float error) {
this.error = error;
}
public final float getVerifyError() {
return this.error;
}
public void setVerifyAspect(float aspect) {
this.aspect = aspect;
}
public final float getVerifyAspect() {
return this.aspect;
}
public void setVerifyMin(int verifyMin) {
this.verifyMin = verifyMin;
}
public void setVerifyMax(int verifyMax) {
this.verifyMax = verifyMax;
}
public void setJudgeAngle(int angle) {
this.angle = angle;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public boolean getDebug() {
return debug;
}
}

@ -0,0 +1,44 @@
package com.yuxue.easypr.core;
import org.bytedeco.javacpp.opencv_core.Mat;
/**
* @author Created by fanwenjie
* @author lin.yao
*
*/
public interface SVMCallback {
/***
* EasyPRgetFeatures,
*
* @param image
* @return
*/
public abstract Mat getHisteqFeatures(final Mat image);
/**
* EasyPRgetFeatures,
*
* @param image
* @return
*/
public abstract Mat getHistogramFeatures(final Mat image);
/**
* SITF
*
* @param image
* @return
*/
public abstract Mat getSIFTFeatures(final Mat image);
/**
* HOG
*
* @param image
* @return
*/
public abstract Mat getHOGFeatures(final Mat image);
}

@ -0,0 +1,382 @@
package com.yuxue.train;
import java.util.*;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_ml.*;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_imgcodecs;
import com.yuxue.easypr.core.Features;
import com.yuxue.easypr.core.SVMCallback;
import com.yuxue.util.Convert;
import com.yuxue.util.FileUtil;
/**
* org.bytedeco.javacpp
* JavaCPP Java 访 C++
*
*
*
*
* svm.xml
* 1res/model/svm.xml
* 2com.yuxue.easypr.core.PlateJudge.plateJudge(Mat)
*
* @author yuxue
* @date 2020-05-14 22:16
*/
public class SVMTrain1 {
private SVMCallback callback = new Features();
// 默认的训练操作的根目录
private static final String DEFAULT_PATH = "D:/PlateDetect/train/plate_detect_svm/";
// 训练模型文件保存位置
private static final String MODEL_PATH = DEFAULT_PATH + "svm.xml";
private static final String hasPlate = "HasPlate";
private static final String noPlate = "NoPlate";
public SVMTrain1() {
}
public SVMTrain1(SVMCallback callback) {
this.callback = callback;
}
/**
* learntain testhasPalte noPlate
* bound%30%
* @param bound
* @param name
*/
private void learn2Plate(float bound, final String name) {
final String filePath = DEFAULT_PATH + "learn/" + name;
Vector<String> files = new Vector<String>();
//// 获取该路径下的所有文件
FileUtil.getFiles(filePath, files);
int size = files.size();
if (0 == size) {
System.err.println("当前目录下没有文件: " + filePath);
return;
}
Collections.shuffle(files, new Random(new Date().getTime()));
//// 随机选取70%作为训练数据30%作为测试数据
int boundry = (int) (bound * size);
// 重新创建目录
FileUtil.recreateDir(DEFAULT_PATH + "train/" + name);
FileUtil.recreateDir(DEFAULT_PATH + "test/" + name);
for (int i = 0; i < boundry; i++) {
Mat img = opencv_imgcodecs.imread(files.get(i));
String str = DEFAULT_PATH + "train/" + name + "/" + name + "_" + Integer.valueOf(i).toString() + ".jpg";
opencv_imgcodecs.imwrite(str, img);
}
for (int i = boundry; i < size; i++) {
Mat img = opencv_imgcodecs.imread(files.get(i));
String str = DEFAULT_PATH + "test/" + name + "/" + name + "_" + Integer.valueOf(i).toString() + ".jpg";
opencv_imgcodecs.imwrite(str, img);
}
}
/**
*
* @param trainingImages
* @param trainingLabels
* @param name
*/
private void getPlateTrain(Mat trainingImages, Vector<Integer> trainingLabels, final String name, int label) {
// int label = 1;
final String filePath = DEFAULT_PATH + "train/" + name;
Vector<String> files = new Vector<String>();
// 获取该路径下的所有文件
FileUtil.getFiles(filePath, files);
int size = files.size();
if (null == files || size <= 0) {
System.out.println("File not found in " + filePath);
return;
}
for (int i = 0; i < size; i++) {
// System.out.println(files.get(i));
Mat inMat = opencv_imgcodecs.imread(files.get(i));
// 调用回调函数决定特征
// Mat features = this.callback.getHisteqFeatures(inMat);
Mat features = this.callback.getHistogramFeatures(inMat);
// 通过直方图均衡化后的彩色图进行预测
Mat p = features.reshape(1, 1);
p.convertTo(p, opencv_core.CV_32F);
// 136 36 14688 1 变换尺寸
// System.err.println(inMat.cols() + "\t" + inMat.rows() + "\t" + p.cols() + "\t" + p.rows());
trainingImages.push_back(p); // 合并成一张图片
trainingLabels.add(label);
}
}
private void getPlateTest(MatVector testingImages, Vector<Integer> testingLabels, final String name, int label) {
// int label = 1;
final String filePath = DEFAULT_PATH + "test/" + name;
Vector<String> files = new Vector<String>();
FileUtil.getFiles(filePath, files);
int size = files.size();
if (0 == size) {
System.out.println("File not found in " + filePath);
return;
}
System.out.println("get " + name + " test!");
for (int i = 0; i < size; i++) {
Mat inMat = opencv_imgcodecs.imread(files.get(i));
testingImages.push_back(inMat);
testingLabels.add(label);
}
}
// ! 测试SVM的准确率回归率以及FScore
public void getAccuracy(Mat testingclasses_preditc, Mat testingclasses_real) {
int channels = testingclasses_preditc.channels();
System.out.println("channels: " + Integer.valueOf(channels).toString());
int nRows = testingclasses_preditc.rows();
System.out.println("nRows: " + Integer.valueOf(nRows).toString());
int nCols = testingclasses_preditc.cols() * channels;
System.out.println("nCols: " + Integer.valueOf(nCols).toString());
int channels_real = testingclasses_real.channels();
System.out.println("channels_real: " + Integer.valueOf(channels_real).toString());
int nRows_real = testingclasses_real.rows();
System.out.println("nRows_real: " + Integer.valueOf(nRows_real).toString());
int nCols_real = testingclasses_real.cols() * channels;
System.out.println("nCols_real: " + Integer.valueOf(nCols_real).toString());
double count_all = 0;
double ptrue_rtrue = 0;
double ptrue_rfalse = 0;
double pfalse_rtrue = 0;
double pfalse_rfalse = 0;
for (int i = 0; i < nRows; i++) {
final float predict = Convert.toFloat(testingclasses_preditc.ptr(i));
final float real = Convert.toFloat(testingclasses_real.ptr(i));
count_all++;
// System.out.println("predict:" << predict).toString());
// System.out.println("real:" << real).toString());
if (predict == 1.0 && real == 1.0)
ptrue_rtrue++;
if (predict == 1.0 && real == 0)
ptrue_rfalse++;
if (predict == 0 && real == 1.0)
pfalse_rtrue++;
if (predict == 0 && real == 0)
pfalse_rfalse++;
}
System.out.println("count_all: " + Double.valueOf(count_all).toString());
System.out.println("ptrue_rtrue: " + Double.valueOf(ptrue_rtrue).toString());
System.out.println("ptrue_rfalse: " + Double.valueOf(ptrue_rfalse).toString());
System.out.println("pfalse_rtrue: " + Double.valueOf(pfalse_rtrue).toString());
System.out.println("pfalse_rfalse: " + Double.valueOf(pfalse_rfalse).toString());
double precise = 0;
if (ptrue_rtrue + ptrue_rfalse != 0) {
precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse);
System.out.println("precise: " + Double.valueOf(precise).toString());
} else {
System.out.println("precise: NA");
}
double recall = 0;
if (ptrue_rtrue + pfalse_rtrue != 0) {
recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue);
System.out.println("recall: " + Double.valueOf(recall).toString());
} else {
System.out.println("recall: NA");
}
if (precise + recall != 0) {
double F = (precise * recall) / (precise + recall);
System.out.println("F: " + Double.valueOf(F).toString());
} else {
System.out.println("F: NA");
}
}
/**
*
* @param dividePrepared
* @return
*/
public int svmTrain(boolean dividePrepared) {
Mat classes = new Mat();
Mat trainingData = new Mat();
Mat trainingImages = new Mat();
Vector<Integer> trainingLabels = new Vector<Integer>();
// 分割learn里的数据到train和test里 // 从库里面选取训练样本
if (!dividePrepared) {
learn2Plate(0.1f, hasPlate); // 性能不好的机器,最好不要挑选太多的样本,这个方案太消耗资源了。
learn2Plate(0.1f, noPlate);
}
// System.err.println("Begin to get train data to memory");
getPlateTrain(trainingImages, trainingLabels, hasPlate, 0);
getPlateTrain(trainingImages, trainingLabels, noPlate, 1);
// System.err.println(trainingImages.cols());
trainingImages.copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32F);
int[] labels = new int[trainingLabels.size()];
for (int i = 0; i < trainingLabels.size(); ++i) {
labels[i] = trainingLabels.get(i).intValue();
}
new Mat(labels).copyTo(classes);
TrainData train_data = TrainData.create(trainingData, ROW_SAMPLE, classes);
SVM svm = SVM.create();
try {
TermCriteria criteria = new TermCriteria(TermCriteria.EPS + TermCriteria.MAX_ITER, 20000, 0.0001);
svm.setTermCriteria(criteria); // 指定
svm.setKernel(SVM.RBF); // 使用预先定义的内核初始化
svm.setType(SVM.C_SVC); // SVM的类型,默认是SVM.C_SVC
svm.setGamma(0.1); // 核函数的参数
svm.setNu(0.1); // SVM优化问题参数
svm.setC(1); // SVM优化问题的参数C
svm.setP(0.1);
svm.setDegree(0.1);
svm.setCoef0(0.1);
svm.trainAuto(train_data, 10,
SVM.getDefaultGrid(SVM.C),
SVM.getDefaultGrid(SVM.GAMMA),
SVM.getDefaultGrid(SVM.P),
SVM.getDefaultGrid(SVM.NU),
SVM.getDefaultGrid(SVM.COEF),
SVM.getDefaultGrid(SVM.DEGREE),
true);
} catch (Exception err) {
System.out.println(err.getMessage());
}
System.out.println("Svm generate done!");
/*FileStorage fsTo = new FileStorage(MODEL_PATH, FileStorage.WRITE);
svm.write(fsTo, "svm");*/
svm.save(MODEL_PATH);
return 0;
}
// 测试
public int svmPredict() {
SVM svm = SVM.create();
try {
svm.clear();
// svm = SVM.loadSVM(MODEL_PATH, "svm");
svm = SVM.load(MODEL_PATH);
} catch (Exception err) {
System.err.println(err.getMessage());
return 0; // next predict requires svm
}
System.out.println("Begin to predict");
// Test SVM
MatVector testingImages = new MatVector();
Vector<Integer> testingLabels_real = new Vector<Integer>();
// 将测试数据加载入内存
getPlateTest(testingImages, testingLabels_real, hasPlate, 0);
getPlateTest(testingImages, testingLabels_real, noPlate, 1);
double count_all = 0;
double ptrue_rtrue = 0;
double ptrue_rfalse = 0;
double pfalse_rtrue = 0;
double pfalse_rfalse = 0;
long size = testingImages.size();
System.err.println(size);
for (int i = 0; i < size; i++) {
Mat inMat = testingImages.get(i);
// Mat features = callback.getHisteqFeatures(inMat);
Mat features = callback.getHistogramFeatures(inMat);
Mat p = features.reshape(1, 1);
p.convertTo(p, opencv_core.CV_32F);
// System.out.println(p.cols() + "\t" + p.rows() + "\t" + p.type());
// samples.cols == var_count && samples.type() == CV_32F
// var_count 的值会在svm.xml库文件中有体现
float predoct = svm.predict(features);
int predict = (int) predoct; // 预期值
int real = testingLabels_real.get(i); // 实际值
if (predict == 1 && real == 1)
ptrue_rtrue++;
if (predict == 1 && real == 0)
ptrue_rfalse++;
if (predict == 0 && real == 1)
pfalse_rtrue++;
if (predict == 0 && real == 0)
pfalse_rfalse++;
}
count_all = size;
System.out.println("Get the Accuracy!");
System.out.println("count_all: " + Double.valueOf(count_all).toString());
System.out.println("ptrue_rtrue: " + Double.valueOf(ptrue_rtrue).toString());
System.out.println("ptrue_rfalse: " + Double.valueOf(ptrue_rfalse).toString());
System.out.println("pfalse_rtrue: " + Double.valueOf(pfalse_rtrue).toString());
System.out.println("pfalse_rfalse: " + Double.valueOf(pfalse_rfalse).toString());
double precise = 0;
if (ptrue_rtrue + ptrue_rfalse != 0) {
precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse);
System.out.println("precise: " + Double.valueOf(precise).toString());
} else
System.out.println("precise: NA");
double recall = 0;
if (ptrue_rtrue + pfalse_rtrue != 0) {
recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue);
System.out.println("recall: " + Double.valueOf(recall).toString());
} else
System.out.println("recall: NA");
double Fsocre = 0;
if (precise + recall != 0) {
Fsocre = 2 * (precise * recall) / (precise + recall);
System.out.println("Fsocre: " + Double.valueOf(Fsocre).toString());
} else
System.out.println("Fsocre: NA");
return 0;
}
public static void main(String[] args) {
SVMTrain1 s = new SVMTrain1();
s.svmTrain(true);
s.svmPredict();
}
}
Loading…
Cancel
Save