master
1647705162@qq.com 4 years ago
parent 2cea317e0b
commit 4d40369f41

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="module" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JavaCV"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Lombok"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/opencv"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

@ -0,0 +1,94 @@
General Data Share License
通用数据共享协议
Version 0.1, 2015-01-25
当您开始使用EasyPR中的GDTS(General Data Test Set通用数据测试集)也就是image/general_test文件夹里的任一数据时您必须遵守以下协议的条款。
EasyPR只允许对GDTS里的数据进行非商业性目的的使用任何商业行为(售卖或者随产品附赠等)都属于违反本协议约定的行为。
EasyPR保留任何权利。
本协议的起草参考了以下两个协议GPL v2.0与ODL(Open Database License)。其核心思想与这两个协议基本相同,但也有许多相异的地方。
本协议与GPL协议相同的地方在于本协议与GPL协议都是“传染性”协议。
当您使用拷贝转移了GDTS中数据(或其中一部分时),您务必要保证这个协议随数据同行。
同时假若您把GDTS中数据(或其中一部分时)进行修改或与其他数据进行合并时那您需要确保新的数据集也必须遵循此GDSL协议的约定条款。
与GPL协议不同的地方在于GPL协议保护的是代码(code)以及基于代码的工作(work),而本协议保护的是数据(data)。
注意,本协议中所保护的数据仅保护这些原始的图片数据,并不针对于您通过这些图片数据训练出的模型,
以及您通过这些图片数据截取出的仅包含车牌的图片,您可以将这些训练模型或车牌图片按照非本协议约定的条款进行操作。
本协议与ODL协议相同的地方在于本协议与ODL协议都是保护数据(data)的协议,并且也提倡数据的开放,共享。
但ODL协议针对的主要是结构化数据而本协议针对的主要是图片数据。
另外ODL协议强调的开放不限制对数据的商业性使用。但本协议规定了数据仅仅只能用于进行非商业性的目的行为
包括学习与研究,但不包括商业性行为,例如销售与随产品赠送等等。
目前本协议的版本为0.1修正稿,任何对本协议的修改与建议都可以跟本协议的组织方联系(easypr_dev@163.com) 。
本协议主要分为三个部分:
1.版权声明:约定了对GDTS数据使用的规范。
2.捐赠说明:说明了如何对GDTS数据进行捐赠的方法。
3.免责声明:声明免责条款。
如果您仅仅是使用EasyPR进行开发与研究那您仅需要读完第一部分。如果您愿意对EasyPR进行捐赠那您需要读完第二部分。
第三部分声明了EasyPR在各个部分的免责条款。
一.版权声明
EasyPR中GDTS(通用数据测试集)的数据仅用作学习与研究之用。尽管EasyPR遵循的是商业友好的开源协议Acache2.0,但那个协议仅适用于您对代码的修改权。
这些测试数据集并不在您可以修改并且出卖的范围之内请确保这些数据集仅仅作为您进行EasyPR图像测试效率的验证与参考。
这些图片是用来测试EasyPR的效果与指标的仅仅用于开源学习目的。任何商业化的使用这些数据例如出卖数据或者将这些数据作为产品的附赠都是不允许的。
为了保证EasyPR中使用的测试数据不有任何侵犯权利的可能性每个在GDTS(通用数据测试集)上上传的数据都具有以下几个特征:
1.年代久远,不具有时效性的数据(例如,至少半年以前的数据)或者已经处理过相关版权事宜的数据。
2.在上传前使用EasyPR提供的函数对图片进行模糊化裁剪性处理确保图片不透露任何可能关于地点时间位置等相关信息。
3.在上传前使用EasyPR提供的反人脸识别工具进行处理确保图片不侵犯到任何人的隐私权和肖像权。
如果您发现EasyPR中存在任何侵犯您可能权利的图片时请跟我们联系(easypr_dev@163.com) 。我们的工作人员会跟您协商,将这些图片修改或做删除处理。
二.捐赠说明
EasyPR里的数据来源广泛部分来自于网络公开途径也有好心的网友以及匿名人士对EasyPR的捐赠。通过这些数据
有效地改善了EasyPR的识别效果与准确率为推广车牌识别技术在中国的开源发展做出了贡献。如果您愿意您也可以向EasyPR捐赠。
在一般的开源与众包软件中捐赠的方式一般是金钱等物质性财产。但EasyPR的捐赠略有不同我们不接受财物的捐赠相反我们接受的是数据的捐赠。
如果您有车牌的图片符合以下三个条件并且您愿意捐赠给EasyPR作为研究与开发的帮助那么您可以跟我们联系(easypr_dev@163.com)
一般选择捐赠的图片在5-30张之间最好每张图片有足够不同的特点我们非常高兴能够接受您对我们开发与研究的支持。
捐赠方式可以选择公开或匿名并且您可以选择捐赠后的数据是纳入GDTS作为通用测试集或者保持私密性仅仅作为EasyPR核心团队训练与测试的图片。
捐赠的图片数据不需要太多我们建议您不要捐赠超过30张以上的图片。感谢您为中国开源软件的发展与数据开源运动所做出的贡献
为了保证EasyPR中使用的测试数据不有任何侵犯权利的可能性每个在GDTS(通用数据测试集)上上传的数据都具有以下几个特征:
1.年代久远,不具有时效性的数据(例如,至少半年以前的数据)或者已经处理过相关版权事宜的数据
2.在上传前使用EasyPR提供的函数对图片进行模糊化裁剪性处理确保图片不透露任何可能关于地点时间位置等相关信息
3.在上传前使用EasyPR提供的反人脸识别工具进行处理确保图片不侵犯到任何人的隐私权和肖像权
只有经过以上处理满足以上三个条件的数据EasyPR才会纳入到GDTS里作为车牌识别的准确率衡判依据。
任何不满足以上三个条件的数据EasyPR都会保持这些数据的隐秘不公开等特性并且确保这些数据处于保密状态。
EasyPR团队保证这些研究仅仅作为开源社区学习机器学习、深度学习、图像识别以及计算机视觉的相关资料与参考不会用作任何商用或者恶意行为。
三.免责声明
EasyPR中GDTS(通用数据测试集)的使用仅仅是为了研究与学习目的。
任何使用这些数据进行商用或者恶意窥探目的的行为都违反EasyPR所遵循的开源法则以及研究目的。EasyPR不对这些恶意后果负有任何责任。
EasyPR谴责这些行为但EasyPR不为这些违反开源原则行为的后果负有责任。
当恶意使用者以及用数据牟利者违反了EasyPR约定的这些条款时也就意味着EasyPR不会对他们所造成的任何行为负有责任。
当您使用这些数据时就意味着您已经同意EasyPR的这些约定您对EasyPR通用测试数据集的滥用以及恶意窥探目行为的后果需要您自己承担
EasyPR及其开源团队与贡献者不承担任何相关的责任。
EasyPR团队保留所有权利。
联系方式
EasyPR开发团队的官方邮箱
(easypr_dev@163.com)
###当您复制了EasyPR声明权利的这些数据(或者其中一部分)的同时,您也必须将这份协议复制一份,并保持协议随数据时刻同行。

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

@ -0,0 +1,14 @@
1.general_test存放通用数据测试集GDTS的文件夹EasyPR的开发者会依照这里的图片来判断EasyPR新算法的改进性。
其他开发者也可以通过这个测试集测试自己修改的程序在通用图片集上的表现。
一般来说这个数据集的里的效果表现是低于下面的native_test的。
你可以通过启动EasyPR->2.批量测试->1.general_test来测试EasyPR在通用数据集上的效果表现。
2.native_test存放使用者特定图片数据集的地方。把你特定环境下的图片放到这里进行测试跟GDTS分开。
由于你希望EasyPR只在你的数据集下表现良好那么你可以只把你的数据放到native_test文件夹下把里面原有的文件删除然后运行测试。
你可以试着改变图像处理算法也可以通过EasyPR的训练功能对你的数据集进行训练然后用训练好的模型替代EasyPR
的模型这样的模型比EasyPR的原模型对你的数据适应性更好
你可以通过启动EasyPR->2.批量测试->2.native_test来测试你的效果表现。
3.tmp文件夹存放EasyPR处理过程的中间图片用于调试使用。
注意:通用数据测试集(GDTS)里的数据遵循GDSL协议请阅读同目录下的"GDSL.txt"获得更多信息。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
package com.yuxue;
import org.mybatis.spring.annotation.MapperScan;
import org.opencv.core.Core;
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) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
String version = System.getProperty("java.version");
if (Integer.parseInt(version.substring(0,1)) == 1 ) {
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,59 @@
package com.yuxue.aop;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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,271 @@
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 if (plateColorJudge(src, PlateColor.BLACK, adaptive_minsv) == true) {
return PlateColor.BLACK;
} 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,112 @@
package com.yuxue.easypr.core;
import java.util.Vector;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.Frame;
/**
*
* 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,104 @@
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,60 @@
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),
BLACK("BLACK","黑牌", 0, 180),
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;
}
}

@ -0,0 +1,132 @@
package com.yuxue.video;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import org.opencv.core.Mat;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
public class CompaerImage {
public static String[][] getPX(String args) {
int[] rgb = new int[3];
File file = new File(args);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
}
catch (Exception e) {
e.printStackTrace();
}
int width = bi.getWidth();
int height = bi.getHeight();
int minx = bi.getMinX();
int miny = bi.getMinY();
String[][] list = new String[width][height];
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
int pixel = bi.getRGB(i, j);
rgb[0] = (pixel & 0xff0000) >> 16;
rgb[1] = (pixel & 0xff00) >> 8;
rgb[2] = (pixel & 0xff);
list[i][j] = rgb[0] + "," + rgb[1] + "," + rgb[2];
}
}
return list;
}
public static String[][] getPX(Mat mat) {
int[] rgb = new int[3];
BufferedImage bi = matToBufferImag(mat);
int width = bi.getWidth();
int height = bi.getHeight();
int minx = bi.getMinX();
int miny = bi.getMinY();
String[][] list = new String[width][height];
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
int pixel = bi.getRGB(i, j);
rgb[0] = (pixel & 0xff0000) >> 16;
rgb[1] = (pixel & 0xff00) >> 8;
rgb[2] = (pixel & 0xff);
list[i][j] = rgb[0] + "," + rgb[1] + "," + rgb[2];
}
}
return list;
}
public static BufferedImage matToBufferImag(Mat mat) {
return (BufferedImage) HighGui.toBufferedImage(mat);
}
public static int compareImage(String imgPath1, Mat mat){
// 分析图片相似度 begin
String[][] list1 = getPX(imgPath1);
String[][] list2 = getPX(mat);
int xiangsi = 0;
int busi = 0;
int i = 0, j = 0;
for (String[] strings : list1) {
if ((i + 1) == list1.length) {
continue;
}
for (int m=0; m<strings.length; m++) {
try {
String[] value1 = list1[i][j].toString().split(",");
String[] value2 = list2[i][j].toString().split(",");
int k = 0;
for (int n=0; n<value2.length; n++) {
if (Math.abs(Integer.parseInt(value1[k]) - Integer.parseInt(value2[k])) < 5) {
xiangsi++;
}
else {
busi++;
}
}
}
catch (RuntimeException e) {
continue;
}
j++;
}
i++;
}
String baifen = "";
try {
baifen = ((Double.parseDouble(xiangsi + "") / Double.parseDouble((busi + xiangsi) + "")) + "");
baifen = baifen.substring(baifen.indexOf(".") + 1, baifen.indexOf(".") + 3);
}
catch (Exception e) {
baifen = "0";
}
if (baifen.length() <= 0) {
baifen = "0";
}
if(busi == 0){
baifen="100";
}
return Integer.parseInt(baifen);
}
public static boolean Compare(Mat mat){
int a = 0;
String path = "D:/PlateDetect/Video_Grab"; //要遍历的路径
File file = new File(path); //获取其file对象
File[] fs = file.listFiles(); //遍历path下的文件和目录放在File数组中
for(File f:fs){ //遍历File[]数组
if(!f.isDirectory()) {
String Path = path + f.getName();
a = compareImage(Path, mat);
if(a >= 50) {
return true;
}
}
}
return false;
}
}

@ -0,0 +1,86 @@
package com.yuxue.video;
import java.io.File;
import java.util.Vector;
import static org.bytedeco.javacpp.opencv_core.*;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.Frame;
import com.yuxue.constant.Constant;
import com.yuxue.easypr.core.CharsRecognise;
import com.yuxue.easypr.core.PlateDetect;
public class Judge {
private static CharsRecognise CR = new CharsRecognise();
private static PlateDetect PD = new PlateDetect();
public String judge(Frame frame, int flag) {
Mat mat = convertToMat(frame);
Vector<Mat> resultVec = new Vector<Mat>(1);
if (0 == PD.plateDetect(mat, resultVec)) {
if(resultVec.size()>0){
String dirname = "D:/PlateDetect/Video_Grab/Temp/" + System.currentTimeMillis() +"/";
File d = new File(dirname);
d.mkdirs();
return CR.charsRecognise(resultVec.get(0), dirname);
}
}
return null;
}
public Mat convertToMat(Frame frame) {
Mat mat = null;
if (frame == null || frame.image == null) {
return null;
} else if (frame.opaque instanceof Mat) {
return (Mat)frame.opaque;
} else if (!isEqual(frame, mat)) {
int depth = getMatDepth(frame.imageDepth);
mat = depth < 0 ? null : new Mat(frame.imageHeight, frame.imageWidth, CV_MAKETYPE(depth, frame.imageChannels),
new Pointer(frame.image[0].position(0)), frame.imageStride * Math.abs(frame.imageDepth) / 8);
}
return mat;
}
private int CV_MAKETYPE(int depth, int imageChannels) {
switch (depth) {
case CV_8U: return CV_8UC3;
case CV_8S: return CV_8SC3;
case CV_16U: return CV_16UC3;
case CV_16S: return CV_16SC3;
case CV_32F: return CV_32FC3;
case CV_32S: return CV_32SC3;
case CV_64F: return CV_64FC3;
}
return 0;
}
static boolean isEqual(Frame frame, Mat mat) {
return mat != null && frame != null && frame.image != null && frame.image.length > 0
&& frame.imageWidth == mat.cols() && frame.imageHeight == mat.rows()
&& frame.imageChannels == mat.channels() && getMatDepth(frame.imageDepth) == mat.depth()
&& new Pointer(frame.image[0]).address() == mat.data().address()
&& frame.imageStride * Math.abs(frame.imageDepth) / 8 == (int)mat.step();
}
public static int getMatDepth(int depth) {
switch (depth) {
case Frame.DEPTH_UBYTE: return CV_8U;
case Frame.DEPTH_BYTE: return CV_8S;
case Frame.DEPTH_USHORT: return CV_16U;
case Frame.DEPTH_SHORT: return CV_16S;
case Frame.DEPTH_FLOAT: return CV_32F;
case Frame.DEPTH_INT: return CV_32S;
case Frame.DEPTH_DOUBLE: return CV_64F;
default: return -1;
}
}
}

@ -0,0 +1,80 @@
package com.yuxue.video;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
public class VideoDemo {
private static Judge judge = new Judge();
private static final String Video_Path = "D:/PlateDetect/Video/Test1.mp4";
private static final String Save_Path = "D:/PlateDetect/Video_Grab/";
public void Video() {
//Frame对象
Frame frame = null;
//标识
int flag = 0;
//打开视频文件
FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(Video_Path);
String A = "";
try {
fFmpegFrameGrabber.start();
//获取总帧数
int count = fFmpegFrameGrabber.getLengthInFrames();
//获取帧率
int fps = (int) fFmpegFrameGrabber.getFrameRate();
//获取总时长
double Length = count / fps;
int Temp = 2;
while (flag <= Length) {
//每秒获取一次图片
for(int i=0;i<Temp*fps;i++) {
frame = fFmpegFrameGrabber.grabImage();
}
if (frame != null) {
A = judge.judge(frame, flag);
if(A != null) {
char[] cs = A.toCharArray();
if(cs.length == 7) {
//文件绝对路径+名字
String fileName = Save_Path + A + ".jpg";
//文件储存对象
File outPut = new File(fileName);
ImageIO.write(FrameToBufferedImage(frame), "jpg", outPut);
}
}
}
flag = flag + Temp;
}
System.out.println("识别已结束");
fFmpegFrameGrabber.stop();
} catch (Exception E) { }
}
public static BufferedImage FrameToBufferedImage(Frame frame) {
//创建BufferedImage对象
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.getBufferedImage(frame);
return bufferedImage;
}
public static void main(String[] args) {
VideoDemo V = new VideoDemo();
V.Video();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

@ -0,0 +1,19 @@
#server:
# port: 8888
spring:
druid:
#datasource:
#eclipse启动的时候默认加载的是target目录下的文件
url: jdbc:sqlite::resource:yx_image_recognition.db?date_string_format=yyyy-MM-dd HH:mm:ss
username: sqlite
password: sqlite
driver-class-name: org.sqlite.JDBC
max-active: 10 #最大连接数
min-idle: 5 #最小连接数
max-wait: 10000 #获取连接的最大等待时间
time-between-eviction-runs-millis: 60000 #空闲连接的检查时间间隔
min-evictable-idle-time-millis: 300000 #空闲连接最小空闲时间

@ -0,0 +1,15 @@
spring:
druid:
#datasource:
#eclipse启动的时候默认加载的是target目录下的文件
# :config 当前jar包所在目录下的config
url: jdbc:sqlite:config/yx_image_recognition.db?date_string_format=yyyy-MM-dd HH:mm:ss
username: sqlite
password: sqlite
driver-class-name: org.sqlite.JDBC
max-active: 10 #最大连接数
min-idle: 5 #最小连接数
max-wait: 10000 #获取连接的最大等待时间
time-between-eviction-runs-millis: 60000 #空闲连接的检查时间间隔
min-evictable-idle-time-millis: 300000 #空闲连接最小空闲时间

@ -0,0 +1,52 @@
server:
port: 16666
servlet:
context-path: /
spring:
application:
name : demo
mvc:
favicon:
enabled: true
messages:
basename: i18n.login
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
## 环境配置文件 dev sqlite
profiles:
active: dev
## 静态页面配置
thymeleaf:
#热部署文件,页面不产生缓存,及时更新
cache: false
prefix: classpath:static/templates/
suffix: .html
encoding: UTF-8
## Mybatis config
mybatis:
mapperLocations: classpath:mapper/**/*.xml
configLocation: classpath:mybatis.xml
## pagehelper
pagehelper:
helperDialect: sqlite #postgresql
reasonable: true
supportMethodsArguments: true
params: countSql
count: countSql
returnPageInfo: check
## 记录日志
logging:
config: classpath:logback-spring.xml
## Start logging
level:
root: INFO

@ -0,0 +1,7 @@
██╗ ██╗██╗ ██╗██╗ ██╗██╗ ██╗███████╗
╚██╗ ██╔╝██║ ██║╚██╗██╔╝██║ ██║██╔════╝
╚████╔╝ ██║ ██║ ╚███╔╝ ██║ ██║█████╗
╚██╔╝ ██║ ██║ ██╔██╗ ██║ ██║██╔══╝
██║ ╚██████╔╝██╔╝ ██╗╚██████╔╝███████╗
╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
:: YX Boot :: Power By SpringBoot (v2.1.0.RELEASE)

@ -0,0 +1,324 @@
#include <numeric>
#include <ctime>
#include "easypr/train/ann_train.h"
#include "easypr/config.h"
#include "easypr/core/chars_identify.h"
#include "easypr/core/feature.h"
#include "easypr/core/core_func.h"
#include "easypr/train/create_data.h"
#include "easypr/util/util.h"
// 原版C++语言 训练代码
namespace easypr {
AnnTrain::AnnTrain(const char* chars_folder, const char* xml): chars_folder_(chars_folder), ann_xml_(xml) {
ann_ = cv::ml::ANN_MLP::create();
type = 0; // type=0, 所有字符type=1, 只有中文字符
kv_ = std::shared_ptr<Kv>(new Kv);
kv_->load("resources/text/province_mapping"); // zh_cuan 川 zh_gan1 甘
}
void AnnTrain::train() {
int classNumber = 0;
cv::Mat layers;
int input_number = 0;
int hidden_number = 0;
int output_number = 0;
if (type == 0) {
classNumber = kCharsTotalNumber;
input_number = kAnnInput;
hidden_number = kNeurons;
output_number = classNumber;
} else if (type == 1) {
classNumber = kChineseNumber;
input_number = kAnnInput;
hidden_number = kNeurons;
output_number = classNumber;
}
int N = input_number;
int m = output_number;
int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2)));
int second_hidden_neurons = int(m * std::sqrt(N / (m + 2)));
bool useTLFN = false;
if (!useTLFN) {
layers.create(1, 3, CV_32SC1);
layers.at<int>(0) = input_number;
layers.at<int>(1) = hidden_number;
layers.at<int>(2) = output_number;
} else {
// 两层神经网络很难训练,所以不要尝试
fprintf(stdout, ">> Use two-layers neural networks,\n");
layers.create(1, 4, CV_32SC1);
layers.at<int>(0) = input_number;
layers.at<int>(1) = first_hidden_neurons;
layers.at<int>(2) = second_hidden_neurons;
layers.at<int>(3) = output_number;
}
ann_->setLayerSizes(layers);
ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP);
ann_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 30000, 0.0001));
ann_->setBackpropWeightScale(0.1);
ann_->setBackpropMomentumScale(0.1);
auto files = Utils::getFiles(chars_folder_);
if (files.size() == 0) {
fprintf(stdout, "No file found in the train folder!\n");
return;
}
// 使用原始数据 或者原始数据+合成数据
auto traindata = sdata(350);
ann_->train(traindata);
ann_->save(ann_xml_);
test();
}
// 识别 中文
std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize);
float maxVal = -2;
int result = 0;
cv::Mat output(1, kChineseNumber, CV_32FC1);
ann_->predict(feature, output);
for (int j = 0; j < kChineseNumber; j++) {
float val = output.at<float>(j);
if (val > maxVal) {
maxVal = val;
result = j;
}
}
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
return std::make_pair(s, province);
}
// 识别 字符
std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize);
float maxVal = -2;
int result = 0;
cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
ann_->predict(feature, output);
for (int j = 0; j < kCharsTotalNumber; j++) {
float val = output.at<float>(j);
if (val > maxVal) {
maxVal = val;
result = j;
}
}
auto index = result;
if (index < kCharactersNumber) {
return std::make_pair(kChars[index], kChars[index]);
}
else {
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
return std::make_pair(s, province);
}
}
// 测试并计算准确率
void AnnTrain::test() {
assert(chars_folder_);
int classNumber = 0;
if (type == 0) classNumber = kCharsTotalNumber;
if (type == 1) classNumber = kChineseNumber;
int corrects_all = 0, sum_all = 0;
std::vector<float> rate_list;
for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
char sub_folder[512] = { 0 };
sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder);
auto chars_files = utils::getFiles(sub_folder);
int corrects = 0, sum = 0;
std::vector<std::pair<std::string, std::string>> error_files;
for (auto file : chars_files) {
auto img = cv::imread(file, 0); // 读取灰度图像
if (!img.data) {
continue;
}
std::pair<std::string, std::string> ch;
if (type == 0) ch = identify(img);
if (type == 1) ch = identifyChinese(img);
if (ch.first == char_key) {
++corrects;
++corrects_all;
} else {
error_files.push_back(std::make_pair(utils::getFileName(file), ch.second));
}
++sum;
++sum_all;
}
float rate = (float)corrects / (sum == 0 ? 1 : sum);
rate_list.push_back(rate);
std::string error_string;
auto end = error_files.end();
if (error_files.size() >= 10) {
end -= static_cast<size_t>(error_files.size() * (1 - 0.1));
}
for (auto k = error_files.begin(); k != end; ++k) {
auto kv = *k;
error_string.append(" ").append(kv.first).append(": ").append(kv.second);
if (k != end - 1) {
error_string.append(",\n");
} else {
error_string.append("\n ...");
}
}
fprintf(stdout, ">> [\n%s\n ]\n", error_string.c_str());
}
fprintf(stdout, ">> [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all, (float)corrects_all / (sum_all == 0 ? 1 : sum_all));
double rate_sum = std::accumulate(rate_list.begin(), rate_list.end(), 0.0);
double rate_mean = rate_sum / (rate_list.size() == 0 ? 1 : rate_list.size());
fprintf(stdout, ">> [classNumber: %d, avg_rate: %.4f]\n", classNumber, rate_mean);
}
// 获取合成图像
cv::Mat getSyntheticImage(const Mat& image) {
int rand_type = rand();
Mat result = image.clone();
if (rand_type % 2 == 0) {
int ran_x = rand() % 5 - 2;
int ran_y = rand() % 5 - 2;
result = translateImg(result, ran_x, ran_y);
} else if (rand_type % 2 != 0) {
float angle = float(rand() % 15 - 7);
result = rotateImg(result, angle);
}
return result;
}
// 处理训练文件
cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> labels;
int classNumber = 0;
if (type == 0) classNumber = kCharsTotalNumber;
if (type == 1) classNumber = kChineseNumber;
srand((unsigned)time(0));
for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
char sub_folder[512] = { 0 };
sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder);
auto chars_files = utils::getFiles(sub_folder);
size_t char_size = chars_files.size();
fprintf(stdout, ">> Characters count: %d \n", int(char_size));
std::vector<cv::Mat> matVec;
matVec.reserve(number_for_count);
for (auto file : chars_files) {
auto img = cv::imread(file, 0); // a grayscale image
matVec.push_back(img);
}
for (int t = 0; t < (int)number_for_count - (int)char_size; t++) {
int rand_range = char_size + t;
int ran_num = rand() % rand_range;
auto img = matVec.at(ran_num);
auto simg = getSyntheticImage(img);
matVec.push_back(simg);
}
for (auto img : matVec) {
auto fps = charFeatures2(img, kPredictSize);
samples.push_back(fps);
labels.push_back(i);
}
}
cv::Mat samples_;
samples.convertTo(samples_, CV_32F);
cv::Mat train_classes = cv::Mat::zeros((int)labels.size(), classNumber, CV_32F);
for (int i = 0; i < train_classes.rows; ++i) {
train_classes.at<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, train_classes);
}
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> labels;
std::cout << "Collecting chars in " << chars_folder_ << std::endl;
int classNumber = 0;
if (type == 0) classNumber = kCharsTotalNumber;
if (type == 1) classNumber = kChineseNumber;
for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
char sub_folder[512] = {0};
sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
std::cout << " >> Featuring characters " << char_key << " in " << sub_folder << std::endl;
auto chars_files = utils::getFiles(sub_folder);
for (auto file : chars_files) {
auto img = cv::imread(file, 0); // 读取灰度图像
auto fps = charFeatures2(img, kPredictSize);
samples.push_back(fps);
labels.push_back(i);
}
}
cv::Mat samples_;
samples.convertTo(samples_, CV_32F);
cv::Mat train_classes = cv::Mat::zeros((int)labels.size(), classNumber, CV_32F);
for (int i = 0; i < train_classes.rows; ++i) {
train_classes.at<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, train_classes);
}
}

@ -0,0 +1,454 @@
#include "easypr/core/chars_identify.h"
#include "easypr/core/character.hpp"
#include "easypr/core/core_func.h"
#include "easypr/core/feature.h"
#include "easypr/core/params.h"
#include "easypr/config.h"
using namespace cv;
namespace easypr {
CharsIdentify* CharsIdentify::instance_ = nullptr;
CharsIdentify* CharsIdentify::instance() {
if (!instance_) {
instance_ = new CharsIdentify;
}
return instance_;
}
CharsIdentify::CharsIdentify() {
LOAD_ANN_MODEL(ann_, kDefaultAnnPath);
LOAD_ANN_MODEL(annChinese_, kChineseAnnPath);
LOAD_ANN_MODEL(annGray_, kGrayAnnPath);
kv_ = std::shared_ptr<Kv>(new Kv);
kv_->load(kChineseMappingPath);
extractFeature = getGrayPlusProject;
}
void CharsIdentify::LoadModel(std::string path) {
if (path != std::string(kDefaultAnnPath)) {
if (!ann_->empty())
ann_->clear();
LOAD_ANN_MODEL(ann_, path);
}
}
void CharsIdentify::LoadChineseModel(std::string path) {
if (path != std::string(kChineseAnnPath)) {
if (!annChinese_->empty())
annChinese_->clear();
LOAD_ANN_MODEL(annChinese_, path);
}
}
void CharsIdentify::LoadGrayChANN(std::string path) {
if (path != std::string(kGrayAnnPath)) {
if (!annGray_->empty())
annGray_->clear();
LOAD_ANN_MODEL(annGray_, path);
}
}
void CharsIdentify::LoadChineseMapping(std::string path) {
kv_->clear();
kv_->load(path);
}
void CharsIdentify::classify(cv::Mat featureRows, std::vector<int>& out_maxIndexs,
std::vector<float>& out_maxVals, std::vector<bool> isChineseVec){
int rowNum = featureRows.rows;
cv::Mat output(rowNum, kCharsTotalNumber, CV_32FC1);
ann_->predict(featureRows, output);
for (int output_index = 0; output_index < rowNum; output_index++) {
Mat output_row = output.row(output_index);
int result = 0;
float maxVal = -2.f;
bool isChinses = isChineseVec[output_index];
if (!isChinses) {
result = 0;
for (int j = 0; j < kCharactersNumber; j++) {
float val = output_row.at<float>(j);
// std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
}
else {
result = kCharactersNumber;
for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
float val = output_row.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
}
out_maxIndexs[output_index] = result;
out_maxVals[output_index] = maxVal;
}
}
void CharsIdentify::classify(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
if (charVecSize == 0)
return;
Mat featureRows;
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
Mat feature = charFeatures(charInput, kPredictSize);
featureRows.push_back(feature);
}
cv::Mat output(charVecSize, kCharsTotalNumber, CV_32FC1);
ann_->predict(featureRows, output);
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
int result = 0;
float maxVal = -2.f;
std::string label = "";
bool isChinses = character.getIsChinese();
if (!isChinses) {
result = 0;
for (int j = 0; j < kCharactersNumber; j++) {
float val = output_row.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
label = std::make_pair(kChars[result], kChars[result]).second;
}
else {
result = kCharactersNumber;
for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
float val = output_row.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
const char* key = kChars[result];
std::string s = key;
std::string province = kv_->get(s);
label = std::make_pair(s, province).second;
}
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
character.setCharacterScore(maxVal);
character.setCharacterStr(label);
}
}
void CharsIdentify::classifyChineseGray(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
if (charVecSize == 0)
return;
Mat featureRows;
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
cv::Mat feature;
extractFeature(charInput, feature);
featureRows.push_back(feature);
}
cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
annGray_->predict(featureRows, output);
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
bool isChinese = true;
float maxVal = -2;
int result = 0;
for (int j = 0; j < kChineseNumber; j++) {
float val = output_row.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
// no match
if (-1 == result) {
result = 0;
maxVal = 0;
isChinese = false;
}
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
character.setCharacterScore(maxVal);
character.setCharacterStr(province);
character.setIsChinese(isChinese);
}
}
void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
if (charVecSize == 0)
return;
Mat featureRows;
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
Mat feature = charFeatures(charInput, kChineseSize);
featureRows.push_back(feature);
}
cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
annChinese_->predict(featureRows, output);
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
bool isChinese = true;
float maxVal = -2;
int result = 0;
for (int j = 0; j < kChineseNumber; j++) {
float val = output_row.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
// no match
if (-1 == result) {
result = 0;
maxVal = 0;
isChinese = false;
}
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
character.setCharacterScore(maxVal);
character.setCharacterStr(province);
character.setIsChinese(isChinese);
}
}
int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){
int result = 0;
cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
ann_->predict(f, output);
maxVal = -2.f;
if (!isChinses) {
if (!isAlphabet) {
result = 0;
for (int j = 0; j < kCharactersNumber; j++) {
float val = output.at<float>(j);
// std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
}
else {
result = 0;
// begin with 11th char, which is 'A'
for (int j = 10; j < kCharactersNumber; j++) {
float val = output.at<float>(j);
// std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
}
}
else {
result = kCharactersNumber;
for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
float val = output.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
}
//std::cout << "maxVal:" << maxVal << std::endl;
return result;
}
bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese) {
cv::Mat feature = charFeatures(input, kPredictSize);
auto index = static_cast<int>(classify(feature, maxVal, isChinese));
if (isChinese) {
//std::cout << "maxVal:" << maxVal << std::endl;
}
float chineseMaxThresh = 0.2f;
if (maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)) {
if (index < kCharactersNumber) {
label = std::make_pair(kChars[index], kChars[index]).second;
}
else {
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
label = std::make_pair(s, province).second;
}
return true;
}
else
return false;
}
std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input, float& out, bool& isChinese) {
cv::Mat feature = charFeatures(input, kChineseSize);
float maxVal = -2;
int result = 0;
cv::Mat output(1, kChineseNumber, CV_32FC1);
annChinese_->predict(feature, output);
for (int j = 0; j < kChineseNumber; j++) {
float val = output.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
// no match
if (-1 == result) {
result = 0;
maxVal = 0;
isChinese = false;
}
else if (maxVal > 0.9){
isChinese = true;
}
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
out = maxVal;
return std::make_pair(s, province);
}
std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat input, float& out, bool& isChinese) {
cv::Mat feature;
extractFeature(input, feature);
float maxVal = -2;
int result = 0;
cv::Mat output(1, kChineseNumber, CV_32FC1);
annGray_->predict(feature, output);
for (int j = 0; j < kChineseNumber; j++) {
float val = output.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
if (val > maxVal) {
maxVal = val;
result = j;
}
}
// no match
if (-1 == result) {
result = 0;
maxVal = 0;
isChinese = false;
} else if (maxVal > 0.9){
isChinese = true;
}
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
out = maxVal;
return std::make_pair(s, province);
}
std::pair<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) {
cv::Mat feature = charFeatures(input, kPredictSize);
float maxVal = -2;
auto index = static_cast<int>(classify(feature, maxVal, isChinese, isAlphabet));
if (index < kCharactersNumber) {
return std::make_pair(kChars[index], kChars[index]);
}
else {
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
return std::make_pair(s, province);
}
}
int CharsIdentify::identify(std::vector<cv::Mat> inputs, std::vector<std::pair<std::string, std::string>>& outputs,
std::vector<bool> isChineseVec) {
Mat featureRows;
size_t input_size = inputs.size();
for (size_t i = 0; i < input_size; i++) {
Mat input = inputs[i];
cv::Mat feature = charFeatures(input, kPredictSize);
featureRows.push_back(feature);
}
std::vector<int> maxIndexs;
std::vector<float> maxVals;
classify(featureRows, maxIndexs, maxVals, isChineseVec);
for (size_t row_index = 0; row_index < input_size; row_index++) {
int index = maxIndexs[row_index];
if (index < kCharactersNumber) {
outputs[row_index] = std::make_pair(kChars[index], kChars[index]);
}
else {
const char* key = kChars[index];
std::string s = key;
std::string province = kv_->get(s);
outputs[row_index] = std::make_pair(s, province);
}
}
return 0;
}
}

@ -0,0 +1,117 @@
#include "easypr/core/chars_recognise.h"
#include "easypr/core/character.hpp"
#include "easypr/util/util.h"
#include <ctime>
namespace easypr {
CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); }
CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); }
int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) {
std::vector<Mat> matChars;
int result = m_charsSegment->charsSegment(plate, matChars);
if (result == 0) {
int num = matChars.size();
for (int j = 0; j < num; j++)
{
Mat charMat = matChars.at(j);
bool isChinses = false;
float maxVal = 0;
if (j == 0) {
bool judge = true;
isChinses = true;
auto character = CharsIdentify::instance()->identifyChinese(charMat, maxVal, judge);
plateLicense.append(character.second);
}
else {
isChinses = false;
auto character = CharsIdentify::instance()->identify(charMat, isChinses);
plateLicense.append(character.second);
}
}
}
if (plateLicense.size() < 7) {
return -1;
}
return result;
}
int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) {
std::vector<Mat> matChars;
std::vector<Mat> grayChars;
Mat plateMat = plate.getPlateMat();
if (0) writeTempImage(plateMat, "plateMat/plate");
Color color;
if (plate.getPlateLocateType() == CMSER) {
color = plate.getPlateColor();
}
else {
int w = plateMat.cols;
int h = plateMat.rows;
Mat tmpMat = plateMat(Rect_<double>(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
color = getPlateType(tmpMat, true);
}
int result = m_charsSegment->charsSegmentUsingOSTU(plateMat, matChars, grayChars, color);
if (result == 0) {
int num = matChars.size();
for (int j = 0; j < num; j++)
{
Mat charMat = matChars.at(j);
Mat grayChar = grayChars.at(j);
if (color != Color::BLUE)
grayChar = 255 - grayChar;
bool isChinses = false;
std::pair<std::string, std::string> character;
float maxVal;
if (0 == j) {
isChinses = true;
bool judge = true;
character = CharsIdentify::instance()->identifyChineseGray(grayChar, maxVal, judge);
plateLicense.append(character.second);
// set plate chinese mat and str
plate.setChineseMat(grayChar);
plate.setChineseKey(character.first);
if (0) writeTempImage(grayChar, "char_data/" + character.first + "/chars_");
}
else if (1 == j) {
isChinses = false;
bool isAbc = true;
character = CharsIdentify::instance()->identify(charMat, isChinses, isAbc);
plateLicense.append(character.second);
}
else {
isChinses = false;
SHOW_IMAGE(charMat, 0);
character = CharsIdentify::instance()->identify(charMat, isChinses);
plateLicense.append(character.second);
}
CCharacter charResult;
charResult.setCharacterMat(charMat);
charResult.setCharacterGrayMat(grayChar);
if (isChinses)
charResult.setCharacterStr(character.first);
else
charResult.setCharacterStr(character.second);
plate.addReutCharacter(charResult);
}
if (plateLicense.size() < 7) {
return -1;
}
}
return result;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,466 @@
#include "easypr/core/feature.h"
#include "easypr/core/core_func.h"
#include "thirdparty/LBP/lbp.hpp"
namespace easypr {
Mat getHistogram(Mat in) {
const int VERTICAL = 0;
const int HORIZONTAL = 1;
// Histogram features
Mat vhist = ProjectedHistogram(in, VERTICAL);
Mat hhist = ProjectedHistogram(in, HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.cols + hhist.cols;
Mat out = Mat::zeros(1, numCols, CV_32F);
int j = 0;
for (int i = 0; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
return out;
}
void getHistogramFeatures(const Mat& image, Mat& features) {
Mat grayImage;
cvtColor(image, grayImage, CV_RGB2GRAY);
//grayImage = histeq(grayImage);
Mat img_threshold;
threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
//Mat img_threshold = grayImage.clone();
//spatial_ostu(img_threshold, 8, 2, getPlateType(image, false));
features = getHistogram(img_threshold);
}
// compute color histom
void getColorFeatures(const Mat& src, Mat& features) {
Mat src_hsv;
//grayImage = histeq(grayImage);
cvtColor(src, src_hsv, CV_BGR2HSV);
int channels = src_hsv.channels();
int nRows = src_hsv.rows;
// consider multi channel image
int nCols = src_hsv.cols * channels;
if (src_hsv.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
const int sz = 180;
int h[sz] = { 0 };
uchar* p;
for (int i = 0; i < nRows; ++i) {
p = src_hsv.ptr<uchar>(i);
for (int j = 0; j < nCols; j += 3) {
int H = int(p[j]); // 0-180
if (H > sz - 1) H = sz - 1;
if (H < 0) H = 0;
h[H]++;
}
}
Mat mhist = Mat::zeros(1, sz, CV_32F);
for (int j = 0; j < sz; j++) {
mhist.at<float>(j) = (float)h[j];
}
// Normalize histogram
double min, max;
minMaxLoc(mhist, &min, &max);
if (max > 0)
mhist.convertTo(mhist, -1, 1.0f / max, 0);
features = mhist;
}
void getHistomPlusColoFeatures(const Mat& image, Mat& features) {
// TODO
Mat feature1, feature2;
getHistogramFeatures(image, feature1);
getColorFeatures(image, feature2);
hconcat(feature1.reshape(1, 1), feature2.reshape(1, 1), features);
}
void getSIFTFeatures(const Mat& image, Mat& features) {
// TODO
}
//HOG Features
void getHOGFeatures(const Mat& image, Mat& features) {
//HOG descripter
HOGDescriptor hog(cvSize(128, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 3); //these parameters work well
std::vector<float> descriptor;
// resize input image to (128,64) for compute
Size dsize = Size(128,64);
Mat trainImg = Mat(dsize, CV_32S);
resize(image, trainImg, dsize);
// compute descripter
hog.compute(trainImg, descriptor, Size(8, 8));
// copy the result
Mat mat_featrue(descriptor);
mat_featrue.copyTo(features);
}
void getHSVHistFeatures(const Mat& image, Mat& features) {
// TODO
}
//! LBP feature
void getLBPFeatures(const Mat& image, Mat& features) {
Mat grayImage;
cvtColor(image, grayImage, CV_RGB2GRAY);
Mat lbpimage;
lbpimage = libfacerec::olbp(grayImage);
Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 32, 4, 4);
features = lbp_hist;
}
Mat charFeatures(Mat in, int sizeData) {
const int VERTICAL = 0;
const int HORIZONTAL = 1;
// cut the cetner, will afect 5% perices.
Rect _rect = GetCenterRect(in);
Mat tmpIn = CutTheRect(in, _rect);
//Mat tmpIn = in.clone();
// Low data feature
Mat lowData;
resize(tmpIn, lowData, Size(sizeData, sizeData));
// Histogram features
Mat vhist = ProjectedHistogram(lowData, VERTICAL);
Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
Mat out = Mat::zeros(1, numCols, CV_32F);
// Asign values to
int j = 0;
for (int i = 0; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
for (int x = 0; x < lowData.cols; x++) {
for (int y = 0; y < lowData.rows; y++) {
out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
j++;
}
}
//std::cout << out << std::endl;
return out;
}
Mat charFeatures2(Mat in, int sizeData) {
const int VERTICAL = 0;
const int HORIZONTAL = 1;
// cut the cetner, will afect 5% perices.
Rect _rect = GetCenterRect(in);
Mat tmpIn = CutTheRect(in, _rect);
//Mat tmpIn = in.clone();
// Low data feature
Mat lowData;
resize(tmpIn, lowData, Size(sizeData, sizeData));
// Histogram features
Mat vhist = ProjectedHistogram(lowData, VERTICAL);
Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
Mat out = Mat::zeros(1, numCols, CV_32F);
int j = 0;
for (int i = 0; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
for (int x = 0; x < lowData.cols; x++) {
for (int y = 0; y < lowData.rows; y++) {
out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
j++;
}
}
//std::cout << out << std::endl;
return out;
}
Mat charProjectFeatures(const Mat& in, int sizeData) {
const int VERTICAL = 0;
const int HORIZONTAL = 1;
SHOW_IMAGE(in, 0);
// cut the cetner, will afect 5% perices.
Mat lowData;
resize(in, lowData, Size(sizeData, sizeData));
SHOW_IMAGE(lowData, 0);
// Histogram features
Mat vhist = ProjectedHistogram(lowData, VERTICAL);
Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.cols + hhist.cols;
Mat out = Mat::zeros(1, numCols, CV_32F);
int j = 0;
for (int i = 0; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
//std::cout << out << std::endl;
return out;
}
void getGrayCharFeatures(const Mat& grayChar, Mat& features) {
// TODO: check channnels == 1
SHOW_IMAGE(grayChar, 0);
SHOW_IMAGE(255 - grayChar, 0);
// resize to uniform size, like 20x32
bool useResize = false;
bool useConvert = true;
bool useMean = true;
bool useLBP = false;
Mat char_mat;
if (useResize) {
char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
} else {
char_mat = grayChar;
}
SHOW_IMAGE(char_mat, 0);
// convert to float
Mat float_img;
if (useConvert) {
float scale = 1.f / 255;
char_mat.convertTo(float_img, CV_32FC1, scale, 0);
} else {
float_img = char_mat;
}
SHOW_IMAGE(float_img, 0);
// cut from mean, it can be optional
Mat mean_img;
if (useMean) {
float_img -= mean(float_img);
mean_img = float_img;
} else {
mean_img = float_img;
}
SHOW_IMAGE(mean_img, 0);
// use lbp to get features, it can be changed to other
Mat feautreImg;
if (useLBP) {
Mat lbpimage = libfacerec::olbp(char_mat);
SHOW_IMAGE(lbpimage, 0);
feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
} else {
feautreImg = mean_img.reshape(1, 1);
}
// return back
features = feautreImg;
}
void getGrayPlusProject(const Mat& grayChar, Mat& features)
{
// TODO: check channnels == 1
SHOW_IMAGE(grayChar, 0);
SHOW_IMAGE(255 - grayChar, 0);
// resize to uniform size, like 20x32
bool useResize = false;
bool useConvert = true;
bool useMean = true;
bool useLBP = false;
Mat char_mat;
if (useResize) {
char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
}
else {
char_mat = grayChar;
}
SHOW_IMAGE(char_mat, 0);
// convert to float
Mat float_img;
if (useConvert) {
float scale = 1.f / 255;
char_mat.convertTo(float_img, CV_32FC1, scale, 0);
}
else {
float_img = char_mat;
}
SHOW_IMAGE(float_img, 0);
// cut from mean, it can be optional
Mat mean_img;
if (useMean) {
float_img -= mean(float_img);
mean_img = float_img;
}
else {
mean_img = float_img;
}
SHOW_IMAGE(mean_img, 0);
// use lbp to get features, it can be changed to other
Mat feautreImg;
if (useLBP) {
Mat lbpimage = libfacerec::olbp(char_mat);
SHOW_IMAGE(lbpimage, 0);
feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
}
else {
feautreImg = mean_img.reshape(1, 1);
}
SHOW_IMAGE(grayChar, 0);
Mat binaryChar;
threshold(grayChar, binaryChar, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SHOW_IMAGE(binaryChar, 0);
Mat projectFeature = charProjectFeatures(binaryChar, 32);
hconcat(feautreImg.reshape(1, 1), projectFeature.reshape(1, 1), features);
}
void getGrayPlusLBP(const Mat& grayChar, Mat& features)
{
// TODO: check channnels == 1
SHOW_IMAGE(grayChar, 0);
SHOW_IMAGE(255 - grayChar, 0);
// resize to uniform size, like 20x32
bool useResize = false;
bool useConvert = true;
bool useMean = true;
bool useLBP = true;
Mat char_mat;
if (useResize) {
char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
}
else {
char_mat = grayChar;
}
SHOW_IMAGE(char_mat, 0);
// convert to float
Mat float_img;
if (useConvert) {
float scale = 1.f / 255;
char_mat.convertTo(float_img, CV_32FC1, scale, 0);
}
else {
float_img = char_mat;
}
SHOW_IMAGE(float_img, 0);
// cut from mean, it can be optional
Mat mean_img;
if (useMean) {
float_img -= mean(float_img);
mean_img = float_img;
}
else {
mean_img = float_img;
}
SHOW_IMAGE(mean_img, 0);
// use lbp to get features, it can be changed to other
Mat originImage = mean_img.clone();
Mat lbpimage = libfacerec::olbp(mean_img);
SHOW_IMAGE(lbpimage, 0);
lbpimage = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
// 32x20 + 16x16
hconcat(mean_img.reshape(1, 1), lbpimage.reshape(1, 1), features);
}
void getLBPplusHistFeatures(const Mat& image, Mat& features) {
Mat grayImage;
cvtColor(image, grayImage, CV_RGB2GRAY);
Mat lbpimage;
lbpimage = libfacerec::olbp(grayImage);
Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 64, 8, 4);
//features = lbp_hist.reshape(1, 1);
Mat greyImage;
cvtColor(image, greyImage, CV_RGB2GRAY);
//grayImage = histeq(grayImage);
Mat img_threshold;
threshold(greyImage, img_threshold, 0, 255,
CV_THRESH_OTSU + CV_THRESH_BINARY);
Mat histomFeatures = getHistogram(img_threshold);
hconcat(lbp_hist.reshape(1, 1), histomFeatures.reshape(1, 1), features);
//std::cout << features << std::endl;
//features = histomFeatures;
}
}

@ -0,0 +1,12 @@
#include "easypr/core/params.h"
namespace easypr {
CParams* CParams::instance_ = nullptr;
CParams* CParams::instance() {
if (!instance_) {
instance_ = new CParams;
}
return instance_;
}
}/*! \namespace easypr*/

@ -0,0 +1,77 @@
#include "easypr/core/plate_detect.h"
#include "easypr/util/util.h"
#include "easypr/core/core_func.h"
#include "easypr/config.h"
namespace easypr {
CPlateDetect::CPlateDetect() {
m_plateLocate = new CPlateLocate();
m_maxPlates = 3;
m_type = 0;
m_showDetect = false;
}
CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); }
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int type,
bool showDetectArea, int img_index) {
std::vector<CPlate> sobel_Plates;
sobel_Plates.reserve(16);
std::vector<CPlate> color_Plates;
color_Plates.reserve(16);
std::vector<CPlate> mser_Plates;
mser_Plates.reserve(16);
std::vector<CPlate> all_result_Plates;
all_result_Plates.reserve(64);
#pragma omp parallel sections
{
#pragma omp section
{
if (!type || type & PR_DETECT_SOBEL) {
m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index);
}
}
#pragma omp section
{
if (!type || type & PR_DETECT_COLOR) {
m_plateLocate->plateColorLocate(src, color_Plates, img_index);
}
}
#pragma omp section
{
if (!type || type & PR_DETECT_CMSER) {
m_plateLocate->plateMserLocate(src, mser_Plates, img_index);
}
}
}
for (auto plate : sobel_Plates) {
plate.setPlateLocateType(SOBEL);
all_result_Plates.push_back(plate);
}
for (auto plate : color_Plates) {
plate.setPlateLocateType(COLOR);
all_result_Plates.push_back(plate);
}
for (auto plate : mser_Plates) {
plate.setPlateLocateType(CMSER);
all_result_Plates.push_back(plate);
}
// use nms to judge plate
PlateJudge::instance()->plateJudgeUsingNMS(all_result_Plates, resultVec, m_maxPlates);
if (0)
showDectectResults(src, resultVec, m_maxPlates);
return 0;
}
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int img_index) {
int result = plateDetect(src, resultVec, m_type, false, img_index);
return result;
}
void CPlateDetect::LoadSVM(std::string path) {
PlateJudge::instance()->LoadModel(path);
}
}

@ -0,0 +1,193 @@
#include "easypr/core/plate_judge.h"
#include "easypr/config.h"
#include "easypr/core/core_func.h"
#include "easypr/core/params.h"
namespace easypr {
PlateJudge* PlateJudge::instance_ = nullptr;
PlateJudge* PlateJudge::instance() {
if (!instance_) {
instance_ = new PlateJudge;
}
return instance_;
}
PlateJudge::PlateJudge() {
bool useLBP = false;
if (useLBP) {
LOAD_SVM_MODEL(svm_, kLBPSvmPath);
extractFeature = getLBPFeatures;
}
else {
LOAD_SVM_MODEL(svm_, kHistSvmPath);
extractFeature = getHistomPlusColoFeatures;
}
}
void PlateJudge::LoadModel(std::string path) {
if (path != std::string(kDefaultSvmPath)) {
if (!svm_->empty())
svm_->clear();
LOAD_SVM_MODEL(svm_, path);
}
}
// set the score of plate
// 0 is plate, -1 is not.
int PlateJudge::plateSetScore(CPlate& plate) {
Mat features;
extractFeature(plate.getPlateMat(), features);
float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT);
//std::cout << "score:" << score << std::endl;
if (0) {
imshow("plate", plate.getPlateMat());
waitKey(0);
destroyWindow("plate");
}
// score is the distance of marginbelow zero is plate, up is not
// when score is below zero, the samll the value, the more possibliy to be a plate.
plate.setPlateScore(score);
if (score < 0.5) return 0;
else return -1;
}
int PlateJudge::plateJudge(const Mat& plateMat) {
CPlate plate;
plate.setPlateMat(plateMat);
return plateSetScore(plate);
}
int PlateJudge::plateJudge(const std::vector<Mat> &inVec,
std::vector<Mat> &resultVec) {
int num = inVec.size();
for (int j = 0; j < num; j++) {
Mat inMat = inVec[j];
int response = -1;
response = plateJudge(inMat);
if (response == 0) resultVec.push_back(inMat);
}
return 0;
}
int PlateJudge::plateJudge(const std::vector<CPlate> &inVec,
std::vector<CPlate> &resultVec) {
int num = inVec.size();
for (int j = 0; j < num; j++) {
CPlate inPlate = inVec[j];
Mat inMat = inPlate.getPlateMat();
int response = -1;
response = plateJudge(inMat);
if (response == 0)
resultVec.push_back(inPlate);
else {
int w = inMat.cols;
int h = inMat.rows;
Mat tmpmat = inMat(Rect_<double>(w * 0.05, h * 0.1, w * 0.9, h * 0.8));
Mat tmpDes = inMat.clone();
resize(tmpmat, tmpDes, Size(inMat.size()));
response = plateJudge(tmpDes);
if (response == 0) resultVec.push_back(inPlate);
}
}
return 0;
}
// non-maximum suppression
void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) {
std::sort(inVec.begin(), inVec.end());
std::vector<CPlate>::iterator it = inVec.begin();
for (; it != inVec.end(); ++it) {
CPlate plateSrc = *it;
//std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl;
Rect rectSrc = plateSrc.getPlatePos().boundingRect();
std::vector<CPlate>::iterator itc = it + 1;
for (; itc != inVec.end();) {
CPlate plateComp = *itc;
Rect rectComp = plateComp.getPlatePos().boundingRect();
float iou = computeIOU(rectSrc, rectComp);
if (iou > overlap) {
itc = inVec.erase(itc);
}
else {
++itc;
}
}
}
resultVec = inVec;
}
// judge plate using nms
int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) {
std::vector<CPlate> plateVec;
int num = inVec.size();
bool useCascadeJudge = true;
for (int j = 0; j < num; j++) {
CPlate plate = inVec[j];
Mat inMat = plate.getPlateMat();
int result = plateSetScore(plate);
if (0 == result) {
if (0) {
imshow("inMat", inMat);
waitKey(0);
destroyWindow("inMat");
}
if (plate.getPlateLocateType() == CMSER) {
int w = inMat.cols;
int h = inMat.rows;
Mat tmpmat = inMat(Rect_<double>(w * 0.05, h * 0.1, w * 0.9, h * 0.8));
Mat tmpDes = inMat.clone();
resize(tmpmat, tmpDes, Size(inMat.size()));
plate.setPlateMat(tmpDes);
if (useCascadeJudge) {
int resultCascade = plateSetScore(plate);
if (plate.getPlateLocateType() != CMSER)
plate.setPlateMat(inMat);
if (resultCascade == 0) {
if (0) {
imshow("tmpDes", tmpDes);
waitKey(0);
destroyWindow("tmpDes");
}
plateVec.push_back(plate);
}
}
else
plateVec.push_back(plate);
}
else
plateVec.push_back(plate);
}
}
std::vector<CPlate> reDupPlateVec;
double overlap = 0.5;
// double overlap = CParams::instance()->getParam1f();
// use NMS to get the result plates
NMS(plateVec, reDupPlateVec, overlap);
// sort the plates due to their scores
std::sort(reDupPlateVec.begin(), reDupPlateVec.end());
// output the plate judge plates
std::vector<CPlate>::iterator it = reDupPlateVec.begin();
int count = 0;
for (; it != reDupPlateVec.end(); ++it) {
resultVec.push_back(*it);
if (0) {
imshow("plateMat", it->getPlateMat());
waitKey(0);
destroyWindow("plateMat");
}
count++;
if (count >= maxPlates)
break;
}
return 0;
}
}

@ -0,0 +1,999 @@
#include "easypr/core/plate_locate.h"
#include "easypr/core/core_func.h"
#include "easypr/util/util.h"
#include "easypr/core/params.h"
using namespace std;
namespace easypr {
const float DEFAULT_ERROR = 0.9f; // 0.6
const float DEFAULT_ASPECT = 3.75f; // 3.75
CPlateLocate::CPlateLocate() {
m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE;
m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH;
m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT;
m_error = DEFAULT_ERROR;
m_aspect = DEFAULT_ASPECT;
m_verifyMin = DEFAULT_VERIFY_MIN;
m_verifyMax = DEFAULT_VERIFY_MAX;
m_angle = DEFAULT_ANGLE;
m_debug = DEFAULT_DEBUG;
}
void CPlateLocate::setLifemode(bool param) {
if (param) {
setGaussianBlurSize(5);
setMorphSizeWidth(10);
setMorphSizeHeight(3);
setVerifyError(0.75);
setVerifyAspect(4.0);
setVerifyMin(1);
setVerifyMax(200);
} 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);
}
}
bool CPlateLocate::verifySizes(RotatedRect mr) {
float error = m_error;
// Spain car plate size: 52x11 aspect 4,7272
// China car plate size: 440mm*140mmaspect 3.142857
// Real car plate size: 136 * 32, aspect 4
float aspect = m_aspect;
// Set a min and max area. All other patchs are discarded
// int min= 1*aspect*1; // minimum area
// int max= 2000*aspect*2000; // maximum area
int min = 34 * 8 * m_verifyMin; // minimum area
int max = 34 * 8 * m_verifyMax; // maximum area
// Get only patchs that match to a respect ratio.
float rmin = aspect - aspect * error;
float rmax = aspect + aspect * error;
float area = mr.size.height * mr.size.width;
float r = (float) mr.size.width / (float) mr.size.height;
if (r < 1) r = (float) mr.size.height / (float) mr.size.width;
// cout << "area:" << area << endl;
// cout << "r:" << r << endl;
if ((area < min || area > max) || (r < rmin || r > rmax))
return false;
else
return true;
}
//! mser search method
int CPlateLocate::mserSearch(const Mat &src, vector<Mat> &out,
vector<vector<CPlate>>& out_plateVec, bool usePlateMser, vector<vector<RotatedRect>>& out_plateRRect,
int img_index, bool showDebug) {
vector<Mat> match_grey;
vector<CPlate> plateVec_blue;
plateVec_blue.reserve(16);
vector<RotatedRect> plateRRect_blue;
plateRRect_blue.reserve(16);
vector<CPlate> plateVec_yellow;
plateVec_yellow.reserve(16);
vector<RotatedRect> plateRRect_yellow;
plateRRect_yellow.reserve(16);
mserCharMatch(src, match_grey, plateVec_blue, plateVec_yellow, usePlateMser, plateRRect_blue, plateRRect_yellow, img_index, showDebug);
out_plateVec.push_back(plateVec_blue);
out_plateVec.push_back(plateVec_yellow);
out_plateRRect.push_back(plateRRect_blue);
out_plateRRect.push_back(plateRRect_yellow);
out = match_grey;
return 0;
}
int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
vector<RotatedRect> &outRects) {
Mat match_grey;
// width is important to the final results;
const int color_morph_width = 10;
const int color_morph_height = 2;
colorMatch(src, match_grey, r, false);
SHOW_IMAGE(match_grey, 0);
Mat src_threshold;
threshold(match_grey, src_threshold, 0, 255,
CV_THRESH_OTSU + CV_THRESH_BINARY);
Mat element = getStructuringElement(
MORPH_RECT, Size(color_morph_width, color_morph_height));
morphologyEx(src_threshold, src_threshold, MORPH_CLOSE, element);
//if (m_debug) {
// utils::imwrite("resources/image/tmp/color.jpg", src_threshold);
//}
src_threshold.copyTo(out);
vector<vector<Point>> contours;
findContours(src_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
if (!verifySizes(mr))
itc = contours.erase(itc);
else {
++itc;
outRects.push_back(mr);
}
}
return 0;
}
int CPlateLocate::sobelFrtSearch(const Mat &src,
vector<Rect_<float>> &outRects) {
Mat src_threshold;
sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth,
m_MorphSizeHeight);
vector<vector<Point>> contours;
findContours(src_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> first_rects;
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
if (verifySizes(mr)) {
first_rects.push_back(mr);
float area = mr.size.height * mr.size.width;
float r = (float) mr.size.width / (float) mr.size.height;
if (r < 1) r = (float) mr.size.height / (float) mr.size.width;
}
++itc;
}
for (size_t i = 0; i < first_rects.size(); i++) {
RotatedRect roi_rect = first_rects[i];
Rect_<float> safeBoundRect;
if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue;
outRects.push_back(safeBoundRect);
}
return 0;
}
int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
vector<RotatedRect> &outRects) {
Mat bound_threshold;
sobelOperT(bound, bound_threshold, 3, 6, 2);
Mat tempBoundThread = bound_threshold.clone();
clearLiuDingOnly(tempBoundThread);
int posLeft = 0, posRight = 0;
if (bFindLeftRightBound(tempBoundThread, posLeft, posRight)) {
// find left and right bounds to repair
if (posRight != 0 && posLeft != 0 && posLeft < posRight) {
int posY = int(bound_threshold.rows * 0.5);
for (int i = posLeft + (int) (bound_threshold.rows * 0.1);
i < posRight - 4; i++) {
bound_threshold.data[posY * bound_threshold.cols + i] = 255;
}
}
utils::imwrite("resources/image/tmp/repaireimg1.jpg", bound_threshold);
// remove the left and right boundaries
for (int i = 0; i < bound_threshold.rows; i++) {
bound_threshold.data[i * bound_threshold.cols + posLeft] = 0;
bound_threshold.data[i * bound_threshold.cols + posRight] = 0;
}
utils::imwrite("resources/image/tmp/repaireimg2.jpg", bound_threshold);
}
vector<vector<Point>> contours;
findContours(bound_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> second_rects;
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
second_rects.push_back(mr);
++itc;
}
for (size_t i = 0; i < second_rects.size(); i++) {
RotatedRect roi = second_rects[i];
if (verifySizes(roi)) {
Point2f refcenter = roi.center + refpoint;
Size2f size = roi.size;
float angle = roi.angle;
RotatedRect refroi(refcenter, size, angle);
outRects.push_back(refroi);
}
}
return 0;
}
int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint,
vector<RotatedRect> &outRects) {
Mat bound_threshold;
sobelOper(bound, bound_threshold, 3, 10, 3);
utils::imwrite("resources/image/tmp/sobelSecSearch.jpg", bound_threshold);
vector<vector<Point>> contours;
findContours(bound_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> second_rects;
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
second_rects.push_back(mr);
++itc;
}
for (size_t i = 0; i < second_rects.size(); i++) {
RotatedRect roi = second_rects[i];
if (verifySizes(roi)) {
Point2f refcenter = roi.center + refpoint;
Size2f size = roi.size;
float angle = roi.angle;
RotatedRect refroi(refcenter, size, angle);
outRects.push_back(refroi);
}
}
return 0;
}
int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW,
int morphH) {
Mat mat_blur;
mat_blur = in.clone();
GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT);
Mat mat_gray;
if (mat_blur.channels() == 3)
cvtColor(mat_blur, mat_gray, CV_RGB2GRAY);
else
mat_gray = mat_blur;
int scale = SOBEL_SCALE;
int delta = SOBEL_DELTA;
int ddepth = SOBEL_DDEPTH;
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
Mat grad;
addWeighted(abs_grad_x, SOBEL_X_WEIGHT, 0, 0, 0, grad);
Mat mat_threshold;
double otsu_thresh_val =
threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH));
morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element);
out = mat_threshold;
return 0;
}
void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
Mat input_grey;
cvtColor(inmat, input_grey, CV_BGR2GRAY);
int w = inmat.cols;
int h = inmat.rows;
Mat tmpMat = inmat(Rect_<double>(w * 0.15, h * 0.1, w * 0.7, h * 0.7));
Color plateType;
if (UNKNOWN == color) {
plateType = getPlateType(tmpMat, true);
}
else {
plateType = color;
}
Mat img_threshold;
if (BLUE == plateType) {
img_threshold = input_grey.clone();
Mat tmp = input_grey(Rect_<double>(w * 0.15, h * 0.15, w * 0.7, h * 0.7));
int threadHoldV = ThresholdOtsu(tmp);
threshold(input_grey, img_threshold, threadHoldV, 255, CV_THRESH_BINARY);
// threshold(input_grey, img_threshold, 5, 255, CV_THRESH_OTSU +
// CV_THRESH_BINARY);
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
} else if (YELLOW == plateType) {
img_threshold = input_grey.clone();
Mat tmp = input_grey(Rect_<double>(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
int threadHoldV = ThresholdOtsu(tmp);
threshold(input_grey, img_threshold, threadHoldV, 255,
CV_THRESH_BINARY_INV);
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
// threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU +
// CV_THRESH_BINARY_INV);
} else
threshold(input_grey, img_threshold, 10, 255,
CV_THRESH_OTSU + CV_THRESH_BINARY);
//img_threshold = input_grey.clone();
//spatial_ostu(img_threshold, 8, 2, plateType);
int posLeft = 0;
int posRight = 0;
int top = 0;
int bottom = img_threshold.rows - 1;
clearLiuDing(img_threshold, top, bottom);
if (0) {
imshow("inmat", inmat);
waitKey(0);
destroyWindow("inmat");
}
if (bFindLeftRightBound1(img_threshold, posLeft, posRight)) {
inmat = inmat(Rect(posLeft, top, w - posLeft, bottom - top));
if (0) {
imshow("inmat", inmat);
waitKey(0);
destroyWindow("inmat");
}
}
}
int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
vector<RotatedRect> &inRects,
vector<CPlate> &outPlates, bool useDeteleArea, Color color) {
Mat mat_debug;
src.copyTo(mat_debug);
for (size_t i = 0; i < inRects.size(); i++) {
RotatedRect roi_rect = inRects[i];
float r = (float) roi_rect.size.width / (float) roi_rect.size.height;
float roi_angle = roi_rect.angle;
Size roi_rect_size = roi_rect.size;
if (r < 1) {
roi_angle = 90 + roi_angle;
swap(roi_rect_size.width, roi_rect_size.height);
}
if (m_debug) {
Point2f rect_points[4];
roi_rect.points(rect_points);
for (int j = 0; j < 4; j++)
line(mat_debug, rect_points[j], rect_points[(j + 1) % 4],
Scalar(0, 255, 255), 1, 8);
}
// changed
// rotation = 90 - abs(roi_angle);
// rotation < m_angel;
// m_angle=60
if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) {
Rect_<float> safeBoundRect;
bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect);
if (!isFormRect) continue;
Mat bound_mat = src(safeBoundRect);
Mat bound_mat_b = src_b(safeBoundRect);
if (0) {
imshow("bound_mat_b", bound_mat_b);
waitKey(0);
destroyWindow("bound_mat_b");
}
Point2f roi_ref_center = roi_rect.center - safeBoundRect.tl();
Mat deskew_mat;
if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle ||
-90.0 == roi_angle) {
deskew_mat = bound_mat;
} else {
Mat rotated_mat;
Mat rotated_mat_b;
if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center, roi_angle))
continue;
if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center, roi_angle))
continue;
// we need affine for rotatioed image
double roi_slope = 0;
// imshow("1roated_mat",rotated_mat);
// imshow("rotated_mat_b",rotated_mat_b);
if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) {
affine(rotated_mat, deskew_mat, roi_slope);
} else
deskew_mat = rotated_mat;
}
Mat plate_mat;
plate_mat.create(HEIGHT, WIDTH, TYPE);
// haitungaga addaffect 25% to full recognition.
if (useDeteleArea)
deleteNotArea(deskew_mat, color);
if (deskew_mat.cols * 1.0 / deskew_mat.rows > 2.3 && deskew_mat.cols * 1.0 / deskew_mat.rows < 6) {
if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT)
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA);
else
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC);
CPlate plate;
plate.setPlatePos(roi_rect);
plate.setPlateMat(plate_mat);
if (color != UNKNOWN) plate.setPlateColor(color);
outPlates.push_back(plate);
}
}
}
return 0;
}
bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
const Point2f center, const double angle) {
if (0) {
imshow("in", in);
waitKey(0);
destroyWindow("in");
}
Mat in_large;
in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type());
float x = in_large.cols / 2 - center.x > 0 ? in_large.cols / 2 - center.x : 0;
float y = in_large.rows / 2 - center.y > 0 ? in_large.rows / 2 - center.y : 0;
float width = x + in.cols < in_large.cols ? in.cols : in_large.cols - x;
float height = y + in.rows < in_large.rows ? in.rows : in_large.rows - y;
/*assert(width == in.cols);
assert(height == in.rows);*/
if (width != in.cols || height != in.rows) return false;
Mat imageRoi = in_large(Rect_<float>(x, y, width, height));
addWeighted(imageRoi, 0, in, 1, 0, imageRoi);
Point2f center_diff(in.cols / 2.f, in.rows / 2.f);
Point2f new_center(in_large.cols / 2.f, in_large.rows / 2.f);
Mat rot_mat = getRotationMatrix2D(new_center, angle, 1);
/*imshow("in_copy", in_large);
waitKey(0);*/
Mat mat_rotated;
warpAffine(in_large, mat_rotated, rot_mat, Size(in_large.cols, in_large.rows),
CV_INTER_CUBIC);
/*imshow("mat_rotated", mat_rotated);
waitKey(0);*/
Mat img_crop;
getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height),
new_center, img_crop);
out = img_crop;
if (0) {
imshow("out", out);
waitKey(0);
destroyWindow("out");
}
/*imshow("img_crop", img_crop);
waitKey(0);*/
return true;
}
bool CPlateLocate::isdeflection(const Mat &in, const double angle,
double &slope) { /*imshow("in",in);
waitKey(0);*/
if (0) {
imshow("in", in);
waitKey(0);
destroyWindow("in");
}
int nRows = in.rows;
int nCols = in.cols;
assert(in.channels() == 1);
int comp_index[3];
int len[3];
comp_index[0] = nRows / 4;
comp_index[1] = nRows / 4 * 2;
comp_index[2] = nRows / 4 * 3;
const uchar* p;
for (int i = 0; i < 3; i++) {
int index = comp_index[i];
p = in.ptr<uchar>(index);
int j = 0;
int value = 0;
while (0 == value && j < nCols) value = int(p[j++]);
len[i] = j;
}
// cout << "len[0]:" << len[0] << endl;
// cout << "len[1]:" << len[1] << endl;
// cout << "len[2]:" << len[2] << endl;
// len[0]/len[1]/len[2] are used to calc the slope
double maxlen = max(len[2], len[0]);
double minlen = min(len[2], len[0]);
double difflen = abs(len[2] - len[0]);
double PI = 3.14159265;
double g = tan(angle * PI / 180.0);
if (maxlen - len[1] > nCols / 32 || len[1] - minlen > nCols / 32) {
double slope_can_1 =
double(len[2] - len[0]) / double(comp_index[1]);
double slope_can_2 = double(len[1] - len[0]) / double(comp_index[0]);
double slope_can_3 = double(len[2] - len[1]) / double(comp_index[0]);
// cout<<"angle:"<<angle<<endl;
// cout<<"g:"<<g<<endl;
// cout << "slope_can_1:" << slope_can_1 << endl;
// cout << "slope_can_2:" << slope_can_2 << endl;
// cout << "slope_can_3:" << slope_can_3 << endl;
// if(g>=0)
slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1
: slope_can_2;
// cout << "slope:" << slope << endl;
return true;
} else {
slope = 0;
}
return false;
}
void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
// imshow("in", in);
// waitKey(0);
Point2f dstTri[3];
Point2f plTri[3];
float height = (float) in.rows;
float width = (float) in.cols;
float xiff = (float) abs(slope) * height;
if (slope > 0) {
// right, new position is xiff/2
plTri[0] = Point2f(0, 0);
plTri[1] = Point2f(width - xiff - 1, 0);
plTri[2] = Point2f(0 + xiff, height - 1);
dstTri[0] = Point2f(xiff / 2, 0);
dstTri[1] = Point2f(width - 1 - xiff / 2, 0);
dstTri[2] = Point2f(xiff / 2, height - 1);
} else {
// left, new position is -xiff/2
plTri[0] = Point2f(0 + xiff, 0);
plTri[1] = Point2f(width - 1, 0);
plTri[2] = Point2f(0, height - 1);
dstTri[0] = Point2f(xiff / 2, 0);
dstTri[1] = Point2f(width - 1 - xiff + xiff / 2, 0);
dstTri[2] = Point2f(xiff / 2, height - 1);
}
Mat warp_mat = getAffineTransform(plTri, dstTri);
Mat affine_mat;
affine_mat.create((int) height, (int) width, TYPE);
if (in.rows > HEIGHT || in.cols > WIDTH)
warpAffine(in, affine_mat, warp_mat, affine_mat.size(),
CV_INTER_AREA);
else
warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC);
out = affine_mat;
}
int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_color_blue;
rects_color_blue.reserve(64);
vector<RotatedRect> rects_color_yellow;
rects_color_yellow.reserve(64);
vector<CPlate> plates_blue;
plates_blue.reserve(64);
vector<CPlate> plates_yellow;
plates_yellow.reserve(64);
Mat src_clone = src.clone();
Mat src_b_blue;
Mat src_b_yellow;
#pragma omp parallel sections
{
#pragma omp section
{
colorSearch(src, BLUE, src_b_blue, rects_color_blue);
deskew(src, src_b_blue, rects_color_blue, plates_blue, true, BLUE);
}
#pragma omp section
{
colorSearch(src_clone, YELLOW, src_b_yellow, rects_color_yellow);
deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW);
}
}
candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end());
candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end());
return 0;
}
//! MSER plate locate
int CPlateLocate::plateMserLocate(Mat src, vector<CPlate> &candPlates, int img_index) {
std::vector<Mat> channelImages;
std::vector<Color> flags;
flags.push_back(BLUE);
flags.push_back(YELLOW);
bool usePlateMser = false;
int scale_size = 1000;
//int scale_size = CParams::instance()->getParam1i();
double scale_ratio = 1;
// only conside blue plate
if (1) {
Mat grayImage;
cvtColor(src, grayImage, COLOR_BGR2GRAY);
channelImages.push_back(grayImage);
}
for (size_t i = 0; i < channelImages.size(); ++i) {
vector<vector<RotatedRect>> plateRRectsVec;
vector<vector<CPlate>> platesVec;
vector<Mat> src_b_vec;
Mat channelImage = channelImages.at(i);
Mat image = scaleImage(channelImage, Size(scale_size, scale_size), scale_ratio);
// vector<RotatedRect> rects;
mserSearch(image, src_b_vec, platesVec, usePlateMser, plateRRectsVec, img_index, false);
for (size_t j = 0; j < flags.size(); j++) {
vector<CPlate>& plates = platesVec.at(j);
Mat& src_b = src_b_vec.at(j);
Color color = flags.at(j);
vector<RotatedRect> rects_mser;
rects_mser.reserve(64);
std::vector<CPlate> deskewPlate;
deskewPlate.reserve(64);
std::vector<CPlate> mserPlate;
mserPlate.reserve(64);
// deskew for rotation and slope image
for (auto plate : plates) {
RotatedRect rrect = plate.getPlatePos();
RotatedRect scaleRect = scaleBackRRect(rrect, (float)scale_ratio);
plate.setPlatePos(scaleRect);
plate.setPlateColor(color);
rects_mser.push_back(scaleRect);
mserPlate.push_back(plate);
}
Mat resize_src_b;
resize(src_b, resize_src_b, Size(channelImage.cols, channelImage.rows));
deskew(src, resize_src_b, rects_mser, deskewPlate, false, color);
for (auto dplate : deskewPlate) {
RotatedRect drect = dplate.getPlatePos();
Mat dmat = dplate.getPlateMat();
for (auto splate : mserPlate) {
RotatedRect srect = splate.getPlatePos();
float iou = 0.f;
bool isSimilar = computeIOU(drect, srect, src.cols, src.rows, 0.95f, iou);
if (isSimilar) {
splate.setPlateMat(dmat);
candPlates.push_back(splate);
break;
}
}
}
}
}
if (0) {
imshow("src", src);
waitKey(0);
destroyWindow("src");
}
return 0;
}
int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
int morphH) {
Mat mat_blur;
mat_blur = in.clone();
GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT);
Mat mat_gray;
if (mat_blur.channels() == 3)
cvtColor(mat_blur, mat_gray, CV_BGR2GRAY);
else
mat_gray = mat_blur;
utils::imwrite("resources/image/tmp/grayblure.jpg", mat_gray);
// equalizeHist(mat_gray, mat_gray);
int scale = SOBEL_SCALE;
int delta = SOBEL_DELTA;
int ddepth = SOBEL_DDEPTH;
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
Mat grad;
addWeighted(abs_grad_x, 1, 0, 0, 0, grad);
utils::imwrite("resources/image/tmp/graygrad.jpg", grad);
Mat mat_threshold;
double otsu_thresh_val =
threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
utils::imwrite("resources/image/tmp/grayBINARY.jpg", mat_threshold);
Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH));
morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element);
utils::imwrite("resources/image/tmp/phologyEx.jpg", mat_threshold);
out = mat_threshold;
return 0;
}
int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_sobel_all;
rects_sobel_all.reserve(256);
vector<CPlate> plates;
plates.reserve(32);
vector<Rect_<float>> bound_rects;
bound_rects.reserve(256);
sobelFrtSearch(src, bound_rects);
vector<Rect_<float>> bound_rects_part;
bound_rects_part.reserve(256);
// enlarge area
for (size_t i = 0; i < bound_rects.size(); i++) {
float fRatio = bound_rects[i].width * 1.0f / bound_rects[i].height;
if (fRatio < 3.0 && fRatio > 1.0 && bound_rects[i].height < 120) {
Rect_<float> itemRect = bound_rects[i];
itemRect.x = itemRect.x - itemRect.height * (4 - fRatio);
if (itemRect.x < 0) {
itemRect.x = 0;
}
itemRect.width = itemRect.width + itemRect.height * 2 * (4 - fRatio);
if (itemRect.width + itemRect.x >= src.cols) {
itemRect.width = src.cols - itemRect.x;
}
itemRect.y = itemRect.y - itemRect.height * 0.08f;
itemRect.height = itemRect.height * 1.16f;
bound_rects_part.push_back(itemRect);
}
}
// second processing to split one
#pragma omp parallel for
for (int i = 0; i < (int)bound_rects_part.size(); i++) {
Rect_<float> bound_rect = bound_rects_part[i];
Point2f refpoint(bound_rect.x, bound_rect.y);
float x = bound_rect.x > 0 ? bound_rect.x : 0;
float y = bound_rect.y > 0 ? bound_rect.y : 0;
float width =
x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x;
float height =
y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y;
Rect_<float> safe_bound_rect(x, y, width, height);
Mat bound_mat = src(safe_bound_rect);
vector<RotatedRect> rects_sobel;
rects_sobel.reserve(128);
sobelSecSearchPart(bound_mat, refpoint, rects_sobel);
#pragma omp critical
{
rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end());
}
}
#pragma omp parallel for
for (int i = 0; i < (int)bound_rects.size(); i++) {
Rect_<float> bound_rect = bound_rects[i];
Point2f refpoint(bound_rect.x, bound_rect.y);
float x = bound_rect.x > 0 ? bound_rect.x : 0;
float y = bound_rect.y > 0 ? bound_rect.y : 0;
float width =
x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x;
float height =
y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y;
Rect_<float> safe_bound_rect(x, y, width, height);
Mat bound_mat = src(safe_bound_rect);
vector<RotatedRect> rects_sobel;
rects_sobel.reserve(128);
sobelSecSearch(bound_mat, refpoint, rects_sobel);
#pragma omp critical
{
rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end());
}
}
Mat src_b;
sobelOper(src, src_b, 3, 10, 3);
deskew(src, src_b, rects_sobel_all, plates);
//for (size_t i = 0; i < plates.size(); i++)
// candPlates.push_back(plates[i]);
candPlates.insert(candPlates.end(), plates.begin(), plates.end());
return 0;
}
int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
vector<CPlate> all_result_Plates;
plateColorLocate(src, all_result_Plates, index);
plateSobelLocate(src, all_result_Plates, index);
plateMserLocate(src, all_result_Plates, index);
for (size_t i = 0; i < all_result_Plates.size(); i++) {
CPlate plate = all_result_Plates[i];
resultVec.push_back(plate.getPlateMat());
}
return 0;
}
int CPlateLocate::plateLocate(Mat src, vector<CPlate> &resultVec, int index) {
vector<CPlate> all_result_Plates;
plateColorLocate(src, all_result_Plates, index);
plateSobelLocate(src, all_result_Plates, index);
plateMserLocate(src, all_result_Plates, index);
for (size_t i = 0; i < all_result_Plates.size(); i++) {
resultVec.push_back(all_result_Plates[i]);
}
return 0;
}
}

@ -0,0 +1,105 @@
#include "easypr/core/plate_recognize.h"
#include "easypr/config.h"
#include "thirdparty/textDetect/erfilter.hpp"
namespace easypr {
CPlateRecognize::CPlateRecognize() {
m_showResult = false;
}
// main method, plate recognize, contain two parts
// 1. plate detect
// 2. chars recognize
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVecOut, int img_index) {
// resize to uniform sizes
float scale = 1.f;
Mat img = uniformResize(src, scale);
// 1. plate detect
std::vector<CPlate> plateVec;
int resultPD = plateDetect(img, plateVec, img_index);
if (resultPD == 0) {
size_t num = plateVec.size();
for (size_t j = 0; j < num; j++) {
CPlate& item = plateVec.at(j);
Mat plateMat = item.getPlateMat();
SHOW_IMAGE(plateMat, 0);
// scale the rect to src;
item.setPlateScale(scale);
RotatedRect rect = item.getPlatePos();
item.setPlatePos(scaleBackRRect(rect, 1.f / scale));
// get plate color
Color color = item.getPlateColor();
if (color == UNKNOWN) {
color = getPlateType(plateMat, true);
item.setPlateColor(color);
}
std::string plateColor = getPlateColor(color);
if (0) {
std::cout << "plateColor:" << plateColor << std::endl;
}
// 2. chars recognize
std::string plateIdentify = "";
int resultCR = charsRecognise(item, plateIdentify);
if (resultCR == 0) {
std::string license = plateColor + ":" + plateIdentify;
item.setPlateStr(license);
plateVecOut.push_back(item);
if (0) std::cout << "resultCR:" << resultCR << std::endl;
}
else {
std::string license = plateColor;
item.setPlateStr(license);
plateVecOut.push_back(item);
if (0) std::cout << "resultCR:" << resultCR << std::endl;
}
}
if (getResultShow()) {
// param type: 0 detect, 1 recognize;
int showType = 1;
if (0 == showType)
showDectectResults(img, plateVec, num);
else
showDectectResults(img, plateVecOut, num);
}
}
return resultPD;
}
void CPlateRecognize::LoadSVM(std::string path) {
PlateJudge::instance()->LoadModel(path);
}
void CPlateRecognize::LoadANN(std::string path) {
CharsIdentify::instance()->LoadModel(path);
}
void CPlateRecognize::LoadChineseANN(std::string path) {
CharsIdentify::instance()->LoadChineseModel(path);
}
void CPlateRecognize::LoadGrayChANN(std::string path) {
CharsIdentify::instance()->LoadGrayChANN(path);
}
void CPlateRecognize::LoadChineseMapping(std::string path) {
CharsIdentify::instance()->LoadChineseMapping(path);
}
// deprected
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &licenseVec) {
vector<CPlate> plates;
int resultPR = plateRecognize(src, plates, 0);
for (auto plate : plates) {
licenseVec.push_back(plate.getPlateStr());
}
return resultPR;
}
}

@ -0,0 +1,31 @@
zh_cuan 川
zh_gan1 甘
zh_hei 黑
zh_jin 津
zh_liao 辽
zh_min 闽
zh_qiong 琼
zh_sx 晋
zh_xin 新
zh_yue 粤
zh_zhe 浙
zh_e 鄂
zh_gui 贵
zh_hu 沪
zh_jing 京
zh_lu 鲁
zh_ning 宁
zh_shan 陕
zh_wan 皖
zh_yu 豫
zh_yun 云
zh_gan 赣
zh_gui1 桂
zh_ji 冀
zh_jl 吉
zh_meng 蒙
zh_qing 青
zh_su 苏
zh_xiang 湘
zh_yu1 渝
zh_zang 藏

@ -0,0 +1,196 @@
#include "easypr/train/svm_train.h"
#include "easypr/util/util.h"
#include "easypr/config.h"
#ifdef OS_WINDOWS
#include <ctime>
#endif
using namespace cv;
using namespace cv::ml;
// 原版C++语言 训练代码
namespace easypr {
SvmTrain::SvmTrain(const char* plates_folder, const char* xml): plates_folder_(plates_folder), svm_xml_(xml) {
assert(plates_folder);
assert(xml);
extractFeature = getHistomPlusColoFeatures;
}
void SvmTrain::train() {
svm_ = cv::ml::SVM::create();
svm_->setType(cv::ml::SVM::C_SVC);
svm_->setKernel(cv::ml::SVM::RBF);
svm_->setDegree(0.1);
// 1.4 bug fix: old 1.4 ver gamma is 1
svm_->setGamma(0.1);
svm_->setCoef0(0.1);
svm_->setC(1);
svm_->setNu(0.1);
svm_->setP(0.1);
svm_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001));
this->prepare();
if (train_file_list_.size() == 0) {
fprintf(stdout, "No file found in the train folder!\n");
fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n");
fprintf(stdout, "Copy train data folder(like \"SVM\") under \"tmp\". \n");
return;
}
auto train_data = tdata();
fprintf(stdout, ">> Training SVM model, please wait...\n");
long start = utils::getTimestamp();
svm_->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C),
SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P),
SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
SVM::getDefaultGrid(SVM::DEGREE), true);
//svm_->train(train_data);
long end = utils::getTimestamp();
fprintf(stdout, ">> Training done. Time elapse: %ldms\n", end - start);
fprintf(stdout, ">> Saving model file...\n");
svm_->save(svm_xml_);
fprintf(stdout, ">> Your SVM Model was saved to %s\n", svm_xml_);
fprintf(stdout, ">> Testing...\n");
this->test();
}
void SvmTrain::test() {
// 1.4 bug fix: old 1.4 ver there is no null judge
// if (NULL == svm_)
LOAD_SVM_MODEL(svm_, svm_xml_);
if (test_file_list_.empty()) {
this->prepare();
}
double count_all = test_file_list_.size();
double ptrue_rtrue = 0;
double ptrue_rfalse = 0;
double pfalse_rtrue = 0;
double pfalse_rfalse = 0;
for (auto item : test_file_list_) {
auto image = cv::imread(item.file);
if (!image.data) {
std::cout << "no" << std::endl;
continue;
}
cv::Mat feature;
extractFeature(image, feature);
auto predict = int(svm_->predict(feature));
//std::cout << "predict: " << predict << std::endl;
auto real = item.label;
if (predict == kForward && real == kForward) ptrue_rtrue++;
if (predict == kForward && real == kInverse) ptrue_rfalse++;
if (predict == kInverse && real == kForward) pfalse_rtrue++;
if (predict == kInverse && real == kInverse) pfalse_rfalse++;
}
std::cout << "count_all: " << count_all << std::endl;
std::cout << "ptrue_rtrue: " << ptrue_rtrue << std::endl;
std::cout << "ptrue_rfalse: " << ptrue_rfalse << std::endl;
std::cout << "pfalse_rtrue: " << pfalse_rtrue << std::endl;
std::cout << "pfalse_rfalse: " << pfalse_rfalse << std::endl;
double precise = 0;
if (ptrue_rtrue + ptrue_rfalse != 0) {
precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse);
std::cout << "precise: " << precise << std::endl;
} else {
std::cout << "precise: "
<< "NA" << std::endl;
}
double recall = 0;
if (ptrue_rtrue + pfalse_rtrue != 0) {
recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue);
std::cout << "recall: " << recall << std::endl;
} else {
std::cout << "recall: "
<< "NA" << std::endl;
}
double Fsocre = 0;
if (precise + recall != 0) {
Fsocre = 2 * (precise * recall) / (precise + recall);
std::cout << "Fsocre: " << Fsocre << std::endl;
} else {
std::cout << "Fsocre: "
<< "NA" << std::endl;
}
}
void SvmTrain::prepare() {
srand(unsigned(time(NULL)));
char buffer[260] = {0};
sprintf(buffer, "%s/has/train", plates_folder_);
auto has_file_train_list = utils::getFiles(buffer);
std::random_shuffle(has_file_train_list.begin(), has_file_train_list.end());
sprintf(buffer, "%s/has/test", plates_folder_);
auto has_file_test_list = utils::getFiles(buffer);
std::random_shuffle(has_file_test_list.begin(), has_file_test_list.end());
sprintf(buffer, "%s/no/train", plates_folder_);
auto no_file_train_list = utils::getFiles(buffer);
std::random_shuffle(no_file_train_list.begin(), no_file_train_list.end());
sprintf(buffer, "%s/no/test", plates_folder_);
auto no_file_test_list = utils::getFiles(buffer);
std::random_shuffle(no_file_test_list.begin(), no_file_test_list.end());
fprintf(stdout, ">> Collecting train data...\n");
for (auto file : has_file_train_list)
train_file_list_.push_back({ file, kForward });
for (auto file : no_file_train_list)
train_file_list_.push_back({ file, kInverse });
fprintf(stdout, ">> Collecting test data...\n");
for (auto file : has_file_test_list)
test_file_list_.push_back({ file, kForward });
for (auto file : no_file_test_list)
test_file_list_.push_back({ file, kInverse });
}
cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() {
cv::Mat samples;
std::vector<int> responses;
for (auto f : train_file_list_) {
auto image = cv::imread(f.file);
if (!image.data) {
fprintf(stdout, ">> Invalid image: %s ignore.\n", f.file.c_str());
continue;
}
cv::Mat feature;
extractFeature(image, feature);
feature = feature.reshape(1, 1);
samples.push_back(feature);
responses.push_back(int(f.label));
}
cv::Mat samples_, responses_;
samples.convertTo(samples_, CV_32FC1);
cv::Mat(responses).copyTo(responses_);
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, responses_);
}
} // namespace easypr

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

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

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save