the1dog 4 years ago
commit 07759933e0

@ -0,0 +1,36 @@
package com.yuxue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import lombok.extern.slf4j.Slf4j;
/**
* spring boot
* @author yuxue
* @date 2019-12-06
*/
@SpringBootApplication
@MapperScan("mapper")
@EnableScheduling //开启对定时任务的支持
@Slf4j
public class Application {
public static void main(String[] args) {
String version = System.getProperty("java.version");
if (Integer.parseInt(
version.substring(0,1)) == 1
&& Integer.parseInt(version.substring(2, 3)) >= 8
&& Integer.parseInt(version.substring(6)) >= 60
|| Integer.parseInt(version.substring(0,1))>=9) {
SpringApplication.run(Application.class, args);
} else {
log.error("java version need greater than 1.8.60, and do not use open jdk !!!");
}
}
}

@ -0,0 +1,22 @@
package com.yuxue.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* controllerapi
*
* @author yuxue
* @date 2019-08-19
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetExclude {
String value() default "";
}

@ -0,0 +1,42 @@
package com.yuxue.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import com.yuxue.annotation.RetExclude;
import com.yuxue.entity.Result;
import lombok.extern.slf4j.Slf4j;
/**
* method
* @author yuxue
* @date 2019-08-20
*/
@Slf4j
public class AroundMethod implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = null;
try {
ret = methodInvocation.proceed();
if(ret instanceof Result) { // 防止出现二次封装
return ret;
}
} catch (Throwable e) {
// 运行时出现异常抛出由ResultReturnExceptionHandler统一处理
throw e;
}
RetExclude re = methodInvocation.getMethod().getAnnotation(RetExclude.class);
if(null != re && null != re.value()) {
log.info("api添加了封装排除注解");
return ret;
}
// log.info("封装返回值");
return Result.ok(ret);
}
}

@ -0,0 +1,34 @@
package com.yuxue.aop;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* aop
* @author yuxue
* @date 2019-08-20
*/
@Configuration
@ConditionalOnMissingBean(DefaultPointcutAdvisor.class)
public class DefaultAopConfig {
// @Value("${test.aop.pointcut:com.yuxue..*.controller..*.*(..)}")
@Value("${test.aop.pointcut:com.yuxue.controller..*.*(..)}")
private String pattern;
@Bean("resultAop")
public DefaultPointcutAdvisor resultAop() {
DefaultPointcutAdvisor pfb = new DefaultPointcutAdvisor();
JdkRegexpMethodPointcut j = new JdkRegexpMethodPointcut();
j.setPattern(pattern);
AroundMethod method = new AroundMethod();
pfb.setAdvice(method);
pfb.setPointcut(j);
return pfb;
}
}

@ -0,0 +1,56 @@
package com.yuxue.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* controller aop
* @author yuxue
* @date 2018-09-07
*/
@Aspect
@Slf4j
@Component
public class WebAop {
@Pointcut("execution(* com.yuxue.controller..*.*(..))")
public void webLog() {}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("====================");
log.info("Cookie: " + request.getHeader("Cookie"));
log.info(request.getMethod() + "=>" + request.getRequestURL().toString());
log.info("IP: " + request.getRemoteAddr());
log.info("CLASS_METHOD: "
+ joinPoint.getSignature().getDeclaringTypeName()
+ "."
+ joinPoint.getSignature().getName());
log.info("ARGS: " + Arrays.toString(joinPoint.getArgs()));
log.info("====================\n");
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 关闭: 返回前进行内容结果日志输出
log.info("RESPONSE: " + ret);
log.info("====================\n");
}
}

@ -0,0 +1,33 @@
package com.yuxue.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
*
*
*/
@Slf4j
@Component
public class CommandRunner implements CommandLineRunner {
@Value("${server.port}")
private String port;
@Override
public void run(String... args) {
try {
String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows")) {
// 默认浏览器打开
// Runtime.getRuntime().exec("cmd /c start http://localhost:" + port + "/index");
}
} catch (Exception ex) {
ex.printStackTrace();
log.error("打开默认浏览器异常", ex);
}
}
}

@ -0,0 +1,40 @@
package com.yuxue.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*
* @CrossOrigin
* @author yuxue
* @date 2018-09-07
*/
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
/** * 至少需要addMapping *** */
registry
.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("PUT", "DELETE", "GET", "POST", "OPTIONS", "HEAD")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Authorization", "Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers")
.allowCredentials(true)//是否带上cookie
.maxAge(3600)
.exposedHeaders(
"access-control-allow-headers",
"access-control-allow-methods",
"access-control-allow-origin",
"access-Control-allow-credentials",
"access-control-max-age",
"X-Frame-Options");
}
};
}
}

@ -0,0 +1,26 @@
package com.yuxue.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
/**
*
* @author yuxue
* @date 2019-06-13
*/
@Configuration
public class DefaultMvcConfig {
/**
*
*
* @return
*/
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}

@ -0,0 +1,48 @@
package com.yuxue.config;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.google.common.collect.Lists;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* druid,sql
*/
@Configuration
public class DruidConfig {
// 这个注解读取配置文件前缀为prefix的配置将外部的配置文件与这里绑定
// 容器的开启与关闭
@ConfigurationProperties(prefix = "spring.druid")
@Bean(initMethod = "init", destroyMethod = "close")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setProxyFilters(Lists.newArrayList(statFilter()));
return dataSource;
}
// bean注解成为spring的bean利用filter将慢sql的日志打印出来
//@Bean
public Filter statFilter() {
StatFilter statFilter = new StatFilter();
// 多长时间定义为慢sql这里定义为5s
statFilter.setSlowSqlMillis(5000);
// 是否打印出慢日志
statFilter.setLogSlowSql(true);
// 是否将日志合并起来
statFilter.setMergeSql(true);
return statFilter;
}
// 这是配置druid的监控
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
}
}

@ -0,0 +1,29 @@
package com.yuxue.config;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
@Configuration
public class LocalDateTimeSerializerConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String pattern;
@Bean
public LocalDateTimeSerializer localDateTimeDeserializer() {
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
}
}

@ -0,0 +1,32 @@
package com.yuxue.config;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
*
* @author yuxue
* @date 2019-06-13
*/
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("i18n");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] split = l.split("_");
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}

@ -0,0 +1,30 @@
package com.yuxue.config;
import com.github.pagehelper.PageInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
*
* @author yuxue
* @date 2018-09-07
*/
@Configuration
public class PageHelperConfig {
@Value("${pagehelper.helperDialect}")
private String helperDialect;
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", helperDialect);
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
}

@ -0,0 +1,51 @@
package com.yuxue.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* swagger-annotations jar 1.5.X, io.swagger.annotations.API description
* swagger使Web api 使 description Controller api
* 使tag便
* Controllertagkeytagvalue
* @ApiOperation valueapiapi 120
* @ApiOperation notesapiapi
* @ApiOperation produces apijsonutf8
*/
/**
* swagger2
* @author yuxue
* @date 2018-09-07
*/
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Image Recognition API")
.description("图像识别技术")
.version("1.0.0")
.build();
}
}

@ -0,0 +1,51 @@
package com.yuxue.config;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@ComponentScan("com.yuxue.auth.service.impl")
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
@Bean(name = "taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(4);
// 设置最大线程数
executor.setMaxPoolSize(8);
// 设置队列容量
executor.setQueueCapacity(100);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("localThread:");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
@Override
public Executor getAsyncExecutor() {
return taskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}

@ -0,0 +1,126 @@
package com.yuxue.constant;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author yuxue
* @date 2018-09-07
*/
public class Constant {
public static final String UTF8 = "UTF-8";
// 车牌识别, 默认车牌图片保存路径
// public static String DEFAULT_DIR = "./PlateDetect/"; // 使用项目的相对路径
public static String DEFAULT_DIR = "D:/PlateDetect/"; // 使用盘符的绝对路径
// 车牌识别, 默认车牌图片处理过程temp路径
// public static String DEFAULT_TEMP_DIR = "./PlateDetect/temp/"; // 使用项目的相对路径
public static String DEFAULT_TEMP_DIR = "D:/PlateDetect/temp/"; // 使用盘符的绝对路径
// 车牌识别,默认处理图片类型
public static String DEFAULT_TYPE = "png,jpg,jpeg";
public static String DEFAULT_ANN_PATH = "res/model/ann.xml";
//public static String DEFAULT_ANN_PATH = "D:/PlateDetect/train/chars_recognise_ann/ann.xml";
public static String DEFAULT_SVM_PATH = "res/model/svm.xml";
public static final int DEFAULT_WIDTH = 136; // cols
public static final int DEFAULT_HEIGHT = 36; // rows
// 车牌识别,判断是否车牌的正则表达式
public static String plateReg = "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1})";
public static int predictSize = 10;
public static int neurons = 40;
// 中国车牌; 34个字符; 没有 字母I、字母O
public final static char strCharacters[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', /* 没有I */ 'J', 'K', 'L', 'M', 'N', /* 没有O */'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
// 没有I和0, 10个数字与24个英文字符之和
public final static Integer numCharacter = strCharacters.length;
// 并不全面,有些省份没有训练数据所以没有字符
// 有些后面加数字2的表示在训练时常看到字符的一种变形也作为训练数据存储
public final static String strChinese[] = {
"zh_cuan", /*川*/
"zh_e", /*鄂*/
"zh_gan", /*赣*/
"zh_gan1", /*甘*/
"zh_gui", /*贵*/
"zh_gui1", /*桂*/
"zh_hei", /*黑*/
"zh_hu", /*沪*/
"zh_ji", /*冀*/
"zh_jin", /*津*/
"zh_jing", /*京*/
"zh_jl", /*吉*/
"zh_liao", /*辽*/
"zh_lu", /*鲁*/
"zh_meng", /*蒙*/
"zh_min", /*闽*/
"zh_ning", /*宁*/
"zh_qing", /*青*/
"zh_qiong", /*琼*/
"zh_shan", /*陕*/
"zh_su", /*苏*/
"zh_sx", /*晋*/
"zh_wan", /*皖*/
"zh_xiang", /*湘*/
"zh_xin", /*新*/
"zh_yu", /*豫*/
"zh_yu1", /*渝*/
"zh_yue", /*粤*/
"zh_yun", /*云*/
"zh_zang", /*藏*/
"zh_zhe" /*浙*/
};
/* 34+31=65 34个字符跟31个汉字 */
public final static Integer numAll = strCharacters.length + strChinese.length;
public static Map<String, String> KEY_CHINESE_MAP = new HashMap<String, String>();
static {
if (KEY_CHINESE_MAP.isEmpty()) {
KEY_CHINESE_MAP.put("zh_cuan", "川");
KEY_CHINESE_MAP.put("zh_e", "鄂");
KEY_CHINESE_MAP.put("zh_gan", "赣");
KEY_CHINESE_MAP.put("zh_gan1", "甘");
KEY_CHINESE_MAP.put("zh_gui", "贵");
KEY_CHINESE_MAP.put("zh_gui1", "桂");
KEY_CHINESE_MAP.put("zh_hei", "黑");
KEY_CHINESE_MAP.put("zh_hu", "沪");
KEY_CHINESE_MAP.put("zh_ji", "冀");
KEY_CHINESE_MAP.put("zh_jin", "津");
KEY_CHINESE_MAP.put("zh_jing", "京");
KEY_CHINESE_MAP.put("zh_jl", "吉");
KEY_CHINESE_MAP.put("zh_liao", "辽");
KEY_CHINESE_MAP.put("zh_lu", "鲁");
KEY_CHINESE_MAP.put("zh_meng", "蒙");
KEY_CHINESE_MAP.put("zh_min", "闽");
KEY_CHINESE_MAP.put("zh_ning", "宁");
KEY_CHINESE_MAP.put("zh_qing", "青");
KEY_CHINESE_MAP.put("zh_qiong", "琼");
KEY_CHINESE_MAP.put("zh_shan", "陕");
KEY_CHINESE_MAP.put("zh_su", "苏");
KEY_CHINESE_MAP.put("zh_sx", "晋");
KEY_CHINESE_MAP.put("zh_wan", "皖");
KEY_CHINESE_MAP.put("zh_xiang", "湘");
KEY_CHINESE_MAP.put("zh_xin", "新");
KEY_CHINESE_MAP.put("zh_yu", "豫");
KEY_CHINESE_MAP.put("zh_yu1", "渝");
KEY_CHINESE_MAP.put("zh_yue", "粤");
KEY_CHINESE_MAP.put("zh_yun", "云");
KEY_CHINESE_MAP.put("zh_zang", "藏");
KEY_CHINESE_MAP.put("zh_zhe", "浙");
}
}
}

@ -0,0 +1,41 @@
package com.yuxue.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.yuxue.annotation.RetExclude;
import springfox.documentation.annotations.ApiIgnore;
@ApiIgnore
@Controller
public class CommonController {
@RetExclude
@RequestMapping(value = "", method = { RequestMethod.GET })
public String doc() {
return "redirect:swagger-ui.html";
}
@RetExclude
@RequestMapping(value = "login", method = { RequestMethod.GET })
public String loginPage() {
return "home/login";
}
@RetExclude
@RequestMapping(value = "index", method = { RequestMethod.GET })
public String indexPage() {
return "home/index";
}
@RetExclude
@RequestMapping(value = "unauthorized", method = { RequestMethod.GET })
public String unauthorizedPage() {
return "unauthorized";
}
}

@ -0,0 +1,49 @@
package com.yuxue.controller;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
/**
*
* Detects faces in an image, draws boxes around them,
* and writes the results to "faceDetection.png".
*/
public class FaceController {
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
public static void main(String[] args) {
// Create a face detector from the cascade file in the resources directory.
// 创建识别器
CascadeClassifier faceDetector = new CascadeClassifier("/src/main/resources/haarcascades/lbpcascade_frontalface.xml");
String imgPath = "/src/main/resources/DetectFace/AverageMaleFace.jpg";
Mat image = Imgcodecs.imread(imgPath);
Mat dst = new Mat();
Imgproc.Canny(image, dst, 130, 250);
// Detect faces in the image. MatOfRect is a special container class for Rect.
MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(dst, faceDetections);
System.out.println(String.format("识别出 %s 张人脸", faceDetections.toArray().length));
// Draw a bounding box around each face.
for (Rect rect : faceDetections.toArray()) {
// Core.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
}
// Save the visualized detection.
// System.out.println(String.format("Writing %s", filename));
//Highgui.imwrite(filename, image);
}
}

@ -0,0 +1,90 @@
package com.yuxue.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.yuxue.annotation.RetExclude;
import com.yuxue.exception.ResultReturnException;
import com.yuxue.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@Api(description = "文件管理")
@RestController
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService service;
/**
*
* D:\\PlateDetect\\ png,jpg,jpeg
* list
* @param dir
* @return
*/
@ApiOperation(value = "获取文件结构", notes = "")
@ApiImplicitParam(name = "dir", value = "文件夹路径", required = true, paramType = "query", dataType = "String")
@RequestMapping(value = "/getFileTreeByDir", method = RequestMethod.GET)
public Object getFileTreeByDir(String dir, String typeFilter) {
try {
if(null != dir) {
dir = URLDecoder.decode(dir, "utf-8");
}
} catch (UnsupportedEncodingException e) {
throw new ResultReturnException("dir参数异常");
}
return service.getFileTreeByDir(dir, typeFilter);
}
/**
*
* @param filePath
* @param response
* @return
* @throws IOException
*/
@RetExclude
@ApiOperation(value = "预览文件", notes = "根据路径,直接读取盘符文件; 返回输出流")
@GetMapping(value = "/readFile", produces= {"image/jpeg"})
public ResponseEntity<InputStreamResource> readFile(String filePath, HttpServletResponse response) throws IOException {
try {
filePath = URLDecoder.decode(filePath, "utf-8");
} catch (UnsupportedEncodingException e) {
throw new ResultReturnException("filePath参数异常");
}
//文件输出流,输出到客户端
File file = service.readFile(filePath);
InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<InputStreamResource>(isr, headers, HttpStatus.OK);
}
}

@ -0,0 +1,49 @@
package com.yuxue.controller;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
/**
* opencv demo
* opencv
* windows
* 1openvphttps://opencv.org/releases/page/2/ 当前使用4.0.1版本
* 2exe \build\java\x64\opencv_java401.dll \build\x64\vc14\bin\
* 3eclipseUser Libraries
* 4build pathlib
*
* demoopencvdemo使maven
*
* @author yuxue
* @date 2020-04-22 14:04
*/
public class OpencvDemo {
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
public static void main(String[] args) {
System.out.println("Welcome to OpenCV " + Core.VERSION);
Mat m1 = Mat.eye(3, 3, CvType.CV_8UC1);
System.out.println("m = " + m1.dump());
System.err.println("==================");
Mat m = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0));
System.out.println("OpenCV Mat: " + m);
Mat mr1 = m.row(1);
mr1.setTo(new Scalar(1));
Mat mc5 = m.col(5);
mc5.setTo(new Scalar(5));
System.out.println("OpenCV Mat data:\n" + m.dump());
}
}

@ -0,0 +1,92 @@
package com.yuxue.controller;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.yuxue.exception.ResultReturnException;
import com.yuxue.service.PlateService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@Api(description = "车牌识别")
@RestController
@RequestMapping("/plate")
public class PlateController {
@Autowired
private PlateService service;
/**
* d:/PlateDetect
*
* temp
*/
@ApiOperation(value = "更新IMG文件基础信息", notes = "")
@RequestMapping(value = "/refreshFileInfo", method = RequestMethod.GET)
public void refreshFileInfo() {
service.refreshFileInfo();
}
/**
*
*
* ; 线
*/
@ApiOperation(value = "图片车牌识别", notes = "路径不能包含中文opencv路径转码过程乱码会报异常")
@RequestMapping(value = "/recogniseAll", method = RequestMethod.GET)
public Object recogniseAll() {
return service.recogniseAll();
}
/**
*
* path
* temp/timestamptimestamp
* temp
*
*/
@ApiOperation(value = "图片车牌识别", notes = "路径不能包含中文opencv路径转码过程乱码会报异常")
@ApiImplicitParams({
@ApiImplicitParam(name = "filePath", value = "文件路径", required = true, paramType = "query", dataType = "String"),
@ApiImplicitParam(name = "reRecognise", value = "重新识别", paramType = "query", dataType = "Boolean", defaultValue="false")
})
@RequestMapping(value = "/recognise", method = RequestMethod.GET)
public Object recognise(String filePath, Boolean reRecognise) {
try {
if(null != filePath) {
filePath = URLDecoder.decode(filePath, "utf-8");
}
if(null == reRecognise) {
reRecognise = false;
}
} catch (UnsupportedEncodingException e) {
throw new ResultReturnException("filePath参数异常");
}
return service.recognise(filePath, reRecognise);
}
@ApiOperation(value = "获取处理步骤", notes = "")
@RequestMapping(value = "/getProcessStep", method = RequestMethod.GET)
public Object getProcessStep() {
return service.getProcessStep();
}
}

@ -0,0 +1,84 @@
package com.yuxue.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.yuxue.entity.SystemMenuEntity;
import com.yuxue.service.SystemMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@Api(description = "菜单管理")
@RestController
@RequestMapping("/systemMenu")
public class SystemMenuController {
@Autowired
private SystemMenuService service;
/**
*
* @param pageNo
* @param pageSize
* @param entity
*/
@ApiOperation(value = "分页获取记录", notes = "分页获取记录")
@ApiImplicitParams({
@ApiImplicitParam(name = "pageNo", value = "当前页码", required = true, paramType = "query", dataType = "Integer", defaultValue = "1"),
@ApiImplicitParam(name = "pageSize", value = "每页数量", required = true, paramType = "query", dataType = "Integer", defaultValue = "10"),
@ApiImplicitParam(name = "map", value = "举例:{} or {\"name\":\"张三\"}", dataType = "entity")
})
@RequestMapping(value = "/queryByPage", method = RequestMethod.POST)
public Object queryByPage(@RequestParam Integer pageNo, @RequestParam Integer pageSize, @RequestBody Map<String, Object> map) {
return service.queryByPage(pageNo, pageSize, map);
}
@ApiOperation(value = "按条件查询", notes = "不分页", response = SystemMenuEntity.class)
@ApiImplicitParams({
@ApiImplicitParam(name = "map", value = "举例:{} or {\"name\":\"张三\"}", dataType = "entity")
})
@RequestMapping(value = "/queryByCondition", method = RequestMethod.POST)
public Object queryByCondition(@RequestBody Map<String, Object> map) {
return service.queryByCondition(map);
}
/**
* PostID
* @param entity
*/
@ApiOperation(value = "新增数据成功返回ID", notes = "新增数据成功返回ID")
@ApiImplicitParam(name = "entity", value = "举例:{} or {\"name\":\"张三\"}", required = true, dataType = "entity")
@RequestMapping(value = "", method = RequestMethod.POST)
public Object save(@RequestBody SystemMenuEntity entity) {
return service.save(entity);
}
/**
*
* @return
*/
@ApiOperation(value = "获取登录用户菜单", notes = "")
@GetMapping("/getUserMenu")
public Object getUserMenu() {
return service.getUserMenu();
}
}

@ -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,81 @@
package com.yuxue.entity;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* t_plate_file
* @author yuxue
* 2020-04-30 11:04:47.169
*/
@Data
@NoArgsConstructor
public class PlateFileEntity implements Serializable {
/**
* id
*/
private Integer id;
/**
* fileName
*/
private String fileName;
/**
* filePath
*/
private String filePath;
/**
* fileType
*/
private String fileType;
/**
* fileLength
*/
private Integer fileLength;
/**
* plate
*/
private String plate;
/**
* plateColor
*/
private String plateColor;
/**
* lastRecoTime
*/
private String lastRecoTime;
/**
* tempPath
*/
private String tempPath;
/**
* recoPlate
*/
private String recoPlate;
/**
* recoColor
*/
private String recoColor;
/**
* recoCorrect
* 0 1 2 3
*/
private Integer recoCorrect;
private List<PlateRecoDebugEntity> debug;
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,66 @@
package com.yuxue.entity;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* t_plate_reco_debug
* @author yuxue
* 2020-04-30 16:17:58.795
*/
@Data
@NoArgsConstructor
public class PlateRecoDebugEntity implements Serializable {
/**
* id
*/
private Integer id;
/**
* parentId
*/
private Integer parentId;
/**
* fileName
*/
private String fileName;
/**
* filePath
*/
private String filePath;
/**
* debugType
*/
private String debugType;
/**
* fileLength
*/
private Integer fileLength;
/**
* lastRecoTime
*/
private String lastRecoTime;
/**
* recoPlate
*/
private String recoPlate;
/**
* plateColor
*/
private String plateColor;
/**
* sort
*/
private Integer sort;
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,93 @@
package com.yuxue.entity;
import java.util.HashMap;
import com.yuxue.exception.ErrorEnum;
/**
*
* @author yuxue
* @date 2018-09-07
*/
public class Result extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
private static final Integer SUCCESS_CODE = 200;
private static final String SUCCESS_INFO = "Success!";
public Result() {
put("code", SUCCESS_CODE);
put("msg", SUCCESS_INFO);
put("success", true);
}
public Result(Object obj) {
put("code", SUCCESS_CODE);
put("msg", SUCCESS_INFO);
put("obj", obj);
put("success", true);
}
public static Result ok() {
return new Result();
}
public static Result ok(Object obj) {
return new Result(obj);
}
/**
*
*
*
* @param todo
* @return
*/
public static Result ok(Object obj, Object todo) {
Result result = new Result(obj);
result.put("todo", todo);
return result;
}
public static Result error() {
return error(ErrorEnum.COMMON_ERROR);
}
public static Result error(String msg) {
Result result = error(ErrorEnum.COMMON_ERROR);
result.put("msg", msg);
return result;
}
public static Result error(String msg, int code) {
Result result = error(ErrorEnum.COMMON_ERROR);
result.put("msg", msg);
result.put("code", code);
return result;
}
public static Result error(ErrorEnum fwWebError) {
Result result = new Result();
result.put("code", fwWebError.code);
result.put("msg", fwWebError.msg);
result.put("success", false);
return result;
}
public static Result error(int code, String msg) {
Result result = new Result();
result.put("code", code);
result.put("msg", msg);
result.put("success", false);
return result;
}
@Override
public Result put(String key, Object value) {
super.put(key, value);
return this;
}
}

@ -0,0 +1,53 @@
package com.yuxue.entity;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* t_system_menu
* @author
*/
@Data
@NoArgsConstructor
public class SystemMenuEntity implements Serializable {
private Integer id;
private String menuName;
private String menuUrl;
private Integer parentId;
private Integer sort;
private Integer menuLevel;
private String menuIcon;
private Integer showFlag;
private Integer platform;
private Integer menuType;
private String permission;
private Date updateTime;
private Integer editorId;
private String createTime;
private Integer creatorId;
private Integer version;
private Integer delFlag;
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,51 @@
package com.yuxue.entity;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* temp_plate_file
* @author yuxue
* 2020-04-30 09:39:59.928
*/
@Data
@NoArgsConstructor
public class TempPlateFileEntity implements Serializable {
/**
* id
*/
private Integer id;
/**
* fileName
*/
private String fileName;
/**
* filePath
*/
private String filePath;
/**
* fileType
*/
private String fileType;
/**
* fileLength
*/
private Long fileLength;
/**
* parentId
*/
private Integer parentId;
/**
* level
*/
private Integer level;
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,46 @@
package com.yuxue.enumtype;
public enum Direction {
VERTICAL("VERTICAL","垂直"),
HORIZONTAL("HORIZONTAL","水平"),
UNKNOWN("UNKNOWN","未知");
public final String code;
public final String desc;
Direction(String code, String desc) {
this.code = code;
this.desc = desc;
}
public static String getDesc(String code) {
Direction[] enums = values();
for (Direction type : enums) {
if (type.code().equals(code)) {
return type.desc();
}
}
return null;
}
public static String getCode(String desc) {
Direction[] enums = values();
for (Direction type : enums) {
if (type.desc().equals(desc)) {
return type.code();
}
}
return null;
}
public String code() {
return this.code;
}
public String desc() {
return this.desc;
}
}

@ -0,0 +1,59 @@
package com.yuxue.enumtype;
/**
*
* @author yuxue
* @date 2020-05-08 12:38
*/
public enum PlateColor {
BLUE("BLUE","蓝牌", 100, 130),
GREEN("GREEN","绿牌", 38, 100),
YELLOW("YELLOW","黄牌", 15, 40),
UNKNOWN("UNKNOWN","未知", 0, 0);
public final String code;
public final String desc;
// opencv颜色识别的HSV中各个颜色所对应的H的范围 Orange 0-22 Yellow 22- 38 Green 38-75 Blue 75-130
public final int minH;
public final int maxH;
PlateColor(String code, String desc, int minH, int maxH) {
this.code = code;
this.desc = desc;
this.minH = minH;
this.maxH = maxH;
}
public static String getDesc(String code) {
PlateColor[] enums = values();
for (PlateColor type : enums) {
if (type.code().equals(code)) {
return type.desc();
}
}
return null;
}
public static String getCode(String desc) {
PlateColor[] enums = values();
for (PlateColor type : enums) {
if (type.desc().equals(desc)) {
return type.code();
}
}
return null;
}
public String code() {
return this.code;
}
public String desc() {
return this.desc;
}
}

@ -0,0 +1,69 @@
package com.yuxue.exception;
/**
*
* @author yuxue
* @date 2018-09-07
*/
public enum ErrorEnum {
// 200-->Success!
// 6000-->Fail
// common
COMMON_ERROR("Fail", 6000),
COMMON_PARAMS_ERR("提交参数不合法", 6001),
COMMON_PARAMS_ID_ERR("提交参数ID不合法", 6002),
COMMON_EMPTY_CONDITION_RESULT("没有找到符合条件的数据", 6003),
COMMON_PARAMS_NOT_EXIST("提交的字段不存在,或者参数格式错误", 6004),
// sql
SQL_ERROR("mysql通用错误", 6100),
SQL_INSERT_FAIL("增加失败", 6101),
SQL_DELETE_FAIL("删除失败", 6102),
SQL_UPDATE_FAIL("修改失败", 6103),
SQL_RECORD_EXIST("添加重复记录", 6104),
SQL_ID_NOT_EXIST("主键ID不能为空", 6105),
SQL_VERSION_NOT_EXIST("数据版本version不能为空", 6106),
// io
FILE_IO_ERROR("io通用错误", 6200),
FILE_NOT_EXIST("文件没找到,请联系管理员", 6201),
FILE_DATA_NULL("文档中不不存在有效的数据", 6202),
FILE_DATA_ERR("文档中的数据格式错误", 6203),
// form
INVALID_PASSWORD("密码格式错误", 6300),
INVALID_EMAIL("邮件格式错误", 6301),
INVALID_NAME("账号格式错误", 6302),
INVALID_PARAMS("填写字段不合法", 6303),
// shiro-login
NO_LOGIN("用户未登录", 401),
UNAUTHORIZED("权限不足", 7001),
ADMIN_ONLY("只有管理员账号可以调用这个接口", 6402),
NO_PERSSIOM("没有权限请求", 6403),
WRONG_ACCOUNT_OR_PSW("账号或密码错误", 6404),
WRONG_ACCOUNT_PSW("账号密码错误", 6405),
WRONG_ACCOUNT_WRONG("用户没有权限(令牌、用户名、密码错误)", 401),
// uploading
UPLOAD_FILE_TYPE_ERROR("上传文件格式错误", 6500),
UPLOAD_FILE_UPLOADING("uploading", 6501),
UPLOAD_FILE_NOT_EXIST("文件不存在", 6502),
UPLOAD_FILE_SIZE_MAX("上传的文件大小超出限制", 6503),
// es
ES_BIG_PAGE_SEARCH("单页查询数据不能超过10000!", 9000);
// NoSQL
public final String msg;
public final int code;
ErrorEnum(String msg, int code) {
this.msg = msg;
this.code = code;
}
}

@ -0,0 +1,53 @@
package com.yuxue.exception;
/**
* runtime
* @author yuxue
* @date 2018-09-07
*/
public class ResultReturnException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String msg = ErrorEnum.COMMON_ERROR.msg;
private int code = ErrorEnum.COMMON_ERROR.code;
public ResultReturnException(ErrorEnum error) {
super(error.msg);
this.msg = error.msg;
this.code = error.code;
}
public ResultReturnException(String msg) {
super(msg);
this.msg = msg;
}
public ResultReturnException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}
@Deprecated
public ResultReturnException(String msg, int code) {
super(msg);
this.msg = msg;
this.code = code;
}
@Deprecated
public ResultReturnException(String msg, int code, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
}
public String getMsg() {
return msg;
}
public int getCode() {
return code;
}
}

@ -0,0 +1,84 @@
package com.yuxue.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import com.yuxue.entity.Result;
/**
* RestController
* @author yuxue
* @date 2018-09-06
*/
@RestControllerAdvice
public class ResultReturnExceptionHandler {
protected static Logger log=LoggerFactory.getLogger(ResultReturnExceptionHandler.class);
/** 捕捉shiro的异常 *//*
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(ShiroException.class)
public Result handle401(ShiroException e) {
log.error(e.getMessage(), e);
return Result.error(ErrorEnum.UNAUTHORIZED);
}
*//** 捕捉UnauthorizedException *//*
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
public Result handle401() {
return Result.error(ErrorEnum.UNAUTHORIZED);
}*/
/** 文件上传大小异常 */
@ExceptionHandler(MultipartException.class)
public Result handleMultipart(Throwable t) {
log.error(t.getMessage(), t);
return Result.error(ErrorEnum.UPLOAD_FILE_SIZE_MAX);
}
/** jackson转换Bean * */
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result handleJsonConv(Throwable t) {
log.error(t.getMessage(), t);
return Result.error(ErrorEnum.COMMON_PARAMS_NOT_EXIST);
}
/** 异常参数处理器 */
@ExceptionHandler(IllegalArgumentException.class)
public Result handleRRException(Throwable e) {
//log.error(e.getMessage(), e);
return Result.error(ErrorEnum.COMMON_PARAMS_ERR.code, e.getMessage());
}
/** 自定义异常 */
@ExceptionHandler(ResultReturnException.class)
public Result handleRRException(ResultReturnException e) {
log.error(exTraceBack(e), e);
return Result.error(e.getCode(), e.getMsg());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error(exTraceBack(e), e);
return Result.error("系统发生错误,请联系管理员");
}
public static String exTraceBack(Exception e) {
StringBuilder sb = new StringBuilder();
StackTraceElement[] stackTrace = e.getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
sb.append("<---");
sb.append(String.format("[%s * %s] ", stackTrace[i].getClassName(), stackTrace[i].getMethodName()));
}
sb.append(e.getMessage());
return sb.toString();
}
}

@ -0,0 +1,25 @@
package com.yuxue.mapper;
import com.yuxue.entity.PlateFileEntity;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PlateFileMapper {
int deleteByPrimaryKey(Integer id);
int insert(PlateFileEntity record);
int insertSelective(PlateFileEntity record);
PlateFileEntity selectByPrimaryKey(Integer id);
List<PlateFileEntity> selectByCondition(Map map);
int updateByPrimaryKeySelective(PlateFileEntity record);
int updateByPrimaryKey(PlateFileEntity record);
List<PlateFileEntity> getUnRecogniseList();
}

@ -0,0 +1,30 @@
package com.yuxue.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.yuxue.entity.PlateRecoDebugEntity;
@Mapper
public interface PlateRecoDebugMapper {
int deleteByPrimaryKey(Integer id);
int insert(PlateRecoDebugEntity record);
int insertSelective(PlateRecoDebugEntity record);
PlateRecoDebugEntity selectByPrimaryKey(Integer id);
List<PlateRecoDebugEntity> selectByCondition(Map map);
int updateByPrimaryKeySelective(PlateRecoDebugEntity record);
int updateByPrimaryKey(PlateRecoDebugEntity record);
int deleteByParentId(@Param("parentId")Integer parentId);
int batchInsert(@Param("list")List<PlateRecoDebugEntity> list);
}

@ -0,0 +1,25 @@
package com.yuxue.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import com.yuxue.entity.SystemMenuEntity;
@Mapper
public interface SystemMenuMapper {
int deleteByPrimaryKey(Integer id);
int insert(SystemMenuEntity record);
int insertSelective(SystemMenuEntity record);
SystemMenuEntity selectByPrimaryKey(Integer id);
List<SystemMenuEntity> selectByCondition(Map map);
int updateByPrimaryKeySelective(SystemMenuEntity record);
int updateByPrimaryKey(SystemMenuEntity record);
}

@ -0,0 +1,34 @@
package com.yuxue.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.yuxue.entity.TempPlateFileEntity;
@Mapper
public interface TempPlateFileMapper {
int deleteByPrimaryKey(Integer id);
int insert(TempPlateFileEntity record);
int insertSelective(TempPlateFileEntity record);
TempPlateFileEntity selectByPrimaryKey(Integer id);
List<TempPlateFileEntity> selectByCondition(Map map);
int updateByPrimaryKeySelective(TempPlateFileEntity record);
int updateByPrimaryKey(TempPlateFileEntity record);
int turncateTable();
int batchInsert(@Param("list")List<TempPlateFileEntity> list);
int updateFileInfo();
}

@ -0,0 +1,16 @@
package com.yuxue.service;
import java.io.File;
import java.util.List;
import com.alibaba.fastjson.JSONObject;
public interface FileService {
List<JSONObject> getFileTreeByDir(String dir, String typeFilter);
File readFile(String filePath);
}

@ -0,0 +1,16 @@
package com.yuxue.service;
public interface PlateService {
public Object getProcessStep();
Object recognise(String filePath, boolean reRecognise);
Object refreshFileInfo();
Object recogniseAll();
}

@ -0,0 +1,30 @@
package com.yuxue.service;
import java.util.List;
import java.util.Map;
import com.github.pagehelper.PageInfo;
import com.yuxue.entity.SystemMenuEntity;
/**
*
* @author yuxue
* @date 2019-06-20 16:15:23
*/
public interface SystemMenuService {
public SystemMenuEntity getByPrimaryKey(Integer id);
public PageInfo<SystemMenuEntity> queryByPage(Integer pageNo, Integer pageSize, Map<String, Object> map);
public List<SystemMenuEntity> queryByCondition(Map<String, Object> map);
public Map<String, Object> save(SystemMenuEntity systemMenuEntity);
public Integer deleteById(Integer id);
public Integer updateById(SystemMenuEntity systemMenuEntity);
public Object getUserMenu();
}

@ -0,0 +1,62 @@
package com.yuxue.service.impl;
import java.io.File;
import java.util.List;
import org.springframework.stereotype.Service;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.yuxue.constant.Constant;
import com.yuxue.exception.ResultReturnException;
import com.yuxue.service.FileService;
import com.yuxue.util.FileUtil;
@Service
public class FileServiceImpl implements FileService {
@Override
public List<JSONObject> getFileTreeByDir(String dir, String typeFilter) {
if(StringUtils.isEmpty(dir)){
dir = Constant.DEFAULT_DIR;
}
if(StringUtils.isEmpty(typeFilter)){
typeFilter = Constant.DEFAULT_TYPE;
}
File f = new File(dir);
List<File> list = FileUtil.listFile(f, typeFilter, false);
List<JSONObject> result = Lists.newArrayList();
list.stream().forEach(n->{
JSONObject jo = new JSONObject();
jo.put("id", n.getAbsolutePath());
jo.put("pid", n.getParentFile().getAbsolutePath());
jo.put("filePath", n.getAbsolutePath());
jo.put("fileName", n.getName());
jo.put("isDir", n.isDirectory());
result.add(jo);
});
return result;
}
@Override
public File readFile(String filePath) {
File f = new File(filePath);
if(!f.exists() || f.isDirectory()) {
throw new ResultReturnException("filePath参数异常找不到指定的文件: " + filePath);
}
if(!f.exists() || f.isDirectory()) {
throw new ResultReturnException("读取图片异常:" + f.getName());
}
return f;
}
}

@ -0,0 +1,271 @@
package com.yuxue.service.impl;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_imgcodecs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yuxue.constant.Constant;
import com.yuxue.easypr.core.CharsRecognise;
import com.yuxue.easypr.core.CoreFunc;
import com.yuxue.easypr.core.PlateDetect;
import com.yuxue.entity.PlateFileEntity;
import com.yuxue.entity.PlateRecoDebugEntity;
import com.yuxue.entity.TempPlateFileEntity;
import com.yuxue.enumtype.PlateColor;
import com.yuxue.mapper.PlateFileMapper;
import com.yuxue.mapper.PlateRecoDebugMapper;
import com.yuxue.mapper.TempPlateFileMapper;
import com.yuxue.service.PlateService;
import com.yuxue.util.FileUtil;
@Service
public class PlateServiceImpl implements PlateService {
// 车牌定位处理步骤该map用于表示步骤图片的顺序
private static Map<String, Integer> debugMap = Maps.newLinkedHashMap();
static {
debugMap.put("result", 99);
debugMap.put("debug_GaussianBlur", 0); // 高斯模糊
debugMap.put("debug_gray", 1); // 图像灰度化
debugMap.put("debug_Sobel", 2); // Sobel 算子
debugMap.put("debug_threshold", 3); //图像二值化
debugMap.put("debug_morphology", 4); // 图像闭操作
debugMap.put("debug_Contours", 5); // 提取外部轮廓
debugMap.put("debug_result", 6); // 原图处理结果
debugMap.put("debug_crop", 7); // 切图
debugMap.put("debug_resize", 8); // 切图resize
debugMap.put("debug_char_threshold", 9); //
// debugMap.put("debug_char_clearLiuDing", 10); // 去除柳钉
debugMap.put("debug_specMat", 11); //
debugMap.put("debug_chineseMat", 12); //
debugMap.put("debug_char_auxRoi", 13); //
}
@Autowired
private PlateFileMapper plateFileMapper;
@Autowired
private PlateRecoDebugMapper plateRecoDebugMapper;
@Autowired
private TempPlateFileMapper tempPlateFileMapper;
@Override
public Object recognise(String filePath, boolean reRecognise) {
filePath = filePath.replaceAll("\\\\", "/");
File f = new File(filePath);
PlateFileEntity e = null;
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("filePath", filePath);
List<PlateFileEntity> list= plateFileMapper.selectByCondition(paramMap);
if(null == list || list.size() <= 0) {
if(FileUtil.checkFile(f)) {
e = new PlateFileEntity();
e.setFileName(f.getName());
e.setFilePath(f.getAbsolutePath().replaceAll("\\\\", "/"));
e.setFileType(f.getName().substring(f.getName().lastIndexOf(".") + 1));
plateFileMapper.insertSelective(e);
}
reRecognise = true;
} else {
e = list.get(0);
}
if(reRecognise) {
doRecognise(f, e, 0); // 重新识别
e = plateFileMapper.selectByPrimaryKey(e.getId()); // 重新识别之后,重新获取一下数据
}
// 查询数据库,返回结果
paramMap.clear();
paramMap.put("parentId", e.getId());
e.setDebug(plateRecoDebugMapper.selectByCondition(paramMap));
return e;
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Object refreshFileInfo() {
File baseDir = new File(Constant.DEFAULT_DIR);
if(!baseDir.exists() || !baseDir.isDirectory()) {
return null;
}
List<TempPlateFileEntity> resultList = Lists.newArrayList();
// 获取baseDir下第一层级的目录 仅获取文件夹,不递归子目录,遍历
List<File> folderList = FileUtil.listFile(baseDir, ";", false);
folderList.parallelStream().forEach(folder -> {
if(!folder.getName().equals("temp")) {
// 遍历每一个文件夹, 递归获取文件夹下的图片
List<File> imgList = FileUtil.listFile(folder, Constant.DEFAULT_TYPE, true);
if(null != imgList && imgList.size() > 0) {
imgList.parallelStream().forEach(n->{
TempPlateFileEntity entity = new TempPlateFileEntity();
entity.setFilePath(n.getAbsolutePath().replaceAll("\\\\", "/"));
entity.setFileName(n.getName());
entity.setFileType(n.getName().substring(n.getName().lastIndexOf(".") + 1));
resultList.add(entity);
});
}
}
});
tempPlateFileMapper.turncateTable();
tempPlateFileMapper.batchInsert(resultList);
tempPlateFileMapper.updateFileInfo();
return 1;
}
@Override
public Object recogniseAll() {
// 查询到还没有进行车牌识别的图片
List<PlateFileEntity> list = plateFileMapper.getUnRecogniseList();
// 开启多线程进行识别
Random r = new Random(99);
list.parallelStream().forEach(n->{
File f = new File(n.getFilePath());
if(FileUtil.checkFile(f)) {
doRecognise(f, n, r.nextInt());
}
});
return 1;
}
@Override
public Object getProcessStep() {
return debugMap;
}
/**
*
*
*
* @param f
* @param result
* @return
*/
public Object doRecognise(File f, PlateFileEntity e, Integer seed) {
// 插入识别过程图片数据信息 通过temp文件夹的文件更新数据库
List<PlateRecoDebugEntity> debug = Lists.newArrayList();
Long ct = System.currentTimeMillis();
String targetPath = Constant.DEFAULT_TEMP_DIR.concat(ct.toString() + seed)
.concat(f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(".")));
// 先将文件拷贝并且重命名到不包含中文及特殊字符的目录下
FileUtil.copyAndRename(f.getAbsolutePath(), targetPath);
// 开始识别,生成过程及结果切图,将识别结果更新到数据库
Mat src = opencv_imgcodecs.imread(targetPath);
String tempPath = Constant.DEFAULT_TEMP_DIR + ct + "/";
FileUtil.createDir(tempPath); // 创建文件夹
// 车牌检测对象
PlateDetect plateDetect = new PlateDetect();
plateDetect.setPDLifemode(true);
plateDetect.setDebug(true, tempPath); // 将过程的图块保存到盘符
Vector<Mat> matVector = new Vector<Mat>();
if (0 == plateDetect.plateDetect(src, matVector)) { // 定位及判断获取到车牌图块Mat
CharsRecognise cr = new CharsRecognise();
cr.setCRDebug(true);
for (int i = 0; i < matVector.size(); ++i) { // 遍历车牌图块Mat进行识别
Mat img = matVector.get(i);
String palte = cr.charsRecognise(img, tempPath); // 字符识别
PlateColor color = CoreFunc.getPlateType(img, true);
String fileName = "result_" + i + ".png";
// 识别的车牌,保存图片文件
String str = tempPath + fileName;
// 此方法生成的文件中文名称都是乱码试了各种编解码均无效OpenCV自身的编解码问题。
opencv_imgcodecs.imwrite(str, img);
// 重命名文件,让生成的文件包含中文
// String newName = palte + "_"+ color + ".png";
// FileUtil.renameFile(str, newName);
PlateRecoDebugEntity de = new PlateRecoDebugEntity();
de.setRecoPlate(palte);
de.setFilePath(str);
de.setFileName(fileName);
de.setPlateColor(color.desc);
de.setParentId(e.getId());
de.setDebugType("result");
de.setSort(debugMap.get("result"));
debug.add(de);
}
} else {
e.setRecoCorrect(3); // 未检测到车牌
}
new File(targetPath).delete(); // 删除拷贝的文件
e.setTempPath(tempPath);
List<File> debugList = FileUtil.listFile(new File(tempPath), Constant.DEFAULT_TYPE, false);
debugList.parallelStream().forEach(d -> {
String name = d.getName().substring(0, d.getName().lastIndexOf("."));
Pattern pattern = Pattern.compile("\\d+$");
Matcher matcher = pattern.matcher(name);
if(matcher.find()) {
name = name.substring(0, name.lastIndexOf("_"));
}
if(!"result".equals(name)) {
PlateRecoDebugEntity de = new PlateRecoDebugEntity();
de.setRecoPlate("");
de.setFilePath(d.getAbsolutePath().replaceAll("\\\\", "/"));
de.setFileName(d.getName());
de.setPlateColor("");
de.setParentId(e.getId());
de.setDebugType(name);
de.setSort(debugMap.get(name));
debug.add(de);
}
});
// 更新图片主表信息
plateFileMapper.updateByPrimaryKeySelective(e);
plateRecoDebugMapper.deleteByParentId(e.getId());
plateRecoDebugMapper.batchInsert(debug);
return 1;
}
}

@ -0,0 +1,101 @@
package com.yuxue.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Maps;
import com.yuxue.entity.SystemMenuEntity;
import com.yuxue.mapper.SystemMenuMapper;
import com.yuxue.service.SystemMenuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author yuxue
* @date 2019-06-20 16:15:23
*/
@Service
public class SystemMenuServiceImpl implements SystemMenuService {
@Autowired
private SystemMenuMapper systemMenuMapper;
@Override
public SystemMenuEntity getByPrimaryKey(Integer id) {
SystemMenuEntity entity = systemMenuMapper.selectByPrimaryKey(id);
return entity;
}
@Override
public PageInfo<SystemMenuEntity> queryByPage(Integer pageNo, Integer pageSize, Map<String, Object> map) {
PageHelper.startPage(pageNo, pageSize);
PageInfo<SystemMenuEntity> page = new PageInfo(systemMenuMapper.selectByCondition(map));
return page;
}
@Override
public List<SystemMenuEntity> queryByCondition(Map<String, Object> map) {
return systemMenuMapper.selectByCondition(map);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Map<String, Object> save(SystemMenuEntity entity) {
entity.setId(0);
systemMenuMapper.insertSelective(entity);
Map<String, Object> result = new HashMap<>();
result.put("id" , entity.getId());
return result;
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Integer deleteById(Integer id){
return systemMenuMapper.deleteByPrimaryKey(id);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Integer updateById(SystemMenuEntity systemMenuEntity) {
if(null == systemMenuEntity || systemMenuEntity.getId() <= 0){
return 0;
}
return systemMenuMapper.updateByPrimaryKeySelective(systemMenuEntity);
}
@Override
public Object getUserMenu() {
Map<String, Object> map = Maps.newHashMap();
//根据角色查询菜单--未完成 //根据层级 sort排序
map.put("showFlag", 1);
List<SystemMenuEntity> menus = systemMenuMapper.selectByCondition(map);
//按层级封装,最多三级
Map<String, Object> result = Maps.newHashMap();
result.put("first", menus.stream().filter(n -> {
return n.getMenuLevel() == 1;
}));
result.put("second", menus.stream().filter(n -> {
return n.getMenuLevel() == 2;
}));
result.put("third", menus.stream().filter(n -> {
return n.getMenuLevel() == 3;
}));
return result;
}
}

@ -0,0 +1,215 @@
package com.yuxue.train;
import java.util.Random;
import java.util.Vector;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.TermCriteria;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.ml.ANN_MLP;
import org.opencv.ml.Ml;
import org.opencv.ml.TrainData;
import com.yuxue.constant.Constant;
import com.yuxue.util.FileUtil;
import com.yuxue.util.PlateUtil;
/**
* org.opencv
*
*
*
*
*
*
* 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 ANNTrain {
private ANN_MLP ann = ANN_MLP.create();
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
// 默认的训练操作的根目录
private static final String DEFAULT_PATH = "D:/PlateDetect/train/chars_recognise_ann/";
// 训练模型文件保存位置
private static final String MODEL_PATH = DEFAULT_PATH + "ann.xml";
public void train(int _predictsize, int _neurons) {
Mat samples = new Mat(); // 使用push_back行数列数不能赋初始值
Vector<Integer> trainingLabels = new Vector<Integer>();
Random rand = new Random();
// 加载数字及字母字符
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 count = 200; // 控制从训练样本中,抽取指定数量的样本
int count = files.size(); // 控制从训练样本中,抽取指定数量的样本
for (int j = 0; j < count; j++) {
String filename = "";
if(j < files.size()) {
filename = files.get(j);
} else {
filename = files.get(rand.nextInt(files.size() - 1)); // 样本不足,随机重复提取已有的样本
}
Mat img = Imgcodecs.imread(filename, 0);
Mat f = PlateUtil.features(img, _predictsize);
samples.push_back(f);
trainingLabels.add(i); // 每一幅字符图片所对应的字符类别索引下标
// 增加随机平移样本
samples.push_back(PlateUtil.features(PlateUtil.randTranslate(img), _predictsize));
trainingLabels.add(i);
// 增加随机旋转样本
samples.push_back(PlateUtil.features(PlateUtil.randRotate(img), _predictsize));
trainingLabels.add(i);
// 增加膨胀样本
samples.push_back(PlateUtil.features(PlateUtil.dilate(img), _predictsize));
trainingLabels.add(i);
// 增加腐蚀样本
/*samples.push_back(PlateUtil.features(PlateUtil.erode(img), _predictsize));
trainingLabels.add(i); */
}
}
samples.convertTo(samples, CvType.CV_32F);
//440 vhist.length + hhist.length + lowData.cols() * lowData.rows();
// CV_32FC1 CV_32SC1 CV_32F
Mat classes = Mat.zeros(trainingLabels.size(), Constant.strCharacters.length, CvType.CV_32F);
float[] labels = new float[trainingLabels.size()];
for (int i = 0; i < labels.length; ++i) {
classes.put(i, trainingLabels.get(i), 1.f);
}
// samples.type() == CV_32F || samples.type() == CV_32S
TrainData train_data = TrainData.create(samples, Ml.ROW_SAMPLE, classes);
ann.clear();
Mat layers = new Mat(1, 3, CvType.CV_32F);
layers.put(0, 0, samples.cols()); // 样本特征数 140 10*10 + 20+20
layers.put(0, 1, _neurons); // 神经元个数
layers.put(0, 2, classes.cols()); // 字符数
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);
int total = 0;
int correct = 0;
// 遍历测试样本下的所有文件,计算预测准确率
for (int i = 0; i < Constant.strCharacters.length; i++) {
char c = Constant.strCharacters[i];
String path = DEFAULT_PATH + "learn/" + c;
Vector<String> files = new Vector<String>();
FileUtil.getFiles(path, files);
for (String filePath : files) {
Mat img = Imgcodecs.imread(filePath, 0);
Mat f = PlateUtil.features(img, Constant.predictSize);
int index = 0;
double maxVal = -2;
Mat output = new Mat(1, Constant.strCharacters.length, CvType.CV_32F);
ann.predict(f, output); // 预测结果
for (int j = 0; j < Constant.strCharacters.length; j++) {
double val = output.get(0, j)[0];
if (val > maxVal) {
maxVal = val;
index = j;
}
}
// 膨胀
f = PlateUtil.features(PlateUtil.dilate(img), Constant.predictSize);
ann.predict(f, output); // 预测结果
for (int j = 0; j < Constant.strCharacters.length; j++) {
double val = output.get(0, j)[0];
if (val > maxVal) {
maxVal = val;
index = j;
}
}
String result = String.valueOf(Constant.strCharacters[index]);
if(result.equals(String.valueOf(c))) {
correct++;
} else {
// 删除异常样本
/*File f1 = new File(filePath);
f1.delete();*/
System.err.print(filePath);
System.err.println("\t预测结果" + result);
}
total++;
}
}
System.out.print("total:" + total);
System.out.print("\tcorrect:" + correct);
System.out.print("\terror:" + (total - correct));
System.out.println("\t计算准确率为" + correct / (total * 1.0));
//牛逼,我操 total:13178 correct:13139 error:39 计算准确率为0.9970405220822584
return;
}
public static void main(String[] args) {
ANNTrain annT = new ANNTrain();
// 这里演示只训练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.");
return;
}
}

@ -0,0 +1,228 @@
package com.yuxue.train;
import java.io.File;
import java.util.Random;
import java.util.Vector;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.TermCriteria;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.ml.ANN_MLP;
import org.opencv.ml.Ml;
import org.opencv.ml.TrainData;
import com.yuxue.constant.Constant;
import com.yuxue.util.FileUtil;
import com.yuxue.util.PlateUtil;
/**
* org.opencv
*
*
*
*
*
* @author yuxue
* @date 2020-07-02 22:16
*/
public class CnANNTrain {
private ANN_MLP ann = ANN_MLP.create();
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
// 默认的训练操作的根目录
private static final String DEFAULT_PATH = "D:/PlateDetect/train/chars_recognise_ann/";
// 训练模型文件保存位置
private static final String MODEL_PATH = DEFAULT_PATH + "ann_cn.xml";
public void train(int _predictsize, int _neurons) {
Mat samples = new Mat(); // 使用push_back行数列数不能赋初始值
Vector<Integer> trainingLabels = new Vector<Integer>();
Random rand = new Random();
// 加载汉字字符
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 count = 300; // 控制从训练样本中,抽取指定数量的样本
int count = files.size(); // 不添加随机样本
for (int j = 0; j < count; j++) {
String filename = "";
if(j < files.size()) {
filename = files.get(j);
} else {
filename = files.get(rand.nextInt(files.size() - 1)); // 样本不足,随机重复提取已有的样本
}
Mat img = Imgcodecs.imread(filename, 0);
// 原图样本
samples.push_back(PlateUtil.features(img, _predictsize));
trainingLabels.add(i);
// 增加随机平移样本
samples.push_back(PlateUtil.features(PlateUtil.randTranslate(img), _predictsize));
trainingLabels.add(i);
// 增加随机旋转样本
samples.push_back(PlateUtil.features(PlateUtil.randRotate(img), _predictsize));
trainingLabels.add(i);
// 增加腐蚀样本
samples.push_back(PlateUtil.features(PlateUtil.erode(img), _predictsize));
trainingLabels.add(i);
}
}
samples.convertTo(samples, CvType.CV_32F);
//440 vhist.length + hhist.length + lowData.cols() * lowData.rows();
// CV_32FC1 CV_32SC1 CV_32F
Mat classes = Mat.zeros(trainingLabels.size(), Constant.strChinese.length, CvType.CV_32F);
float[] labels = new float[trainingLabels.size()];
for (int i = 0; i < labels.length; ++i) {
classes.put(i, trainingLabels.get(i), 1.f);
}
// samples.type() == CV_32F || samples.type() == CV_32S
TrainData train_data = TrainData.create(samples, Ml.ROW_SAMPLE, classes);
ann.clear();
Mat layers = new Mat(1, 3, CvType.CV_32F);
layers.put(0, 0, samples.cols()); // 样本特征数 140 10*10 + 20+20
layers.put(0, 1, _neurons); // 神经元个数
layers.put(0, 2, classes.cols()); // 字符数
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);
int total = 0;
int correct = 0;
// 遍历测试样本下的所有文件,计算预测准确率
for (int i = 0; i < Constant.strChinese.length; i++) {
String strChinese = Constant.strChinese[i];
String path = DEFAULT_PATH + "learn/" + strChinese;
Vector<String> files = new Vector<String>();
FileUtil.getFiles(path, files);
for (String filePath : files) {
Mat img = Imgcodecs.imread(filePath, 0);
Mat f = PlateUtil.features(img, Constant.predictSize);
int index = 0;
double maxVal = -2;
Mat output = new Mat(1, Constant.strChinese.length, CvType.CV_32F);
ann.predict(f, output); // 预测结果
for (int j = 0; j < Constant.strChinese.length; j++) {
double val = output.get(0, j)[0];
if (val > maxVal) {
maxVal = val;
index = j;
}
}
// 腐蚀 -- 识别中文字符效果会好一点,识别数字及字母效果会更差
f = PlateUtil.features(PlateUtil.erode(img), Constant.predictSize);
ann.predict(f, output); // 预测结果
for (int j = 0; j < Constant.strChinese.length; j++) {
double val = output.get(0, j)[0];
if (val > maxVal) {
maxVal = val;
index = j;
}
}
String result = Constant.strChinese[index];
if(result.equals(strChinese)) {
correct++;
} else {
// 删除异常样本
/*File f1 = new File(filePath);
f1.delete();*/
System.err.print(filePath);
System.err.println("\t预测结果" + Constant.KEY_CHINESE_MAP.get(result));
}
total++;
}
}
System.out.print("total:" + total);
System.out.print("\tcorrect:" + correct);
System.out.print("\terror:" + (total - correct));
System.out.println("\t计算准确率为" + correct / (total * 1.0));
//预测结果:
//单字符100样本数 total:3230 correct:2725 error:505 计算准确率为0.8436532507739938
//单字符200样本数 total:3230 correct:2889 error:341 计算准确率为0.8944272445820434
//单字符300样本数 total:3230 correct:2943 error:287 计算准确率为0.9111455108359133
//单字符400样本数 total:3230 correct:2937 error:293 计算准确率为0.9092879256965944
//无随机样本 total:3230 correct:3050 error:180 计算准确率为0.9442724458204335
//无随机,删除异常样本 total:3050 correct:2987 error:63 计算准确率为0.979344262295082
//无随机,删除异常样本 total:2987 correct:2973 error:14 计算准确率为0.9953130231001004
//无随机,删除异常样本 total:2987 correct:2932 error:55 计算准确率为0.9815868764646802
//无随机,删除异常样本 total:2987 correct:2971 error:16 计算准确率为0.9946434549715434
// 个人测试多次之后,得出结论:
// 1、每个字符下样本数量不一致最多的299个样本最少的不到10个样本从测试结果来看样本太少会影响预测结果
// 2、这里的训练跟测试的样本都是基于相同的样本文件所以测试结果存在一定的局限性仅供参考
// 3、测试过程中使用了随机样本实际上发现重复样本对预测结果影响不大
// 4、中文字符分离出来之后预测准确性要高很多
// 5、随机平移、随机旋转、膨胀、腐蚀会增加样本数量同时增加预测准确性
// 6、每次重新训练后结果是不一致的没有重新训练多次使用样本预测结果是一致的
// 7、经过多次测试这里的训练方案跟预测结果准确率在90%左右
// 8、用于训练的样本尽量要多一点样本特征丰富一点这样子可以提高准确性但是用于预测的样本要尽量规范、正常
return;
}
public static void main(String[] args) {
CnANNTrain annT = new CnANNTrain();
// 这里演示只训练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.");
return;
}
}

@ -0,0 +1,279 @@
package com.yuxue.train;
import java.io.File;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.Core.MinMaxLocResult;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.TermCriteria;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.ml.Ml;
import org.opencv.ml.SVM;
import org.opencv.ml.TrainData;
import com.google.common.collect.Lists;
import com.yuxue.constant.Constant;
import com.yuxue.enumtype.Direction;
import com.yuxue.util.FileUtil;
/**
* org.opencv
*
* windows
* 1openvphttps://opencv.org/releases/page/2/ 当前使用4.0.1版本
* 2exe \build\java\x64\opencv_java401.dll \build\x64\vc14\bin\
* 3eclipseUser Libraries
* 4build pathlib
*
*
*
*
* svm.xml
* 1res/model/svm.xml
* 2com.yuxue.easypr.core.PlateJudge.plateJudge(Mat)
*
* @author yuxue
* @date 2020-05-13 10:10
*/
public class SVMTrain {
// 默认的训练操作的根目录
private static final String DEFAULT_PATH = "D:/PlateDetect/train/plate_detect_svm/";
// 训练模型文件保存位置
private static final String MODEL_PATH = DEFAULT_PATH + "svm2.xml";
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
public static void main(String[] arg) {
// 训练, 生成svm.xml库文件
train();
// 识别,判断样本文件是否是车牌
pridect();
}
public static void train() {
// 正样本 // 136 × 36 像素 训练的源图像文件要相同大小
List<File> imgList0 = FileUtil.listFile(new File(DEFAULT_PATH + "/learn/HasPlate"), Constant.DEFAULT_TYPE, false);
// 负样本 // 136 × 36 像素 训练的源图像文件要相同大小
List<File> imgList1 = FileUtil.listFile(new File(DEFAULT_PATH + "/learn/NoPlate"), Constant.DEFAULT_TYPE, false);
// 标记:正样本用 0 表示,负样本用 1 表示。
int labels[] = createLabelArray(imgList0.size(), imgList1.size());
int sample_num = labels.length; // 图片数量
// 用于存放所有样本的矩阵
Mat trainingDataMat = null;
// 存放标记的Mat,每个图片都要给一个标记
Mat labelsMat = new Mat(sample_num, 1, CvType.CV_32SC1);
labelsMat.put(0, 0, labels);
for (int i = 0; i < sample_num; i++) { // 遍历所有的正负样本,处理样本用于生成训练的库文件
String path = "";
if(i < imgList0.size()) {
path = imgList0.get(i).getAbsolutePath();
} else {
path = imgList1.get(i - imgList0.size()).getAbsolutePath();
}
Mat inMat = Imgcodecs.imread(path); // 读取样本文件
Mat dst = getFeature(inMat); // 获取样本文件的特征
// 创建一个行数为sample_num, 列数为 rows*cols 的矩阵; 用于存放样本
if (trainingDataMat == null) {
trainingDataMat = new Mat(sample_num, dst.rows() * dst.cols(), CvType.CV_32F);
}
// 将样本矩阵转换成只有一行的矩阵保存为float数组
float[] arr = new float[dst.rows() * dst.cols()];
int l = 0;
for (int j = 0; j < dst.rows(); j++) { // 遍历行
for (int k = 0; k < dst.cols(); k++) { // 遍历列
double[] a = dst.get(j, k);
arr[l] = (float) a[0];
l++;
}
}
trainingDataMat.put(i, 0, arr); // 多张图的特征合并到一个矩阵
}
// Imgcodecs.imwrite(DEFAULT_PATH + "trainingDataMat.jpg", trainingDataMat);
// 配置SVM训练器参数
TermCriteria criteria = new TermCriteria(TermCriteria.EPS + TermCriteria.MAX_ITER, 20000, 0.0001);
SVM svm = SVM.create();
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);
TrainData td = TrainData.create(trainingDataMat, Ml.ROW_SAMPLE, labelsMat);// 类封装的训练数据
boolean success = svm.train(td.getSamples(), Ml.ROW_SAMPLE, td.getResponses());// 训练统计模型
System.out.println("svm training result: " + success);
svm.save(MODEL_PATH);// 保存模型
}
public static void pridect() {
// 加载训练得到的 xml 模型文件
SVM svm = SVM.load(MODEL_PATH);
// 136 × 36 像素 需要跟训练的源图像文件保持相同大小
doPridect(svm, DEFAULT_PATH + "test/A01_NMV802_0.jpg");
doPridect(svm, DEFAULT_PATH + "test/debug_resize_1.jpg");
doPridect(svm, DEFAULT_PATH + "test/debug_resize_2.jpg");
doPridect(svm, DEFAULT_PATH + "test/debug_resize_3.jpg");
doPridect(svm, DEFAULT_PATH + "test/S22_KG2187_3.jpg");
doPridect(svm, DEFAULT_PATH + "test/S22_KG2187_5.jpg");
}
public static void doPridect(SVM svm, String imgPath) {
Mat src = Imgcodecs.imread(imgPath);
Mat dst = getFeature(src);
// 如果训练时使用这个标识那么符合的图像会返回9.0
float flag = svm.predict(dst);
if (flag == 0) {
System.err.println(imgPath + " 目标符合");
}
if (flag == 1) {
System.out.println(imgPath + " 目标不符合");
}
}
public static int[] createLabelArray(Integer i1, Integer i2) {
int labels[] = new int[i1 + i2];
for (int i = 0; i < labels.length; i++) {
if(i < i1) {
labels[i] = 0;
} else {
labels[i] = 1;
}
}
return labels;
}
public static Mat getFeature(Mat inMat) {
Mat histogram = getHistogramFeatures(inMat);
Mat color = getColorFeatures(inMat);
List<Mat> list = Lists.newArrayList();
list.add(histogram);
list.add(color);
Mat dst = new Mat();
// hconcat 水平拼接 // vconcat 垂直拼接
Core.hconcat(list, dst);
return dst;
}
public static Mat getHistogramFeatures(Mat src) {
Mat img_grey = new Mat();
Imgproc.cvtColor(src, img_grey, Imgproc.COLOR_BGR2GRAY);
Mat img_threshold = new Mat();
Imgproc.threshold(img_grey, img_threshold, 0, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
// Histogram features
float[] vhist = projectedHistogram(img_threshold, Direction.VERTICAL);
float[] hhist = projectedHistogram(img_threshold, Direction.HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.length + hhist.length;
Mat features = Mat.zeros(1, numCols, CvType.CV_32F);
int j = 0;
for (int i = 0; i < vhist.length; i++) {
features.put(0, j, vhist[i]);
j++;
}
for (int i = 0; i < hhist.length; i++) {
features.put(0, j, hhist[i]);
j++;
}
return features;
}
public static float[] projectedHistogram(Mat inMat, Direction direction){
Mat img = new Mat();
inMat.copyTo(img);
int sz = img.rows();
if(Direction.VERTICAL.equals(direction)) {
sz = img.cols();
}
// 统计这一行或一列中非零元素的个数并保存到nonZeroMat中
float[] nonZeroMat = new float[sz];
Core.extractChannel(img, img, 0); // 提取0通道
for (int j = 0; j < sz; j++) {
Mat data = Direction.HORIZONTAL.equals(direction) ? img.row(j) : img.col(j);
int count = Core.countNonZero(data);
nonZeroMat[j] = count;
}
// Normalize histogram
float max = 1F;
for (int j = 0; j < nonZeroMat.length; j++) {
max = Math.max(max, nonZeroMat[j]);
}
for (int j = 0; j < nonZeroMat.length; j++) {
nonZeroMat[j] /= max;
}
return nonZeroMat;
}
public static Mat getColorFeatures(Mat src) {
Mat src_hsv = new Mat();
Imgproc.cvtColor(src, src_hsv, Imgproc.COLOR_BGR2GRAY);
int sz = 180;
int[] h = new int[180];
for (int i = 0; i < src_hsv.rows(); i++) {
for (int j = 0; j < src_hsv.cols(); j++) {
int H = (int) src_hsv.get(i, j)[0];// 0-180
if (H > sz - 1) {
H = sz - 1;
}
if (H < 0) {
H = 0;
}
h[H]++;
}
}
// 创建黑色的图
Mat features = Mat.zeros(1, sz, CvType.CV_32F);
for (int j = 0; j < sz; j++) {
features.put(0, j, (float)h[j]);
}
MinMaxLocResult m = Core.minMaxLoc(features);
double max = m.maxVal;
if (max > 0) {
features.convertTo(features, -1, 1.0f / max, 0);
}
return features;
}
}

@ -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