master
Atlas Shen 4 years ago
commit 95980a6021

@ -0,0 +1,87 @@
package com.yuxue.util;
import org.bytedeco.javacpp.BytePointer;
/**
* There are 3 kinds of convert functions:
* 1. [float|double|int|long] to[Float|Double|Int|Long](BytePointer pointer)
* 2. byte[] getBytes([float|double|int|long] value)
* 3. [float|double|int|long] to[Float|Double|Int|Long](byte[] value)
*
* @author lin.yao
*
*/
public class Convert {
public static float toFloat(BytePointer pointer) {
byte[] buffer = new byte[4];
pointer.get(buffer);
return toFloat(buffer);
}
public static double toDouble(BytePointer pointer) {
byte[] buffer = new byte[8];
pointer.get(buffer);
return toDouble(buffer);
}
public static int toInt(BytePointer pointer) {
byte[] buffer = new byte[4];
pointer.get(buffer);
return toInt(buffer);
}
public static long toLong(BytePointer pointer) {
byte[] buffer = new byte[8];
pointer.get(buffer);
return toLong(buffer);
}
public static byte[] getBytes(float value) {
return getBytes(Float.floatToIntBits(value));
}
public static byte[] getBytes(double value) {
return getBytes(Double.doubleToLongBits(value));
}
public static byte[] getBytes(int value) {
final int length = 4;
byte[] buffer = new byte[length];
for (int i = 0; i < length; ++i)
buffer[i] = (byte) ((value >> (i * 8)) & 0xFF);
return buffer;
}
public static byte[] getBytes(long value) {
final int length = 8;
byte[] buffer = new byte[length];
for (int i = 0; i < length; ++i)
buffer[i] = (byte) ((value >> (i * 8)) & 0xFF);
return buffer;
}
public static int toInt(byte[] value) {
final int length = 4;
int n = 0;
for (int i = 0; i < length; ++i)
n += (value[i] & 0xFF) << (i * 8);
return n;
}
public static long toLong(byte[] value) {
final int length = 8;
long n = 0;
for (int i = 0; i < length; ++i)
n += ((long) (value[i] & 0xFF)) << (i * 8);
return n;
}
public static double toDouble(byte[] value) {
return Double.longBitsToDouble(toLong(value));
}
public static float toFloat(byte[] value) {
return Float.intBitsToFloat(toInt(value));
}
}

@ -0,0 +1,196 @@
package com.yuxue.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.yuxue.exception.ResultReturnException;
/**
*
* @author yuxue
* @date 2020-04-19 15:23
*/
public class FileUtil {
static Lock lock = new ReentrantLock();
public static boolean copyAndRename(String from, String to) {
Path sourcePath = Paths.get(from);
Path destinationPath = Paths.get(to);
try {
Files.copy(sourcePath, destinationPath);
} catch(FileAlreadyExistsException e) {
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public static boolean checkFile(final File file) {
if(file.exists() && file.isFile()) {
return true;
}
return false;
}
/**
*
* @param file
* @param newName +
* @return
*/
public static boolean renameFile(String filePath, String newName) {
File file = new File(filePath);
return renameFile(file, newName);
}
/**
*
* @param file
* @param newName +
* @return
*/
public static boolean renameFile(File file, String newName) {
if(file.exists()) {
String targetPath = null;
if(newName.indexOf("/") >= 0 || newName.indexOf("\\\\") >= 0) {
targetPath = newName;
} else {
targetPath = file.getParentFile().getAbsolutePath() + "/" + newName;
}
File targetFile = new File(targetPath);
file.renameTo(targetFile);
return true;
}
return false;
}
public static void createDir(String dir) {
File file = new File(dir);
if(file.exists() && file.isDirectory()) {
return ;
} else {
file.mkdirs();
}
}
/**
*
* @param dir
*/
public static void recreateDir(final String dir) {
new File(dir).delete();
new File(dir).mkdir();
}
/**
*
* @param path String
* @param files
*/
public static void getFiles(final String path, Vector<String> files) {
getFiles(new File(path), files);
}
/**
*
* @param dir FIle
* @param files
*/
private static void getFiles(final File dir, Vector<String> files) {
File[] filelist = dir.listFiles();
for (File file : filelist) {
if (file.isDirectory()) {
getFiles(file, files);
} else {
files.add(file.getAbsolutePath());
}
}
}
/**
*
* @param dir
* @param filename
* @param recursive
* @return
*/
public static List<File> listFile(File dir, final String fileType, boolean recursive) {
if (!dir.exists()) {
throw new ResultReturnException("目录:" + dir + "不存在");
}
if (!dir.isDirectory()) {
throw new ResultReturnException(dir + "不是目录");
}
FileFilter ff = null;
if (fileType == null || fileType.length() == 0) {
ff = new FileFilter() {
@Override
public boolean accept(File pathname) {
return true;
}
};
} else {
ff = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
}
String name = pathname.getName().toLowerCase();
String format = name.substring(name.lastIndexOf(".") + 1);
if (fileType.contains(format)) {
return true;
} else {
return false;
}
}
};
}
return listFile(dir, ff, recursive);
}
/**
*
* @param dir
* @param ff
* @param recursive
* @return
*/
public static List<File> listFile(File dir, FileFilter ff, boolean recursive) {
List<File> list = new ArrayList<File>();
File[] files = dir.listFiles(ff);
if (files != null && files.length > 0) {
for (File f : files) {
// 如果是文件,添加文件到list中
if (f.isFile() || (f.isDirectory() && !f.getName().startsWith("."))) {
list.add(f);
} else if (recursive) {
// 获取子目录中的文件,添加子目录中的经过过滤的所有文件添加到list
list.addAll(listFile(f, ff, true));
}
}
}
return list;
}
}

@ -0,0 +1,899 @@
package com.yuxue.util;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yuxue.constant.Constant;
import com.yuxue.enumtype.PlateColor;
/**
*
*
* @author yuxue
* @date 2020-05-18 12:07
*/
public class ImageUtil {
private static String DEFAULT_BASE_TEST_PATH = "D:/PlateDetect/temp/";
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
// 车牌定位处理步骤该map用于表示步骤图片的顺序
private static Map<String, Integer> debugMap = Maps.newLinkedHashMap();
static {
debugMap.put("yuantu", 0); // 原图
debugMap.put("gaussianBlur", 0); // 高斯模糊
debugMap.put("gray", 0); // 图像灰度化
debugMap.put("sobel", 0); // Sobel 运算,得到图像的一阶水平方向导数
debugMap.put("threshold", 0); //图像二值化
debugMap.put("morphology", 0); // 图像闭操作
debugMap.put("clearInnerHole", 0); // 降噪
debugMap.put("clearSmallConnArea", 0); // 降噪
debugMap.put("clearAngleConn", 0); // 降噪
debugMap.put("clearHole", 0); // 降噪
debugMap.put("contours", 0); // 提取外部轮廓
debugMap.put("screenblock", 0); // 外部轮廓筛选
debugMap.put("crop", 0); // 切图
debugMap.put("resize", 0); // 切图resize
// 设置index 用于debug生成文件时候按名称排序
Integer index = 100;
for (Entry<String, Integer> entry : debugMap.entrySet()) {
entry.setValue(index);
index ++;
}
}
public static void main(String[] args) {
Instant start = Instant.now();
String tempPath = DEFAULT_BASE_TEST_PATH + "test/";
String filename = tempPath + "/100_yuantu.jpg";
filename = tempPath + "/100_yuantu1.jpg";
// filename = tempPath + "/109_crop_0.png";
// 读取原图
Mat src = Imgcodecs.imread(filename);
Boolean debug = true;
// 高斯模糊
Mat gsMat = ImageUtil.gaussianBlur(src, debug, tempPath);
// 灰度图
Mat gray = ImageUtil.gray(gsMat, debug, tempPath);
Mat sobel = ImageUtil.sobel(gray, debug, tempPath);
Mat threshold = ImageUtil.threshold(sobel, debug, tempPath);
// Mat scharr = ImageUtil.scharr(gray, debug, tempPath);
// Mat threshold = ImageUtil.threshold(scharr, debug, tempPath);
Mat morphology = ImageUtil.morphology(threshold, debug, tempPath);
List<MatOfPoint> contours = ImageUtil.contours(src, morphology, debug, tempPath);
Vector<Mat> rects = ImageUtil.screenBlock(src, contours, debug, tempPath);
PlateUtil.loadSvmModel("D:/PlateDetect/train/plate_detect_svm/svm2.xml");
PlateUtil.loadAnnModel("D:/PlateDetect/train/chars_recognise_ann/ann.xml");
Vector<Mat> dst = new Vector<Mat>();
PlateUtil.hasPlate(rects, dst, debug, tempPath);
System.err.println("识别到的车牌数量:" + dst.size());
dst.stream().forEach(inMat -> {
PlateColor color = PlateUtil.getPlateColor(inMat, true, debug, tempPath);
System.err.println(color.desc);
Vector<Mat> charMat = new Vector<Mat>();
PlateUtil.charsSegment(inMat, color, charMat, debug, tempPath);
});
/*String filename = tempPath + "/hsvMat_1590994270425.jpg";
Mat src = Imgcodecs.imread(filename);
Vector<Mat> charMat = new Vector<Mat>();
PlateUtil.charsSegment(src, PlateColor.BLUE, charMat, true, tempPath);*/
Instant end = Instant.now();
System.err.println("总耗时:" + Duration.between(start, end).toMillis());
// ImageUtil.rgb2Hsv(src, debug, tempPath);
// ImageUtil.getHSVValue(src, debug, tempPath);
}
/**
*
* @param inMat
* @param debug
* @return
*/
public static final int DEFAULT_GAUSSIANBLUR_SIZE = 5;
public static Mat gaussianBlur(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.GaussianBlur(inMat, dst, new Size(DEFAULT_GAUSSIANBLUR_SIZE, DEFAULT_GAUSSIANBLUR_SIZE), 0, 0, Core.BORDER_DEFAULT);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("gaussianBlur") + "_gaussianBlur.jpg", dst);
}
return dst;
}
/**
*
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat gray(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.cvtColor(inMat, dst, Imgproc.COLOR_BGR2GRAY);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("gray") + "_gray.jpg", dst);
}
inMat.release();
return dst;
}
/**
* Sobel
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static final int SOBEL_SCALE = 1;
public static final int SOBEL_DELTA = 0;
public static final int SOBEL_X_WEIGHT = 1;
public static final int SOBEL_Y_WEIGHT = 0;
public static Mat sobel(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
// Sobel滤波 计算水平方向灰度梯度的绝对值
Imgproc.Sobel(inMat, grad_x, CvType.CV_16S, 1, 0, 3, SOBEL_SCALE, SOBEL_DELTA, Core.BORDER_DEFAULT);
Core.convertScaleAbs(grad_x, abs_grad_x); // 增强对比度
Imgproc.Sobel(inMat, grad_y, CvType.CV_16S, 0, 1, 3, SOBEL_SCALE, SOBEL_DELTA, Core.BORDER_DEFAULT);
Core.convertScaleAbs(grad_y, abs_grad_y);
grad_x.release();
grad_y.release();
// 计算结果梯度
Core.addWeighted(abs_grad_x, SOBEL_X_WEIGHT, abs_grad_y, SOBEL_Y_WEIGHT, 0, dst);
abs_grad_x.release();
abs_grad_y.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("sobel") + "_sobel.jpg", dst);
}
return dst;
}
/**
* scharr
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat scharr(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
//注意求梯度的时候我们使用的是Scharr算法sofia算法容易收到图像细节的干扰
//所谓梯度运算就是对图像中的像素点进行就导数运算,从而得到相邻两个像素点的差异值 by:Tantuo
Imgproc.Scharr(inMat, grad_x, CvType.CV_32F, 1, 0);
Imgproc.Scharr(inMat, grad_y, CvType.CV_32F, 0, 1);
//openCV中有32位浮点数的CvType用于保存可能是负值的像素数据值
Core.convertScaleAbs(grad_x, abs_grad_x);
Core.convertScaleAbs(grad_y, abs_grad_y);
//openCV中使用release()释放Mat类图像使用recycle()释放BitMap类图像
grad_x.release();
grad_y.release();
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
abs_grad_x.release();
abs_grad_y.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("sobel") + "_sobel.jpg", dst);
}
return dst;
}
/**
* 256 0255
* 10
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat threshold(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.threshold(inMat, dst, 100, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("threshold") + "_threshold.jpg", dst);
}
inMat.release();
return dst;
}
/**
* 使
* @param inMat
* @param debug
* @param tempPath
* @return
*/
// public static final int DEFAULT_MORPH_SIZE_WIDTH = 15;
// public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3;
public static final int DEFAULT_MORPH_SIZE_WIDTH = 9;
public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3;
public static Mat morphology(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
Size size = new Size(DEFAULT_MORPH_SIZE_WIDTH, DEFAULT_MORPH_SIZE_HEIGHT);
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, size);
Imgproc.morphologyEx(inMat, dst, Imgproc.MORPH_CLOSE, element);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("morphology") + "_morphology0.jpg", dst);
}
// 填补内部孔洞,为了去除小连通区域的时候,降低影响
Mat a = clearInnerHole(dst, 8, 16, debug, tempPath);
// 去除小连通区域
Mat b = clearSmallConnArea(a, 1, 10, debug, tempPath);
// 按斜边去除
// Mat e = clearAngleConn(b, 5, debug, tempPath);
// 填补边缘孔洞
// Mat d = clearHole(a, 4, 2, debug, tempPath);
return b;
}
/**
* Find of possibles plates
*
* @param src
* @param inMat morphology Mat
* @param debug
* @param tempPath
* @return
*/
public static List<MatOfPoint> contours(Mat src, Mat inMat, Boolean debug, String tempPath) {
List<MatOfPoint> contours = Lists.newArrayList();
Mat hierarchy = new Mat();
// 提取外部轮廓
// CV_RETR_EXTERNAL只检测最外围轮廓
// CV_RETR_LIST 检测所有的轮廓
// CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
Imgproc.findContours(inMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
if (debug) {
Mat result = new Mat();
src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图
// 将轮廓描绘到原图
Imgproc.drawContours(result, contours, -1, new Scalar(0, 0, 255, 255));
// 输出带轮廓的原图
Imgcodecs.imwrite(tempPath + debugMap.get("contours") + "_contours.jpg", result);
}
return contours;
}
/**
*
* @param src
* @param matVector
* @param debug
* @param tempPath
* @return
*/
public static final int DEFAULT_ANGLE = 30; // 角度判断所用常量
public static final int TYPE = CvType.CV_8UC3;
public static Vector<Mat> screenBlock(Mat src, List<MatOfPoint> contours, Boolean debug, String tempPath){
Vector<Mat> dst = new Vector<Mat>();
List<MatOfPoint> mv = Lists.newArrayList(); // 用于在原图上描绘筛选后的结果
for (int i = 0, j = 0; i < contours.size(); i++) {
MatOfPoint m1 = contours.get(i);
MatOfPoint2f m2 = new MatOfPoint2f();
m1.convertTo(m2, CvType.CV_32F);
// RotatedRect 该类表示平面上的旋转矩形,有三个属性: 矩形中心点(质心); 边长(长和宽); 旋转角度
// boundingRect()得到包覆此轮廓的最小正矩形, minAreaRect()得到包覆轮廓的最小斜矩形
RotatedRect mr = Imgproc.minAreaRect(m2);
double angle = Math.abs(mr.angle);
if (checkPlateSize(mr) && angle <= DEFAULT_ANGLE) { // 判断尺寸及旋转角度 ±30°排除不合法的图块
mv.add(contours.get(i));
Size rect_size = new Size((int) mr.size.width, (int) mr.size.height);
if (mr.size.width / mr.size.height < 1) { // 宽度小于高度
angle = 90 + angle; // 旋转90°
rect_size = new Size(rect_size.height, rect_size.width);
}
// 旋转角度,根据需要是否进行角度旋转
/*Mat img_rotated = new Mat();
Mat rotmat = Imgproc.getRotationMatrix2D(mr.center, angle, 1); // 旋转
Imgproc.warpAffine(src, img_rotated, rotmat, src.size()); // 仿射变换 考虑是否需要进行投影变换?
*/
// 切图
Mat img_crop = new Mat();
Imgproc.getRectSubPix(src, rect_size, mr.center, img_crop);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("crop") + "_crop_" + j + ".png", img_crop);
}
// 处理切图,调整为指定大小
Mat resized = new Mat(Constant.DEFAULT_HEIGHT, Constant.DEFAULT_WIDTH, TYPE);
Imgproc.resize(img_crop, resized, resized.size(), 0, 0, Imgproc.INTER_CUBIC);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("resize") + "_resize_" + j + ".png", resized);
j++;
}
dst.add(resized);
}
}
if (debug) {
Mat result = new Mat();
src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图
// 将轮廓描绘到原图
Imgproc.drawContours(result, mv, -1, new Scalar(0, 0, 255, 255));
// 输出带轮廓的原图
Imgcodecs.imwrite(tempPath + debugMap.get("screenblock") + "_screenblock.jpg", result);
}
return dst;
}
/**
* minAreaRect
*
* 绿
* : 440mm*140mm 3.142857
* @param mr
* @return
*/
final static float DEFAULT_ERROR = 0.7f; // 宽高比允许70%误差
final static float DEFAULT_ASPECT = 3.142857f;
public static final int DEFAULT_VERIFY_MIN = 1;
public static final int DEFAULT_VERIFY_MAX = 30;
private static boolean checkPlateSize(RotatedRect mr) {
// 切图面积取值范围
int min = 44 * 14 * DEFAULT_VERIFY_MIN;
int max = 44 * 14 * DEFAULT_VERIFY_MAX;
// 切图横纵比取值范围;关键在于纵横比例
float rmin = DEFAULT_ASPECT - DEFAULT_ASPECT * DEFAULT_ERROR;
float rmax = DEFAULT_ASPECT + DEFAULT_ASPECT * DEFAULT_ERROR;
// 切图计算面积
int area = (int) (mr.size.height * mr.size.width);
// 切图宽高比
double r = mr.size.width / mr.size.height;
/*if (r < 1) { // 注释掉不处理width 小于height的图片
r = mr.size.height / mr.size.width;
}*/
return min <= area && area <= max && rmin <= r && r <= rmax;
}
/**
* rgbhsv
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat rgb2Hsv(Mat inMat, Boolean debug, String tempPath) {
// 转到HSV空间进行处理
Mat dst = new Mat();
Imgproc.cvtColor(inMat, dst, Imgproc.COLOR_BGR2HSV);
List<Mat> hsvSplit = Lists.newArrayList();
Core.split(dst, hsvSplit);
// 直方图均衡化是一种常见的增强图像对比度的方法,使用该方法可以增强局部图像的对比度,尤其在数据较为相似的图像中作用更加明显
Imgproc.equalizeHist(hsvSplit.get(2), hsvSplit.get(2));
Core.merge(hsvSplit, dst);
if (debug) {
// Imgcodecs.imwrite(tempPath + "hsvMat_"+System.currentTimeMillis()+".jpg", dst);
}
return dst;
}
/**
* HSVH
* HSV广H, HueS,SaturationV, Value
* 1.PSH0-360S0%-100%V0%-100%
* 2.openCVcvSplitIPL_DEPTH_32FH0-360S0-10%-100%V0-10%-100%
* 3.openCVcvSplitIPL_DEPTH_8UCH0-180S0-255V0-255
* @param inMat
* @param debug
*/
public static void getHSVValue(Mat inMat, Boolean debug, String tempPath) {
int nRows = inMat.rows();
int nCols = inMat.cols();
Map<Integer, Integer> map = Maps.newHashMap();
for (int i = 0; i < nRows; ++i) {
for (int j = 0; j < nCols; j += 3) {
int H = (int)inMat.get(i, j)[0];
// int S = (int)inMat.get(i, j)[1];
// int V = (int)inMat.get(i, j)[2];
if(map.containsKey(H)) {
int count = map.get(H);
map.put(H, count+1);
} else {
map.put(H, 1);
}
}
}
Set<Integer> set = map.keySet();
Object[] arr = set.toArray();
Arrays.sort(arr);
for (Object key : arr) {
System.out.println(key + ": " + map.get(key));
}
return;
}
/**
*
* https://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/53084090
* @param inMat
* @return
*/
public static Rect maxAreaRect(Mat threshold, Point point) {
int edge[] = new int[4];
edge[0] = (int) point.x + 1;//top
edge[1] = (int) point.y + 1;//right
edge[2] = (int) point.y - 1;//bottom
edge[3] = (int) point.x - 1;//left
boolean[] expand = { true, true, true, true};//扩展标记位
int n = 0;
while (expand[0] || expand[1] || expand[2] || expand[3]){
int edgeID = n % 4;
expand[edgeID] = expandEdge(threshold, edge, edgeID);
n++;
}
Point tl = new Point(edge[3], edge[0]);
Point br = new Point(edge[1], edge[2]);
return new Rect(tl, br);
}
/**
* @brief expandEdge
* @param img:8
* @param edge 4
* @param edgeID
* @return
*/
public static boolean expandEdge(Mat img, int edge[], int edgeID) {
int nc = img.cols();
int nr = img.rows();
switch (edgeID) {
case 0:
if (edge[0] > nr) {
return false;
}
for (int i = edge[3]; i <= edge[1]; ++i) {
if (img.get(edge[0], i)[0]== 255) {// 遇见255像素表明碰到边缘线
return false;
}
}
edge[0]++;
return true;
case 1:
if (edge[1] > nc) {
return false;
}
for (int i = edge[2]; i <= edge[0]; ++i) {
if (img.get(i, edge[1])[0] == 255)
return false;
}
edge[1]++;
return true;
case 2:
if (edge[2] < 0) {
return false;
}
for (int i = edge[3]; i <= edge[1]; ++i) {
if (img.get(edge[2], i)[0] == 255)
return false;
}
edge[2]--;
return true;
case 3:
if (edge[3] < 0) {
return false;
}
for (int i = edge[2]; i <= edge[0]; ++i) {
if (img.get(i, edge[3])[0] == 255)
return false;
}
edge[3]--;
return true;
default:
return false;
}
}
/**
*
* rowLimit != colsLimit, 使
*
* @param inMat
* @param rowLimit
* @param colsLimit
* @param debug
* @param tempPath
* @return
*/
public static Mat clearInnerHole(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
// 白色点较少,遍历白色点速度快
if (inMat.get(i, j)[0] == white && label.get(i, j)[0] == uncheck) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
// 遍历四条边
for (int k = x1; k < x2; k++) {
if(inMat.get(k, y1)[0] == black || inMat.get(k, y2)[0] == black) {
count++;
}
}
for (int k = y1; k < y2; k++) {
if(inMat.get(x1, k)[0] == black || inMat.get(x2, k)[0] == black) {
count++;
}
}
// 根据中心点+limit定位四个角生成一个矩形
// 矩形四条边都是白色,内部的黑点标记为 要被替换的对象
if(count == 0 ) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == black && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, white);
}
}
}
label.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearInnerHole") + "_clearInnerHole.jpg", dst);
Instant end = Instant.now();
System.out.println("clearInnerHole执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
*
*
* @param inMat 0255
* @param rowLimit
* @param colsLimit
* @param debug
* @param tempPath
*/
public static Mat clearHole(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == white) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
if(inMat.get(x1, y1)[0] == white) {// 左上角
count++;
}
if(inMat.get(x1, y2)[0] == white) { // 左下角
count++;
}
if(inMat.get(x2, y1)[0] == white) { // 右上角
count++;
}
if(inMat.get(x2, y2)[0] == white) { // 右下角
count++;
}
// 根据中心点+limit定位四个角生成一个矩形
// 将四个角都是白色的矩形,内部的黑点标记为 要被替换的对象
if(count >=4 ) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == black && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, white); // 黑色替换成白色
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearHole") + "_clearHole.jpg", dst);
Instant end = Instant.now();
System.out.println("clearHole执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
*
*
* @param inMat
* @param rowLimit
* @param colsLimit
* @param debug
* @param tempPath
* @return
*/
public static Mat clearSmallConnArea(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == black) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
if(inMat.get(x1, y1)[0] == black) {// 左上角
count++;
}
if(inMat.get(x1, y2)[0] == black) { // 左下角
count++;
}
if(inMat.get(x2, y1)[0] == black) { // 右上角
count++;
}
if(inMat.get(x2, y2)[0] == black) { // 右下角
count++;
}
// 根据 中心点+limit定位四个角生成一个矩形
// 将四个角都是黑色的矩形,内部的白点标记为 要被替换的对象
if(count >= 4) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, black); // 白色替换成黑色
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearSmallConnArea") + "_clearSmallConnArea.jpg", dst);
Instant end = Instant.now();
System.out.println("clearSmallConnArea执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
*
* 45
* @param inMat
* @param limit
* @param angle
* @param debug
* @param tempPath
* @return
*/
public static Mat clearAngleConn(Mat inMat, int limit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == black) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
int x1 = i;
int x2 = i + limit >= inMat.rows() ? inMat.rows() - 1 : i + limit;
int y1 = j;
int y2 = j + limit >= inMat.cols() ? inMat.cols() - 1 : j + limit ;
// 根据 中心点+limit定位四个角生成一个矩形
// 将2个角都是黑色的线内部的白点标记为 要被替换的对象
// 【\】 斜对角线
if(inMat.get(x1, y1)[0] == black && inMat.get(x2, y2)[0] == black) {
for (int n = x1, m = y1; n < x2 && m < y2; n++, m++) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
if(inMat.get(x1, y2)[0] == black && inMat.get(x2, y1)[0] == black) {
// 【/】 斜对角线
for (int n = x1, m = y2; n < x2 && m > y1; n++, m--) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
}
}
// 白色替换成黑色
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, black);
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearAngleConn") + "_clearAngleConn.jpg", dst);
Instant end = Instant.now();
System.out.println("clearAngleConn执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
}

@ -0,0 +1,546 @@
package com.yuxue.util;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.ml.ANN_MLP;
import org.opencv.ml.SVM;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yuxue.constant.Constant;
import com.yuxue.enumtype.Direction;
import com.yuxue.enumtype.PlateColor;
import com.yuxue.train.SVMTrain;
/**
*
*
*
*
* @author yuxue
* @date 2020-05-28 15:11
*/
public class PlateUtil {
// 车牌定位处理步骤该map用于表示步骤图片的顺序
private static Map<String, Integer> debugMap = Maps.newLinkedHashMap();
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
debugMap.put("platePredict", 0);
debugMap.put("colorMatch", 0);
debugMap.put("plateThreshold", 0);
debugMap.put("plateContours", 0);
debugMap.put("plateRect", 0);
debugMap.put("plateCrop", 0);
debugMap.put("char_clearLiuDing", 0); // 去除柳钉
debugMap.put("specMat", 0);
debugMap.put("chineseMat", 0);
debugMap.put("char_auxRoi", 0);
// 设置index 用于debug生成文件时候按名称排序
Integer index = 200;
for (Entry<String, Integer> entry : debugMap.entrySet()) {
entry.setValue(index);
index ++;
}
// 这个位置加载模型文件会报错,暂时没时间定位啥问题报错
/*loadSvmModel("D:/PlateDetect/train/plate_detect_svm/svm2.xml");
loadAnnModel("D:/PlateDetect/train/chars_recognise_ann/ann.xml");*/
}
private static SVM svm = SVM.create();
private static ANN_MLP ann=ANN_MLP.create();
public static void loadSvmModel(String path) {
svm.clear();
svm=SVM.load(path);
}
// 加载ann配置文件 图像转文字的训练库文件
public static void loadAnnModel(String path) {
ann.clear();
ann = ANN_MLP.load(path);
}
public static void main(String[] args) {
/*System.err.println(PalteUtil.isPlate("粤AI234K"));
System.err.println(PalteUtil.isPlate("鄂CD3098"));*/
}
/**
*
* @param str
* @return
*/
public static Boolean isPlate(String str) {
Pattern p = Pattern.compile(Constant.plateReg);
Boolean bl = false;
Matcher m = p.matcher(str);
while(m.find()) {
bl = true;
break;
}
return bl;
}
/**
*
* @param inMat
* @param dst
*/
public static void hasPlate(Vector<Mat> inMat, Vector<Mat> dst, Boolean debug, String tempPath) {
int i = 0;
for (Mat src : inMat) {
if(src.rows() == Constant.DEFAULT_HEIGHT && src.cols() == Constant.DEFAULT_WIDTH) {
Mat samples = SVMTrain.getFeature(src);
float flag = svm.predict(samples);
if (flag == 0) {
dst.add(src);
if(debug) {
System.err.println("目标符合");
Imgcodecs.imwrite(tempPath + debugMap.get("platePredict") + "_platePredict" + i + ".png", src);
}
i++;
} else {
System.out.println("目标不符合");
}
} else {
System.err.println("非法图块");
}
}
return;
}
/**
*
* @param inMat
* @return
*/
public static PlateColor getPlateColor(Mat inMat, Boolean adaptive_minsv, Boolean debug, String tempPath) {
// 判断阈值
final float thresh = 0.70f;
if(colorMatch(inMat, PlateColor.GREEN, adaptive_minsv, debug, tempPath) > thresh) {
return PlateColor.GREEN;
}
if(colorMatch(inMat, PlateColor.YELLOW, adaptive_minsv, debug, tempPath) > thresh) {
return PlateColor.YELLOW;
}
if(colorMatch(inMat, PlateColor.BLUE, adaptive_minsv, debug, tempPath) > thresh) {
return PlateColor.BLUE;
}
return PlateColor.UNKNOWN;
}
/**
*
* @param inMat
* @param r
* @param adaptive_minsv
* @param debug
* @param tempPath
* @return
*/
public static Float colorMatch(Mat inMat, PlateColor r, Boolean adaptive_minsv, Boolean debug, String tempPath) {
final float max_sv = 255;
final float minref_sv = 64;
final float minabs_sv = 95;
Mat hsvMat = ImageUtil.rgb2Hsv(inMat, debug, tempPath);
// 匹配模板基色,切换以查找想要的基色
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);
for (int i = 0; i < hsvMat.rows(); ++i) {
for (int j = 0; j < hsvMat.cols(); j += 3) {
int H = (int)hsvMat.get(i, j)[0];
int S = (int)hsvMat.get(i, j)[1];
int V = (int)hsvMat.get(i, j)[2];
boolean colorMatched = false;
if ( min_h < H && H <= max_h) {
int Hdiff = Math.abs(H - avg_h);
float Hdiff_p = Hdiff / diff_h;
float min_sv = 0;
if (adaptive_minsv) {
min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p);
} else {
min_sv = minabs_sv;
}
if ((min_sv < S && S <= max_sv) && (min_sv < V && V <= max_sv)) {
colorMatched = true;
}
}
if (colorMatched == true) {
hsvMat.put(i, j, 0, 0, 255);
} else {
hsvMat.put(i, j, 0, 0, 0);
}
}
}
// 获取颜色匹配后的二值灰度图
List<Mat> hsvSplit = Lists.newArrayList();
Core.split(hsvMat, hsvSplit);
Mat gray = hsvSplit.get(2);
float percent = (float) Core.countNonZero(gray) / (gray.rows() * gray.cols());
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("colorMatch") + "_colorMatch.jpg", gray);
}
return percent;
}
/**
*
* @param inMat
* @param charMat vector
* @param debug
* @param tempPath
*/
public static final int DEFAULT_ANGLE = 30; // 角度判断所用常量
public static void charsSegment(Mat inMat, PlateColor color, Vector<Mat> charMat, Boolean debug, String tempPath) {
Mat gray = new Mat();
Imgproc.cvtColor(inMat, gray, Imgproc.COLOR_BGR2GRAY);
Mat threshold = new Mat();
switch (color) {
case BLUE:
Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
break;
case YELLOW:
Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY_INV);
break;
case GREEN:
Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY_INV);
break;
default:
return;
}
// 图片处理,降噪等
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("plateThreshold") + "_plateThreshold.jpg", threshold);
}
// 获取轮廓
Mat contour = new Mat();
threshold.copyTo(contour);
List<MatOfPoint> contours = Lists.newArrayList();
// 提取外部轮廓
Imgproc.findContours(contour, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
if (debug) {
Mat result = new Mat();
inMat.copyTo(result);
Imgproc.drawContours(result, contours, -1, new Scalar(0, 0, 255, 255));
Imgcodecs.imwrite(tempPath + debugMap.get("plateContours") + "_plateContours.jpg", result);
}
Vector<Rect> rt = new Vector<Rect>();
for (int i = 0; i < contours.size(); i++) {
Rect mr = Imgproc.boundingRect(contours.get(i));
/*if(debug) {
Mat mat = new Mat(threshold, mr);
Imgcodecs.imwrite(tempPath + debugMap.get("plateRect") + "_plateRect_" + i + ".jpg", mat);
}*/
if (checkCharSizes(mr)) {
rt.add(mr);
}
}
if(null == rt || rt.size() <= 0) {
return;
}
Vector<Rect> sorted = new Vector<Rect>();
sortRect(rt, sorted);
String plate = "";
Vector<Mat> dst = new Vector<Mat>();
for (int i = 0; i < sorted.size(); i++) {
Mat img_crop = new Mat(threshold, sorted.get(i));
img_crop = preprocessChar(img_crop);
dst.add(img_crop);
if(debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("plateCrop") + "_plateCrop_" + i + ".jpg", img_crop);
}
Mat f = features(img_crop, Constant.predictSize);
// 字符预测
Mat output = new Mat(1, 140, CvType.CV_32F);
int index = (int) ann.predict(f, output, 0);
if (index < Constant.numCharacter) {
plate += String.valueOf(Constant.strCharacters[index]);
} else {
String s = Constant.strChinese[index - Constant.numCharacter];
plate += Constant.KEY_CHINESE_MAP.get(s);
}
}
System.err.println("===>" + plate);
return;
}
/**
* :
* @param in
* @return
*/
final static int CHAR_SIZE = 20;
private static Mat preprocessChar(Mat in) {
int h = in.rows();
int w = in.cols();
Mat transformMat = Mat.eye(2, 3, CvType.CV_32F);
int m = Math.max(w, h);
transformMat.put(0, 2, (m - w) / 2f);
transformMat.put(1, 2, (m - h) / 2f);
Mat warpImage = new Mat(m, m, in.type());
Imgproc.warpAffine(in, warpImage, transformMat, warpImage.size(), Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, new Scalar(0));
Mat resized = new Mat(CHAR_SIZE, CHAR_SIZE, CvType.CV_8UC3);
Imgproc.resize(warpImage, resized, resized.size(), 0, 0, Imgproc.INTER_CUBIC);
return resized;
}
/**
*
* 1
* @param r
* @return
*/
public static Boolean checkCharSizes(Rect r) {
float minHeight = 15f;
float maxHeight = 35f;
double charAspect = r.size().width / r.size().height;
return charAspect <1 && minHeight <= r.size().height && r.size().height < maxHeight;
}
/**
* Rect
* @param vecRect
* @param out
* @return
*/
public static void sortRect(Vector<Rect> vecRect, Vector<Rect> out) {
Map<Integer, Integer> map = Maps.newHashMap();
for (int i = 0; i < vecRect.size(); ++i) {
map.put(vecRect.get(i).x, i);
}
Set<Integer> set = map.keySet();
Object[] arr = set.toArray();
Arrays.sort(arr);
for (Object key : arr) {
out.add(vecRect.get(map.get(key)));
}
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];
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 = 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;
}
public static Mat features(Mat in, int sizeData) {
float[] vhist = projectedHistogram(in, Direction.VERTICAL);
float[] hhist = projectedHistogram(in, Direction.HORIZONTAL);
Mat lowData = new Mat();
if (sizeData > 0) {
Imgproc.resize(in, lowData, new Size(sizeData, sizeData));
}
int numCols = vhist.length + hhist.length + lowData.cols() * lowData.rows();
Mat out = new Mat(1, numCols, CvType.CV_32F);
int j = 0;
for (int i = 0; i < vhist.length; ++i, ++j) {
out.put(0, j, vhist[i]);
}
for (int i = 0; i < hhist.length; ++i, ++j) {
out.put(0, j, hhist[i]);
}
for (int x = 0; x < lowData.cols(); x++) {
for (int y = 0; y < lowData.rows(); y++, ++j) {
double[] val = lowData.get(x, y);
out.put(0, j, val[0]);
}
}
return out;
}
/**
*
* @param inMat
* @return
*/
public static Mat dilate(Mat inMat) {
Mat result = inMat.clone();
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2));
Imgproc.dilate(inMat, result, element);
return result;
}
/**
*
* @param inMat
* @return
*/
public static Mat erode(Mat inMat) {
Mat result = inMat.clone();
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2));
Imgproc.erode(inMat, result, element);
return result;
}
/**
*
* @param inMat
* @return
*/
public static Mat randTranslate(Mat inMat) {
Random rand = new Random();
Mat result = inMat.clone();
int ran_x = rand.nextInt(10000) % 5 - 2; // 控制在-2~3个像素范围内
int ran_y = rand.nextInt(10000) % 5 - 2;
return translateImg(result, ran_x, ran_y);
}
/**
*
* @param inMat
* @return
*/
public static Mat randRotate(Mat inMat) {
Random rand = new Random();
Mat result = inMat.clone();
float angle = (float) (rand.nextInt(10000) % 15 - 7); // 旋转角度控制在-7~8°范围内
return rotateImg(result, angle);
}
/**
*
* @param img
* @param offsetx
* @param offsety
* @return
*/
public static Mat translateImg(Mat img, int offsetx, int offsety){
Mat dst = new Mat();
//定义平移矩阵
Mat trans_mat = Mat.zeros(2, 3, CvType.CV_32FC1);
trans_mat.put(0, 0, 1);
trans_mat.put(0, 2, offsetx);
trans_mat.put(1, 1, 1);
trans_mat.put(1, 2, offsety);
Imgproc.warpAffine(img, dst, trans_mat, img.size()); // 仿射变换
return dst;
}
/**
*
* @param source
* @param angle
* @return
*/
public static Mat rotateImg(Mat source, float angle){
Point src_center = new Point(source.cols() / 2.0F, source.rows() / 2.0F);
Mat rot_mat = Imgproc.getRotationMatrix2D(src_center, angle, 1);
Mat dst = new Mat();
// 仿射变换 可以考虑使用投影变换; 这里使用放射变换进行旋转,对于实际效果来说感觉意义不大,反而会干扰结果预测
Imgproc.warpAffine(source, dst, rot_mat, source.size());
return dst;
}
}
Loading…
Cancel
Save