Commit 003b7634 by 陈皓

init

parent cae76626
......@@ -56,4 +56,19 @@ public class AccessController {
.body(new FileSystemResource(file));
}
@GetMapping(value = "/data/**")
public ResponseEntity<Resource> downloadData(HttpServletRequest request) {
File file = new File(request.getRequestURI());
AssertUtils.isTrue(file.exists(), "访问文件不存在");
String contentDisposition = ContentDisposition
.builder("attachment")
.filename(request.getRequestURI())
.build().toString();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new FileSystemResource(file));
}
}
......@@ -33,6 +33,10 @@ spring:
uri: lb://FILE-SERVER
predicates:
- Path=/file/**
- id: data
uri: lb://FILE-SERVER
predicates:
- Path=/data/**
- id: user
uri: lb://USER-SERVER
predicates:
......
......@@ -81,6 +81,86 @@
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- OpenCv -->
<!-- mvn install:install-file -Dfile=./src/main/resources/lib/opencv-460.jar -DgroupId=org.opencv -DartifactId=opencv -Dversion=4.6.0 -Dpackaging=jar -->
<dependency>
<groupId>org.opencv</groupId>
<artifactId>opencv</artifactId>
<version>4.6.0</version>
</dependency>
<!-- Webp图片格式支持 -->
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>
<!-- JPEG图片格式支持 -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.9.4</version>
</dependency>
<!-- TIFF图片格式支持 -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.9.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-imaging</artifactId>
<version>1.0-alpha3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.22</version>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.19</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
<build>
......
package com.zq.imgproc.config;
import com.zq.common.constant.FeignHeader;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* @author wilmiam
* @since 2021-07-09 10:34
*/
@Configuration
public class FeignConfig {
/**
* 转发请求头
*/
private static final List<String> FORWARD_HEADERS = Arrays.asList(
"AUTHORIZATION",
"X-FORWARDED-FOR",
"X-FORWARDED-PROTO",
"X-FORWARDED-PORT",
"X-FORWARDED-HOST",
"FORWARDED",
"PROXY-CLIENT-IP",
"WL-PROXY-CLIENT-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-REAL-IP",
"HOST"
);
/**
* 解决fein远程调用丢失请求头
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return;
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
template.header(FeignHeader.API_TOKEN, request.getParameter("token"));
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
// 不要设置content-length
if ("content-length".equals(name)) {
continue;
}
if (FORWARD_HEADERS.contains(name.toUpperCase())) {
String values = request.getHeader(name);
template.header(name, values);
}
}
}
}
};
}
}
package com.zq.imgproc.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author chenhao
* @since 2023/8/11 10:30
*/
@Slf4j
@Configuration
public class InitConfig {
@Value("${imgconfig.opencv}")
String opencvUrl;
@PostConstruct
public void asposeRegister() {
System.load(opencvUrl);
log.info(">> opencv 加载成功...");
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.imgproc.config;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.imgproc.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.imgproc.config;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.utils.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
* @author Zheng Jie
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
Set<String> allType = anonymousUrls.get(RequestMethodEnum.ALL.getType());
//不使用注解的时候在这添加url放行
allType.add("/archive/api/**");
httpSecurity
// 禁用 CSRF
.csrf().disable()
// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(allType.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, redisUtils, properties);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}
package com.zq.imgproc.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
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;
/**
* <p>
* Swagger配置类
* </p>
*
* @ClassName SwaggerConfig
* @Author chenhao
* @Date 2022/10/14 18:04
*/
@Configuration
@EnableWebMvc
public class SwaggerConfig implements WebMvcConfigurer {
@Value("${spring.cloud.config.profile:product}")
private String profile;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
registry.addResourceHandler("swagger-ui.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
WebMvcConfigurer.super.addResourceHandlers(registry);
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("项目")
// 生产环境关闭
.enable(!"product".equals(profile))
.select()
.apis(RequestHandlerSelectors.basePackage("com.zq.archive.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("后台接口文档")
.description("查看接口文档")
.build();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.imgproc.config;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final RedisUtils redisUtils;
private final SecurityProperties properties;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, redisUtils);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.imgproc.config;
import cn.hutool.core.util.StrUtil;
import com.zq.common.config.redis.BaseCacheKeys;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.context.ContextUtils;
import com.zq.common.vo.OnlineUserDto;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
/**
* @author /
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final RedisUtils redisUtils;
private SecurityProperties properties;
/**
* @param tokenProvider Token
* @param adminFeignClient admin
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, RedisUtils redisUtils) {
this.tokenProvider = tokenProvider;
this.properties = properties;
this.redisUtils = redisUtils;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = tokenProvider.getToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = redisUtils.getObj(properties.getOnlineKey() + token, OnlineUserDto.class);
} catch (Exception e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
String username = String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY));
redisUtils.hdel(BaseCacheKeys.USER_DATA_MAP_KEY, username);
}
}
if (onlineUserDto != null && StringUtils.isNotBlank(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 设置当前用户
ContextUtils.setAdminContext(onlineUserDto);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
package com.zq.imgproc.config;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
public static final String AUTHORITIES_KEY = "auth";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.compact();
}
public Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.isBlank(bearerToken)) {
return null;
}
if (bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}
package com.zq.imgproc.constant;
import lombok.Getter;
/**
* <p>
* 常用的阈值
* </P>
*
* @author chenhao
* @since 2023/11/26
*/
@Getter
public enum Threshold {
/**
* 亮度过低阈值
*/
START_VALUE("img-start", 75),
/**
* 亮度过低阈值
*/
END_VALUE("img-end", 175),
/**
* 清晰度阈值
*/
CLARITY("img-clarity", 2.5),
/**
* 弯曲度阈值
*/
BEND("img-clarity", 2.5);
private final String key;
private final double value;
Threshold(String key, double value) {
this.key = key;
this.value = value;
}
}
package com.zq.imgproc.controller;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.common.annotation.rest.AnonymousGetMapping;
import com.zq.common.utils.AssertUtils;
import com.zq.common.utils.UuidUtils;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.server.ImgProcService;
import com.zq.imgproc.utils.*;
import com.zq.imgproc.vo.DetectionVO;
import com.zq.imgproc.vo.OptimizationReq;
import com.zq.imgproc.vo.OptimizationVO;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
/**
* <p>
* 图片处理API
* </p>
*
* @author chenhao
* @since 2023/3/18 9:32
*/
@io.swagger.annotations.Api(tags = "图片处理API")
@RequestMapping("/imgproc/v1")
@RestController
public class ApiController {
@Value("${imgconfig.opencv}")
String opencvUrl;
@Value("${imgconfig.deskew}")
String deskewUrl;
private final ImgProcService service;
@Autowired
public ApiController(ImgProcService service) {
this.service = service;
}
@ApiOperation("测试")
@AnonymousGetMapping("/ping")
public ResultVo<?> ping() {
return ResultVo.success("测试成功");
}
@ApiOperation("图片检测")
@PostMapping ("/detection")
public ResultVo<DetectionVO> detection(@RequestBody OptimizationReq req) throws Exception {
AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFilename(), "缺少文件名");
return ResultVo.success(service.detection2(req));
}
@ApiOperation("图片校正(deskew工具)")
@PostMapping("/imageCorrection")
public ResultVo<?> imageCorrection(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 图片矫正
ImageCorrectionUtil.deskew(imgPath, savePath, deskewUrl, false);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("去黑边")
@PostMapping("/removeBlack")
public ResultVo<?> removeBlack(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 去黑边
RemoveBlackUtil2.remove(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(savePath));
}
@ApiOperation("图片灰度化")
@PostMapping("/gray")
public ResultVo<?> gray(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 灰度化
ImageUtil.gray(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("图片边缘检测")
@PostMapping("/canny")
public ResultVo<?> canny(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 边缘检测
ImageUtil.canny(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("图片弯曲矫正")
@PostMapping("/correct")
public ResultVo<?> correct(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
String imgPath = UploadUtils.saveTempFile(file, "api");
// 图片弯曲矫正
HashMap<String, Object> map = new HashMap<>();
map.put("file", FileUtil.file(imgPath));
String response = HttpUtil.post("http://ddns.gxmailu.com:18888/api/correct", map);
JSONObject res = JSONUtil.parseObj(response);
if (res.get("success", Boolean.class)) {
String base64 = res.get("data", String.class);
return ResultVo.success(base64);
} else {
return ResultVo.fail(res.get("msg", String.class));
}
}
@ApiOperation("图片优化")
@PostMapping("/imageOptimization")
public ResultVo<OptimizationVO> imageOptimization(@RequestBody OptimizationReq req) throws IOException {
AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFilename(), "缺少文件名");
return ResultVo.success(service.optimization(req));
}
}
package com.zq.imgproc.controller;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.PageVo;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.entity.ImageDetection;
import com.zq.imgproc.server.ImageDetectionService;
import com.zq.imgproc.vo.DetectionPageReq;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* <p>
* 前端控制器
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@RequestMapping("/imgproc/detection")
@RestController
public class ImageDetectionController {
@Autowired
ImageDetectionService service;
@ApiOperation("分页查询")
@PostMapping("/selectPage")
public ResultVo<PageVo<ImageDetection>> selectPage(@RequestBody DetectionPageReq req) {
return ResultVo.success(service.selectPage(req));
}
@ApiOperation("根据ID查询记录")
@GetMapping("/selectOne/{id}")
public ResultVo<ImageDetection> selectOne(@PathVariable Long id) {
AssertUtils.notNull(id, "数据ID不能为空!");
return ResultVo.success(service.selectOne(id));
}
@ApiOperation("文件解压")
@PostMapping("/upload")
public ResultVo<?> upload(@RequestParam Long userId,
@RequestParam String nickName,
@RequestPart MultipartFile[] file) throws Exception {
AssertUtils.notNull(userId, "用户ID不能为空!");
return service.detection(userId, nickName, file);
}
}
package com.zq.imgproc.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.server.ImgProcService;
import com.zq.imgproc.utils.DecompressUtil;
import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
/**
* <p>
* 图片检测接口
* </p>
*
* @author chenhao
* @since 2022/10/24 14:58
*/
@Api(tags = "图片检测接口")
@RequestMapping("/imgproc/server")
@RestController
public class ImgProcController {
/**
* 普通的缓存 Cache
*/
private final Cache<String, Double> cache = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30))
.expireAfterWrite(Duration.ofHours(48))
.maximumSize(1024)
.concurrencyLevel(4)
.initialCapacity(1024)
.build();
private final ImgProcService service;
@Autowired
public ImgProcController(ImgProcService service) {
this.service = service;
}
@ApiOperation("图片检测")
@PostMapping ("/detection")
public ResultVo<DetectionResVo> detection(@RequestPart MultipartFile file) throws Exception {
AssertUtils.notNull(file, "图片不能为空");
return ResultVo.success(service.detection(file));
}
@ApiOperation("文件压缩(zip)")
@PostMapping("/compressZip")
public void compressZip(@RequestBody CompressReq req, HttpServletResponse response) {
String path = DecompressUtil.compress(req.getPathList());
InputStream inputStream = FileUtil.getInputStream(path);
String name = new String(req.getName().getBytes(), StandardCharsets.ISO_8859_1);
ServletUtil.write(response, inputStream,"application/octet-stream;charset=utf-8",name);
}
@ApiOperation("图片检测")
@PostMapping("/detectionByPath")
public ResultVo<?> detection(@RequestBody List<ImgVO> pathList) throws Exception {
if (pathList.isEmpty()) {
return ResultVo.success();
}
return ResultVo.success(service.detection(pathList));
}
@ApiOperation("导出检测结果")
@PostMapping("/getDetection")
public ResponseEntity<Resource> getDetection(@RequestBody List<ImgVO> pathList) throws Exception {
return service.getDetection(pathList);
}
@ApiOperation("图片校正(deskew工具)")
@PostMapping("/imageCorrection")
public ResultVo<?> imageCorrection(@RequestBody List<ImgVO> pathList) {
if (pathList.isEmpty()) {
return ResultVo.success();
}
return ResultVo.success(service.deskew(pathList));
}
@ApiOperation("去黑边")
@PostMapping("/removeBlack")
public ResultVo<?> removeBlack(@RequestBody List<ImgVO> pathList) {
if (pathList.isEmpty()) {
return ResultVo.success();
}
return ResultVo.success(service.removeBlack(pathList));
}
@ApiOperation("图片旋转")
@PostMapping("/rotate")
public ResultVo<?> rotate(@RequestBody RotateReq req) {
if (req == null) {
return ResultVo.success();
}
return ResultVo.success(service.rotate(req));
}
@ApiOperation("图片灰度化")
@PostMapping("/gray")
public ResultVo<?> rotate(@RequestBody List<ImgVO> pathList) {
if (pathList.isEmpty()) {
return ResultVo.success();
}
return ResultVo.success(service.gray(pathList));
}
@ApiOperation("图片边缘检测")
@PostMapping("/canny")
public ResultVo<?> canny(@RequestBody List<ImgVO> pathList) {
if (pathList.isEmpty()) {
return ResultVo.success();
}
return ResultVo.success(service.canny(pathList));
}
@ApiOperation("图片上传")
@PostMapping("/upload")
public ResultVo<?> upload(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
return ResultVo.success(ImageUtil.saveTempFile(file));
}
@ApiOperation("识别红色")
@PostMapping("/recognizeRed")
public ResultVo<Boolean> recognizeRed(@RequestBody OptimizationReq req) {
ResultVo<Boolean> resultVo;
try {
resultVo = ResultVo.success(service.recognizeRed(req));
} catch (Exception e) {
resultVo = ResultVo.success(false);
}
return resultVo;
}
@ApiOperation("去除红色")
@PostMapping("/removeRed")
public ResultVo<?> removeRed(@RequestBody OptimizationReq req) {
ResultVo<?> resultVo;
try {
resultVo = ResultVo.success(service.removeRed(req));
} catch (Exception e) {
resultVo = ResultVo.fail("处理失败");
}
return resultVo;
}
}
package com.zq.imgproc.controller;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.dao.ImgSettingDao;
import com.zq.imgproc.entity.ImgSetting;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@io.swagger.annotations.Api(tags = "图片指标")
@RequestMapping("/imgproc/setting")
@RestController
public class ImgSettingController {
@Autowired
ImgSettingDao imgSettingDao;
@ApiOperation("根据用户ID获取指标")
@GetMapping("/getSetting/{userId}")
public ResultVo<?> getSetting(@PathVariable long userId) {
return ResultVo.success(
imgSettingDao.selectOne(Wrappers.lambdaQuery(ImgSetting.builder().userId(userId).build()))
);
}
@ApiOperation("设置或更新图片指标")
@PostMapping("/setSetting")
public ResultVo<?> setSetting(@RequestBody ImgSetting imgSetting) {
if (imgSetting.getUserId() == null || imgSetting.getUserId() < 0) {
return ResultVo.fail("用户ID不能为空!");
}
if (imgSetting.getBrightnessStart() != null) {
if (imgSetting.getBrightnessStart() < 0 || imgSetting.getBrightnessStart() > 255) {
return ResultVo.fail("亮度值的范围在0到255之间!");
}
}
if (imgSetting.getBrightnessEnd() != null) {
if (imgSetting.getBrightnessEnd() < 0 || imgSetting.getBrightnessEnd() > 255) {
return ResultVo.fail("亮度值的范围在0到255之间!");
}
}
if (imgSetting.getBend() != null) {
if (imgSetting.getBend() < 0 || imgSetting.getBend() > 1) {
return ResultVo.fail("弯曲置信度的范围在0到1之间!");
}
}
if (imgSetting.getId() == null) {
imgSetting.setCreateTime(DateUtil.date());
imgSetting.setUpdateTime(DateUtil.date());
imgSettingDao.insert(imgSetting);
} else {
imgSettingDao.updateById(imgSetting);
}
return ResultVo.success(imgSetting);
}
}
package com.zq.imgproc.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zq.imgproc.entity.ImageDetection;
import com.zq.imgproc.vo.DetectionPageReq;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@Repository
public interface ImageDetectionDao extends BaseMapper<ImageDetection> {
Page<ImageDetection> queryPageList(Page<ImageDetection> page, @Param("req") DetectionPageReq req);
/**
* 批量插入
*/
Integer insertBatch(List<ImageDetection> list);
}
package com.zq.imgproc.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.imgproc.entity.ImgSetting;
import org.springframework.stereotype.Repository;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@Repository
public interface ImgSettingDao extends BaseMapper<ImgSetting> {
}
package com.zq.imgproc.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023-11-28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("image_detection")
public class ImageDetection implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("数据ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("用户ID")
@TableField("user_id")
private Long userId;
@ApiModelProperty("用户名称")
@TableField("nick_name")
private String nickName;
@ApiModelProperty("图片名称")
@TableField("img_name")
private String imgName;
@ApiModelProperty("文件名称")
@TableField("file_name")
private String fileName;
@ApiModelProperty("文件路径")
@TableField("file_url")
private String fileUrl;
@ApiModelProperty("图片大小")
@TableField("size")
private Double size;
@ApiModelProperty("水平分辨率")
@TableField("width_resolution")
private Integer widthResolution;
@ApiModelProperty("垂直分辨率")
@TableField("height_resolution")
private Integer heightResolution;
@ApiModelProperty("DPI")
@TableField("dpi")
private Integer dpi;
@ApiModelProperty("图片清晰度")
@TableField("clarity")
private Double clarity;
@ApiModelProperty("图片弯曲置信度")
@TableField("bend")
private Double bend;
@ApiModelProperty("图片亮度起始范围")
@TableField("brightness")
private Integer brightness;
@ApiModelProperty("图片偏离角度")
@TableField("deskew_angel")
private Integer deskewAngel;
@ApiModelProperty("图片黑边数量")
@TableField("black")
private Integer black;
@ApiModelProperty("是否合格,0不合格,1合格")
@TableField("qualified")
private Integer qualified;
@ApiModelProperty("上传时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date uploadTime;
}
package com.zq.imgproc.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value = "img_setting")
public class ImgSetting implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("数据ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("用户ID")
@TableField("user_id")
private Long userId;
@ApiModelProperty("图片大小")
@TableField("size")
private Integer size;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("size_enabled")
private Integer sizeEnabled;
@ApiModelProperty("图片DPI")
@TableField("dpi")
private Integer dpi;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("dpi_enabled")
private Integer dpiEnabled;
@ApiModelProperty("图片清晰度")
@TableField("clarity")
private Double clarity;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("clarity_enabled")
private Integer clarityEnabled;
@ApiModelProperty("图片亮度起始范围")
@TableField("brightness_start")
private Integer brightnessStart;
@ApiModelProperty("图片亮度结束范围")
@TableField("brightness_end")
private Integer brightnessEnd;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("brightness_enabled")
private Integer brightnessEnabled;
@ApiModelProperty("图片偏离角度")
@TableField("deskew_angel")
private Integer deskewAngel;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("deskew_enabled")
private Integer deskewEnabled;
@ApiModelProperty("图片水平分辨率")
@TableField("width_resolution")
private Integer widthResolution;
@ApiModelProperty("图片垂直分辨率")
@TableField("height_resolution")
private Integer heightResolution;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("resolution_enabled")
private Integer resolutionEnabled;
@ApiModelProperty("黑边的数值")
@TableField("black")
private Integer black;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("black_enabled")
private Integer blackEnabled;
@ApiModelProperty("弯曲的置信度")
@TableField("bend")
private Double bend;
@ApiModelProperty("是否启用,0是禁用,1是启用")
@TableField("bend_enabled")
private Integer bendEnabled;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
package com.zq.imgproc.server;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.PageVo;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.dao.ImageDetectionDao;
import com.zq.imgproc.dao.ImgSettingDao;
import com.zq.imgproc.entity.ImageDetection;
import com.zq.imgproc.entity.ImgSetting;
import com.zq.imgproc.utils.DecompressUtil;
import com.zq.imgproc.utils.Deskew;
import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.vo.DetectionPageReq;
import com.zq.imgproc.vo.ImgVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ImageDetectionService {
private final ImgSettingDao imgSettingDao;
private final ImageDetectionDao imageDetectionDao;
public ResultVo<?> detection(long userId, String nickName, MultipartFile[] file) throws Exception {
// 查询用户配置
ImgSetting imgSetting = imgSettingDao.selectOne(
Wrappers.lambdaQuery(ImgSetting.builder().userId(userId).build())
);
if (imgSetting == null) {
return ResultVo.fail("查询不到用户的配置信息!");
}
// 解压文件
List<ImgVO> imgs = new ArrayList<>();
for (MultipartFile one : file) {
if (DecompressUtil.isCompress(one)) {
String path = DecompressUtil.decompress(one);
AssertUtils.hasText(path, "解压失败!");
imgs.addAll(DecompressUtil.loopFiles(path, one.getOriginalFilename()));
} else {
String url = ImageUtil.saveTempFile(one);
imgs.add(ImgVO.builder().fileName(one.getOriginalFilename()).name(one.getOriginalFilename()).url(url).build());
}
}
// 保存上传信息
List<ImageDetection> detections = new ArrayList<>();
File image;
for (ImgVO img : imgs) {
image = FileUtil.file(img.getUrl());
if (!image.exists()) {
continue;
}
// 图片检测
ImageDetection detection = new ImageDetection();
// 设置上传信息
detection.setUserId(userId);
detection.setNickName(nickName);
detection.setUploadTime(DateUtil.date());
// 设置文件信息
detection.setFileName(img.getName());
detection.setImgName(img.getFileName());
detection.setFileUrl(img.getUrl());
detection.setSize(transformMb(image.length()));
// 设置图片检测信息
Mat src = Imgcodecs.imread(img.getUrl());
// 检测图片的分辨率
detection.setWidthResolution(src.width());
detection.setHeightResolution(src.height());
// 检测图片的DPI
detection.setDpi(ImageUtil.getDpi(image));
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src));
detection.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
// 检测图片的亮度
detection.setBrightness(Convert.toInt(ImageUtil.brightness(src)));
// 检测图片倾斜角度
detection.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src)));
// 检测图片的黑边
detection.setBlack(ImageUtil.blackDetection2(src));
// 图片弯曲检测
// BendResult bendResult = BendUtil.getBendResult(img.getUrl());
// detection.setBend(bendResult.getConfidence());
detection.setBend(0.0);
// 检查是否合格
detection.setQualified(check(detection, imgSetting));
detections.add(detection);
}
// 批量插入
imageDetectionDao.insertBatch(detections);
return ResultVo.success(detections);
}
/**
* 检查图片是否合格
* 1. 亮度
* 2. 清晰度
* 3. 偏离度
* 4. 黑边
* 5. 弯曲
*/
private Integer check(ImageDetection detection, ImgSetting imgSetting) {
// 亮度
if (detection.getBrightness() < imgSetting.getBrightnessStart() || detection.getBrightness() > imgSetting.getBrightnessEnd()) {
return 0;
}
// 清晰度
if (detection.getClarity().compareTo(imgSetting.getClarity()) < 0) {
return 0;
}
// 偏离度
if (detection.getDeskewAngel() > imgSetting.getDeskewAngel()) {
return 0;
}
// 黑边
if (detection.getBlack() > imgSetting.getBlack()) {
return 0;
}
// 弯曲度
if (detection.getBend().compareTo(imgSetting.getBend()) > 0) {
return 0;
}
return 1;
}
/**
* 将字节数转换为MB
*/
private double transformMb(long size) {
BigDecimal sizeDecimal = BigDecimal.valueOf(size);
BigDecimal mb = BigDecimal.valueOf(1024 * 1024);
return sizeDecimal.divide(mb, 2, RoundingMode.DOWN).doubleValue();
}
public PageVo<ImageDetection> selectPage(DetectionPageReq req) {
Page<ImageDetection> ipage = Page.of(req.getPage(), req.getSize());
imageDetectionDao.queryPageList(ipage, req);
return PageVo.ofReqVo(req, ipage.getRecords(), Convert.toInt(ipage.getTotal()));
}
public ImageDetection selectOne(Long id) {
return imageDetectionDao.selectById(id);
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.vo.BendResult;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
/**
* <p>
* 弯曲度检测
* </P>
*
* @author yww
* @since 2023/11/26
*/
@Slf4j
public class BendUtil {
private final static String URL = "http://129.204.37.121:8001/api/det/bending";
private final static String WANPRO_URL = "http://172.28.1.223:8030/api/det/bending";
private final static String URL2 = "http://147.1.3.244:8030/api/det/bending";
public static void main(String[] args) {
System.out.println(getBendResult("C:\\Users\\11419\\Desktop\\project\\company\\test\\9.png").toString());
}
public static BendResult getBendResult(String imgPath) {
// 设定参数
HashMap<String, Object> param = new HashMap<>();
param.put("file", FileUtil.file(imgPath));
BendResult res = null;
try {
String result = HttpUtil.post(URL2, param, 1000 * 20);
res = parseResult(result);
} catch (Exception e) {
log.error("图片弯曲检测失败!", e);
}
if (res == null) {
res = BendResult.builder().confidence(0.0).build();
}
return res;
}
/**
* 解析弯曲检测结果
*/
public static BendResult parseResult(String resultJson) {
BendResult result = null;
ResultVo resultVo = JSONUtil.toBean(resultJson, ResultVo.class);
if (!resultVo.isSuccess()) {
log.warn("弯曲度检测出错: {}", resultVo.getErrMsg());
return null;
}
JSONObject results = JSONUtil.parseObj(Convert.toStr(resultVo.getData()));
List<BendResult> bendResults = JSONUtil.toList(results.getStr("results"), BendResult.class);
if (bendResults == null || bendResults.isEmpty()) {
return null;
}
double max = -1;
for (BendResult bendResult : bendResults) {
if (bendResult.getConfidence() == null) {
continue;
}
if (bendResult.getConfidence() > max) {
result = bendResult;
max = bendResult.getConfidence();
}
}
return result;
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.extra.compress.CompressUtil;
import cn.hutool.extra.compress.extractor.Extractor;
import cn.hutool.http.HttpUtil;
import com.zq.common.utils.UuidUtils;
import com.zq.imgproc.vo.ImgVO;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/3/7 14:29
*/
public class DecompressUtil {
private static final String[] IMG_EXTS = {"png", "jpg", "jpeg", "tif", "bmp", "heic"};
private static final String[] COMPRESS_EXTS = {"zip", "7z"};
/**
* 压缩包解压
*
* @param file 压缩包文件
* @return 保存的文件夹路径
* @throws IOException IO异常
*/
public static String decompress(MultipartFile file) throws IOException {
String ext = FileUtil.extName(file.getOriginalFilename());
if ("7z".equals(ext)) {
return decompress7z(file);
} else if ("zip".equals(ext)) {
return decompressZip(file);
} else {
return "";
}
}
/**
* 7z压缩包解压
* @param file 压缩包文件
* @return 保存的文件夹路径
* @throws IOException IO异常
*/
public static String decompress7z(MultipartFile file) throws IOException {
// 文件路径要素
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String filePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
// 解压文件
InputStream inputStream = file.getInputStream();
Extractor extractor = CompressUtil.createExtractor(CharsetUtil.defaultCharset(),
ArchiveStreamFactory.SEVEN_Z,
inputStream);
extractor.extract(FileUtil.file(filePath));
extractor.close();
filePath = filePath + "/";
return filePath;
}
/**
* zip压缩包解压
* @param file 压缩包文件
* @return 保存的文件夹路径
* @throws IOException IO异常
*/
public static String decompressZip(MultipartFile file) throws IOException {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String filePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
ZipUtil.unzip(file.getInputStream(), FileUtil.file(filePath), Charset.forName("GBK"));
filePath = filePath + "/";
return filePath;
}
/**
* 文件压缩成zip
*
* @param list 图片文件
* @return zip压缩文件
*/
public static String compress(List<ImgVO> list) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
for (ImgVO img : list) {
// 网上的文件需要下载
if (img.getUrl().startsWith("http://ddns.gxmailu.com:18888")) {
HttpUtil.downloadFile(img.getUrl(), savePath + img.getFileName());
} else {
FileUtil.copyFile(img.getUrl(), savePath + img.getFileName(), StandardCopyOption.COPY_ATTRIBUTES);
}
}
String filePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + ".zip";
ZipUtil.zip(savePath, filePath, false);
FileUtil.del(savePath);
return filePath;
}
/**
* 遍历文件夹,返回图片文件
*
* @param path 文件及路径
* @return 图片文件列表
* @throws IOException IO异常
*/
public static List<ImgVO> loopFiles(String path, String name) throws IOException {
List<ImgVO> resList = new ArrayList<>();
List<File> fileList = FileUtil.loopFiles(path);
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (File file : fileList) {
String ext = FileUtil.extName(file);
if (StrUtil.isBlank(ext) || Arrays.stream(IMG_EXTS).noneMatch(img -> img.equalsIgnoreCase(ext))) {
continue;
}
// 反转义
String url = savePath + index + "." + ext;
File dest = new File(url);
FileUtils.writeByteArrayToFile(dest, FileUtil.readBytes(file));
index++;
resList.add(ImgVO.builder().fileName(file.getName()).url(url).name(name).build());
}
return resList;
}
/**
* 文件类型判断
*
* @param file 压缩包文件
* @return 类型正确返回true
*/
public static boolean isCompress(MultipartFile file) {
String ext = FileUtil.extName(file.getOriginalFilename());
return Arrays.stream(COMPRESS_EXTS).anyMatch(suffix -> suffix.equalsIgnoreCase(ext));
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.imgproc.vo.DetectionVO;
import com.zq.imgproc.vo.OptimizationReq;
import com.zq.imgproc.vo.OptimizationVO;
public class DemoUtil {
final static String URL = "localhost:8900/imgproc/v1/";
public static void main(String[] args) {
imageOptimization();
}
public static void detection() {
String testImg = "C:/Users/11419/Desktop/Deskew/TestImages/3.png";
String resImg = "C:/Users/11419/Desktop/Deskew/TestImages/res.jpg";
String url = URL + "detection";
OptimizationReq req = new OptimizationReq();
req.setFilename("3.png");
req.setFileContent(Base64.encode(FileUtil.file(testImg)));
long start = System.currentTimeMillis();
HttpRequest request = HttpUtil.createPost(url)
.body(JSONUtil.toJsonStr(req));
try (HttpResponse response = request.execute()) {
System.out.println(System.currentTimeMillis() - start);
String res = response.body();
JSONObject object = JSONUtil.parseObj(res);
DetectionVO detectionRes = object.get("data", DetectionVO.class);
System.out.println(JSONUtil.toJsonStr(detectionRes));
}
}
public static void imageOptimization() {
String testImg = "C:/Users/11419/Desktop/Deskew/TestImages/6.png";
String resImg = "C:/Users/11419/Desktop/Deskew/TestImages/6res.png";
String url = URL + "imageOptimization";
OptimizationReq req = new OptimizationReq();
req.setFilename("4.png");
req.setFileContent(Base64.encode(FileUtil.file(testImg)));
long start = System.currentTimeMillis();
HttpRequest request = HttpUtil.createPost(url)
.body(JSONUtil.toJsonStr(req));
try (HttpResponse response = request.execute()) {
System.out.println(System.currentTimeMillis()- start);
String res = response.body();
JSONObject object = JSONUtil.parseObj(res);
OptimizationVO optimizationVO = object.get("data", OptimizationVO.class);
FileUtil.writeBytes(Base64.decode(optimizationVO.getFileContent()), FileUtil.file(resImg));
optimizationVO.setFileContent(null);
System.out.println(JSONUtil.toJsonStr(optimizationVO));
}
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.convert.Convert;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Deskew {
public static void main(String[] args) {
System.load("D:/project/imgproc/lib/opencv_java460.dll");
Mat image = Imgcodecs.imread("C:/Users/11419/Desktop/Deskew/TestImages/4.png");
int angle = getDeskewAngle(image);
System.out.println(angle);
Mat roateMat = rotate(image, angle);
Imgcodecs.imwrite("C:/Users/11419/Desktop/Deskew/TestImages/4res.png", roateMat);
}
public static Integer getDeskewAngle(Mat src) {
// 图片灰度化
Mat gray = src.clone();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
// // 高斯模糊(?)
// @Cleanup
// UMat blur = gray.clone();
// GaussianBlur(gray, blur, new Size(9, 9), 0);
// // 二值化(?)
// @Cleanup
// UMat thresh = blur.clone();
// threshold(blur, thresh, 0, 255, THRESH_BINARY_INV + THRESH_OTSU);
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
// 图片膨胀
Mat erode = gray.clone();
Imgproc.erode(gray, erode, kernel);
// 图片腐蚀
Mat dilate = erode.clone();
Imgproc.dilate(erode, dilate, kernel);
// 边缘检测
Mat canny = dilate.clone();
Imgproc.Canny(dilate, canny, 50, 150);
// 霍夫变换得到线条
Mat lines = new Mat();
//累加器阈值参数,小于设置值不返回
int threshold = 90;
//最低线段长度,低于设置值则不返回
double minLineLength = 100;
//间距小于该值的线当成同一条线
double maxLineGap = 10;
// 霍夫变换,通过步长为1,角度为PI/180来搜索可能的直线
Imgproc.HoughLinesP(canny, lines, 1, Math.PI / 180, threshold, minLineLength, maxLineGap);
// Mat mat = canny.clone();
// Scalar color = new Scalar(0, 0, 255);
// for (int i = 0; i < lines.rows(); i++) {
// double[] line = lines.get(i, 0);
// Imgproc.line(mat, new Point(line[0], line[1]), new Point(line[2], line[3]), color,3, LINE_AA);
// }
// Imgcodecs.imwrite("C:\\Users\\11419\\Desktop\\test\\bend2res.jpg", mat);
// 计算倾斜角度
List<Integer> angelList = new ArrayList<>();
for (int i = 0; i < lines.rows(); i++) {
double[] line = lines.get(i, 0);
int k = calculateAngle(line[0], line[1], line[2], line[3]);
angelList.add(k);
}
if (angelList.isEmpty()) {
return 0;
}
gray.release();
kernel.release();
erode.release();
dilate.release();
canny.release();
lines.release();
// 可能还得需要考虑方差来决定选择平均数还是众数
return most(angelList);
}
/**
* 求数组众数
*
* @param angelList 数组
* @return 数组众数
*/
private static int most(List<Integer> angelList) {
if (angelList.isEmpty()) {
return 0;
}
int res = 0;
int max = Integer.MIN_VALUE;
Map<Integer, Integer> map = new HashMap<>();
for (int i : angelList) {
map.put(i, map.getOrDefault(i, 0) + 1);
}
for (Integer i : map.keySet()) {
int count = map.get(i);
if (count > max) {
max = count;
res = i;
}
}
return res;
}
/**
* 计算直线的倾斜角
*/
private static int calculateAngle(double x1, double y1, double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
if (Math.abs(dx) < 1e-4) {
return 90;
} else if (Math.abs(dy) < 1e-4) {
return 0;
} else {
double radians = Math.atan2(dy, dx);
double degrees = Math.toDegrees(radians);
return Convert.toInt(Math.round(degrees));
}
}
/**
* 图片旋转
*
* @param image 输入图片
* @param angle 旋转角度
* @return 输出图片
*/
public static Mat rotate(Mat image, double angle) {
int w = image.cols();
int h = image.rows();
if (angle < 0) {
angle = 360 + angle;
}
Point center = new Point((double) w / 2, (double) h / 2);
double scale = 1.0;
Mat rotationMatrix = Imgproc.getRotationMatrix2D(center, angle, scale);
// 设置填充颜色为白色
Scalar backgroundColor = new Scalar(255, 255, 255);
Mat rotatedImage = new Mat();
Imgproc.warpAffine(image, rotatedImage, rotationMatrix, new Size(w, h), Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, backgroundColor);
rotationMatrix.release();
return rotatedImage;
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.util.RuntimeUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
/**
* <p>
* 纠偏
* </p>
*
* @author chenhao
* @since 2023/3/8 10:23
*/
public class ImageCorrectionUtil {
/**
* 纠正图片旋转
*
* @param srcImgPath 图片地址
*/
public static void correctImg(String srcImgPath) {
FileOutputStream fos = null;
try {
// 原始图片
File srcFile = new File(srcImgPath);
// 获取偏转角度
int angle = getAngle(srcFile);
if (angle != 90 && angle != 270) {
return;
}
// 原始图片缓存
BufferedImage srcImg = ImageIO.read(srcFile);
// 宽高互换
// 原始宽度
int imgWidth = srcImg.getHeight();
// 原始高度
int imgHeight = srcImg.getWidth();
// 中心点位置
double centerWidth = ((double) imgWidth) / 2;
double centerHeight = ((double) imgHeight) / 2;
// 图片缓存
BufferedImage targetImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
// 旋转对应角度
Graphics2D g = targetImg.createGraphics();
g.rotate(Math.toRadians(angle), centerWidth, centerHeight);
g.drawImage(srcImg, (imgWidth - srcImg.getWidth()) / 2, (imgHeight - srcImg.getHeight()) / 2, null);
g.rotate(Math.toRadians(-angle), centerWidth, centerHeight);
g.dispose();
// 输出图片
fos = new FileOutputStream(srcFile);
ImageIO.write(targetImg, "jpg", fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 获取图片旋转角度
*
* @param file 上传图片
* @return 图片旋转角度
*/
private static int getAngle(File file) throws Exception {
Metadata metadata = ImageMetadataReader.readMetadata(file);
for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) {
if ("Orientation".equals(tag.getTagName())) {
String orientation = tag.getDescription();
if (orientation.contains("90")) {
return 90;
} else if (orientation.contains("180")) {
return 180;
} else if (orientation.contains("270")) {
return 270;
}
}
}
}
return 0;
}
/**
* 调用deskew工具
* Usage:
* deskew [-o output] [-a angle] [-b color] [..] input
* input: Input image file
* Options:
* -o output: 输出图像文件路径(默认:out.png)
* -a angle: 最大期望倾斜角度(两个方向)(默认:10度)
* -b color: 背景颜色十六进制格式RRGGBB|LL|AARRGGBB(默认为黑色)
* Ext. options:
* -q filter: 用于旋转的重采样过滤器 (default: linear,
* values: nearest|linear|cubic|lanczos)
* -t a|treshold: 自动阈值或0..255的值 (default: a)
* -r rect: 仅在内容矩形中进行倾斜检测 (pixels):
* left,top,right,bottom (default: whole page)
* -f format: 输出像素格式 (values: b1|g8|rgb24|rgba32)
* -l angle: 如果倾斜角度较小,则跳过倾斜步骤 (default: 0.01)
* -g flags: Operational flags (any combination of):
* c - auto crop, d - detect only (no output to file)
* -s info: 信息转储 (any combination of):
* s - skew detection stats, p - program parameters, t - timings
* -c specs: 某些文件格式的输出压缩规格。可以定义几个规格-用逗号分隔。支持规格:
* jXX - JPEG compression quality, XX is in range [1,100(best)]
* tSCHEME - TIFF compression scheme: none|lzw|rle|deflate|jpeg|g4
* Supported file formats
* Input: BMP, JPG, PNG, JNG, GIF, DDS, TGA, PBM, PGM, PPM, PAM, PFM, TIF, PSD
* Output: BMP, JPG, PNG, JNG, GIF, DDS, TGA, PGM, PPM, PAM, PFM, TIF, PSD
*
* @return 调用结果
*/
public static String deskew(String src, String dst, String deskewUrl, boolean deskewAngle) {
StringBuilder str = new StringBuilder();
// 命令行位置
str.append(deskewUrl).append(" ");
// 背景颜色
str.append("-b").append(" ").append("FFFFFF").append(" ");
if (deskewAngle) {
// 图片旋转耗时很多,跳过图片旋转
str.append("-l").append(" ").append("80").append(" ");
}
// 输出路径
str.append("-o").append(" ").append(dst).append(" ");
// 输入路径
str.append(src);
return RuntimeUtil.execForStr(str.toString());
}
public static String deskew2(String src, String dst, String deskewpyUrl) {
StringBuilder str = new StringBuilder();
// python启动
str.append("python").append(" ");
// python文件位置
str.append(deskewpyUrl).append(" ");
// 输入文件
str.append("-i").append(" ").append(src).append(" ");
// 输出文件
str.append("-o").append(" ").append(dst).append(" ");
return RuntimeUtil.execForStr(str.toString());
}
public static void main(String[] args) {
String path1 = "C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\3.png";
String path2 = "C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\res3.png";
String du = "C:\\Users\\11419\\Desktop\\lib\\correct.py";
String res = deskew2(path1, path2, du);
System.out.println(res);
}
}
package com.zq.imgproc.utils;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* <p>
* 去黑边
* </p>
*
* @author chenhao
* @since 2023/3/8 9:31
*/
public class RemoveBlackUtil {
public static void main(String[] args) {
String testImg = "C:\\Users\\11419\\Desktop\\test\\a.jpg";
String resImg = "C:\\Users\\11419\\Desktop\\test\\8.jpg";
System.load("D:\\project\\imgproc\\lib\\opencv_java460.dll");
remove(testImg, resImg);
}
public static void remove(String src,String dst) {
Mat img = Imgcodecs.imread(src);
if(img.empty()){
return;
}
Mat greyImg = img.clone();
//1.彩色转灰色
Imgproc.cvtColor(img, greyImg, Imgproc.COLOR_BGR2GRAY);
ImageUtil.saveImage(greyImg, "C:\\Users\\11419\\Desktop\\test\\1.jpg");
Mat gaussianBlurImg = greyImg.clone();
// 2.高斯滤波,降噪
Imgproc.GaussianBlur(greyImg, gaussianBlurImg, new Size(3,3),0);
ImageUtil.saveImage(greyImg, "C:\\Users\\11419\\Desktop\\test\\2.jpg");
// 3.Canny边缘检测
Mat cannyImg = gaussianBlurImg.clone();
Imgproc.Canny(gaussianBlurImg, cannyImg, 50, 200);
ImageUtil.saveImage(cannyImg, "C:\\Users\\11419\\Desktop\\test\\3.jpg");
// 4.膨胀,连接边缘
Mat dilateImg = cannyImg.clone();
Imgproc.dilate(cannyImg, dilateImg, new Mat(), new Point(-1, -1), 3, 1, new Scalar(1));
ImageUtil.saveImage(dilateImg, "C:\\Users\\11419\\Desktop\\test\\4.jpg");
//5.对边缘检测的结果图再进行轮廓提取
List<MatOfPoint> contours = new ArrayList<>();
List<MatOfPoint> drawContours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(dilateImg, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
Mat linePic = Mat.zeros(dilateImg.rows(), dilateImg.cols(), CvType.CV_8UC3);
//6.找出轮廓对应凸包的四边形拟合
List<MatOfPoint> squares = new ArrayList<>();
List<MatOfPoint> hulls = new ArrayList<>();
MatOfInt hull = new MatOfInt();
MatOfPoint2f approx = new MatOfPoint2f();
approx.convertTo(approx, CvType.CV_32F);
for (MatOfPoint contour : contours) {
// 边框的凸包
Imgproc.convexHull(contour, hull);
// 用凸包计算出新的轮廓点
Point[] contourPoints = contour.toArray();
int[] indices = hull.toArray();
List<Point> newPoints = new ArrayList<>();
for (int index : indices) {
newPoints.add(contourPoints[index]);
}
MatOfPoint2f contourHull = new MatOfPoint2f();
contourHull.fromList(newPoints);
// 多边形拟合凸包边框(此时的拟合的精度较低)
Imgproc.approxPolyDP(contourHull, approx, Imgproc.arcLength(contourHull, true) * 0.02, true);
MatOfPoint mat = new MatOfPoint();
mat.fromArray(approx.toArray());
drawContours.add(mat);
// 筛选出面积大于某一阈值的,且四边形的各个角度都接近直角的凸四边形
MatOfPoint approxf1 = new MatOfPoint();
approx.convertTo(approxf1, CvType.CV_32S);
if (approx.rows() == 4 && Math.abs(Imgproc.contourArea(approx)) > 40000 &&
Imgproc.isContourConvex(approxf1)) {
double maxCosine = 0;
for (int j = 2; j < 5; j++) {
double cosine = Math.abs(getAngle(approxf1.toArray()[j % 4], approxf1.toArray()[j - 2], approxf1.toArray()[j - 1]));
maxCosine = Math.max(maxCosine, cosine);
}
// 角度大概72度
if (maxCosine < 0.3) {
MatOfPoint tmp = new MatOfPoint();
contourHull.convertTo(tmp, CvType.CV_32S);
squares.add(approxf1);
hulls.add(tmp);
}
}
}
//这里是把提取出来的轮廓通过不同颜色的线描述出来,具体效果可以自己去看
Random r = new Random();
for (int i = 0; i < drawContours.size(); i++) {
Imgproc.drawContours(linePic, drawContours, i, new Scalar(r.nextInt(255),r.nextInt(255), r.nextInt(255)));
}
ImageUtil.saveImage(linePic, "C:\\Users\\11419\\Desktop\\test\\5.jpg");
//7.找出最大的矩形
int index = findLargestSquare(squares);
MatOfPoint largest_square;
if(squares.size() > 0){
largest_square = squares.get(index);
}else{
System.out.println("图片无法识别");
return;
}
Mat polyPic = Mat.zeros(img.size(), CvType.CV_8UC3);
Imgproc.drawContours(polyPic, squares, index, new Scalar(0, 0,255), 2);
ImageUtil.saveImage(polyPic, "C:\\Users\\11419\\Desktop\\test\\6.jpg");
//存储矩形的四个凸点
hull = new MatOfInt();
Imgproc.convexHull(largest_square, hull, false);
List<Integer> hullList = hull.toList();
List<Point> polyContoursList = largest_square.toList();
List<Point> hullPointList = new ArrayList<>();
for (Integer integer : hullList) {
Imgproc.circle(polyPic, polyContoursList.get(integer), 10, new Scalar(r.nextInt(255), r.nextInt(255), r.nextInt(255), 3));
hullPointList.add(polyContoursList.get(integer));
}
Core.addWeighted(polyPic, 1, img, 1, 0, img);
ImageUtil.saveImage(img, "C:\\Users\\11419\\Desktop\\test\\7.jpg");
List<Point> lastHullPointList = new ArrayList<>(hullPointList);
//dstPoints储存的是变换后各点的坐标,依次为左上,右上,右下, 左下
//srcPoints储存的是上面得到的四个角的坐标
Point[] dstPoints = {new Point(0,0), new Point(img.cols(),0), new Point(img.cols(),img.rows()), new Point(0,img.rows())};
Point[] srcPoints = new Point[4];
boolean sorted = false;
int n = 4;
//对四个点进行排序 分出左上 右上 右下 左下
while (!sorted && n > 0){
for (int i = 1; i < n; i++){
sorted = true;
if (lastHullPointList.get(i-1).x > lastHullPointList.get(i).x){
Point tempp1 = lastHullPointList.get(i);
Point tempp2 = lastHullPointList.get(i-1);
lastHullPointList.set(i, tempp2);
lastHullPointList.set(i-1, tempp1);
sorted = false;
}
}
n--;
}
//即先对四个点的x坐标进行冒泡排序分出左右,再根据两对坐标的y值比较分出上下
if (lastHullPointList.get(0).y < lastHullPointList.get(1).y){
srcPoints[0] = lastHullPointList.get(0);
srcPoints[3] = lastHullPointList.get(1);
}else{
srcPoints[0] = lastHullPointList.get(1);
srcPoints[3] = lastHullPointList.get(0);
}
if (lastHullPointList.get(2).y < lastHullPointList.get(3).y){
srcPoints[1] = lastHullPointList.get(2);
srcPoints[2] = lastHullPointList.get(3);
}else{
srcPoints[1] = lastHullPointList.get(3);
srcPoints[2] = lastHullPointList.get(2);
}
List<Point> listSrcs = java.util.Arrays.asList(srcPoints[0], srcPoints[1], srcPoints[2], srcPoints[3]);
Mat srcPointsMat = Converters.vector_Point_to_Mat(listSrcs, CvType.CV_32F);
List<Point> dstSrcs = java.util.Arrays.asList(dstPoints[0], dstPoints[1], dstPoints[2], dstPoints[3]);
Mat dstPointsMat = Converters.vector_Point_to_Mat(dstSrcs, CvType.CV_32F);
//参数分别为输入输出图像、变换矩阵、大小。
//坐标变换后就得到了我们要的最终图像。
Mat transMat = Imgproc.getPerspectiveTransform(srcPointsMat, dstPointsMat); //得到变换矩阵
Mat outPic = new Mat();
Imgproc.warpPerspective(img, outPic, transMat, img.size());
ImageUtil.saveImage(outPic, dst);
}
// 根据三个点计算中间那个点的夹角 pt1 pt0 pt2
private static double getAngle(Point pt1, Point pt2, Point pt0) {
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/Math.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// 找到最大的正方形轮廓
private static int findLargestSquare(List<MatOfPoint> squares) {
if (squares.size() == 0) {
return -1;
}
int max_width = 0;
int max_height = 0;
int max_square_idx = 0;
int currentIndex = 0;
for (MatOfPoint square : squares) {
Rect rectangle = Imgproc.boundingRect(square);
if (rectangle.width >= max_width && rectangle.height >= max_height) {
max_width = rectangle.width;
max_height = rectangle.height;
max_square_idx = currentIndex;
}
currentIndex++;
}
return max_square_idx;
}
}
package com.zq.imgproc.utils;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
/**
* <p>
* 去黑边
* </p>
*
* @author chenhao
* @since 2023/3/10 15:01
*/
public class RemoveBlackUtil2 {
/**
* 去黑边"全黑"阈值
*/
private static final Integer BLACK_VALUE = 100;
public static void remove(String src, String dst) {
Mat mat = Imgcodecs.imread(src);
Mat res = removeBlackEdge(mat);
ImageUtil.saveImage(res, dst);
mat.release();
res.release();
}
/**
* 去除图片黑边,若无黑边,则原图返回。默认“全黑”阈值为 {@code BLACK_VALUE}
*
* @param srcMat 预去除黑边的Mat
* @return 去除黑边之后的Mat
*/
private static Mat removeBlackEdge(Mat srcMat) {
return removeBlackEdge(srcMat, BLACK_VALUE);
}
/**
* 去除图片黑边,若无黑边,则原图返回。
*
* @param blackValue 一般低于5的已经是很黑的颜色了
* @param srcMat 源Mat对象
* @return Mat对象
*/
private static Mat removeBlackEdge(Mat srcMat, int blackValue) {
// 灰度化
Mat grayMat = gray(srcMat);
// 定义边界
int topRow = 0;
int leftCol = 0;
int rightCol = grayMat.width() - 1;
int bottomRow = grayMat.height() - 1;
// 上方黑边判断
for (int row = 0; row < grayMat.height(); row++) {
if (ImageUtil.sum(grayMat.row(row)) / grayMat.width() < blackValue) {
topRow = row;
} else {
break;
}
}
// 左边黑边判断
for (int col = 0; col < grayMat.width(); col++) {
if (ImageUtil.sum(grayMat.col(col)) / grayMat.height() < blackValue) {
leftCol = col;
} else {
break;
}
}
// 右边黑边判断
for (int col = grayMat.width() - 1; col > 0; col--) {
if (ImageUtil.sum(grayMat.col(col)) / grayMat.height() < blackValue) {
rightCol = col;
} else {
break;
}
}
// 下方黑边判断
for (int row = grayMat.height() - 1; row > 0; row--) {
if (ImageUtil.sum(grayMat.row(row)) / grayMat.width() < blackValue) {
bottomRow = row;
} else {
break;
}
}
int x = leftCol;
int y = topRow;
int width = rightCol - leftCol;
int height = bottomRow - topRow;
grayMat.release();
if (leftCol == 0 && rightCol == grayMat.width() - 1 && topRow == 0 && bottomRow == grayMat.height() - 1) {
return srcMat;
}
return cut(srcMat, x, y, width, height);
}
/**
* 灰度处理 BGR灰度处理
*
* @param src 原图Mat
* @return Mat 灰度后的Mat
*/
private static Mat gray(Mat src) {
Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
return gray;
}
/**
* 按照指定的尺寸截取Mat,坐标原点为左上角
*
* @param src 源Mat
* @param x x
* @param y y
* @param width width
* @param height height
* @return 截取后的Mat
*/
private static Mat cut(Mat src, int x, int y, int width, int height) {
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (width > src.width()) {
width = src.width();
}
if (height > src.height()) {
height = src.height();
}
// 截取尺寸
Rect rect = new Rect(x, y, width, height);
return new Mat(src, rect);
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.zq.imgproc.vo.DetectionVO;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/20
*/
@Slf4j
public class Test2 {
public static void main(String[] args) throws Exception {
System.load("D:\\project\\imgproc\\lib\\opencv_java460.dll");
String src = "C:\\Users\\11419\\Desktop\\test\\black1.png";
String dst = "C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\6res.png";
long start = System.currentTimeMillis();
DetectionVO vo = new DetectionVO();
Mat image = Imgcodecs.imread(src);
log.info("加载图片耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片的分辨率
vo.setWidthResolution(image.width());
vo.setHeightResolution(image.height());
log.info("检测图片分辨率耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片的DPI
vo.setDpi(ImageUtil.getDpi(FileUtil.file(src)));
log.info("检测图片DPI耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(image));
vo.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
log.info("检测图片清晰度耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片的亮度
vo.setBrightness(Convert.toInt(ImageUtil.brightness(image)));
log.info("检测图片亮度耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片倾斜角度
vo.setAngle(Deskew.getDeskewAngle(image));
log.info("检测图片倾斜角度耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
// 检测图片的黑边
blackDetection2(image);
blackDetection3(image);
System.out.println(JSONUtil.toJsonStr(vo));
}
/**
* 黑边检测
*
* @param src 图片矩阵
* @return true表示可能存在黑边
*/
public static boolean blackDetection(Mat src) {
int BLACK_VALUE = 50;
int width = src.width();
int height = src.height();
// 定义初始边界
int top = 0;
int left = 0;
int right = width - 1;
int bottom = height - 1;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
top = row;
} else {
break;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
left = col;
} else {
break;
}
}
// 右边黑边判断
for (int col = width - 1; col > 0; col--) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
right = col;
} else {
break;
}
}
// 下方黑边判断
for (int row = height - 1; row > 0; row--) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
bottom = row;
} else {
break;
}
}
// 若是边界没有被更新,认为不存在黑边
return top != 0 || left != 0 || right != width - 1 || bottom != height - 1;
}
public static void blackDetection2(Mat src) {
long start = System.currentTimeMillis();
int BLACK_VALUE = 30;
int width = src.width();
int height = src.height();
int rows = 0;
int cols = 0;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
rows++;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
cols++;
}
}
log.info("{}, {}, {}, {}", "22222", rows, cols, System.currentTimeMillis() - start);
}
public static void blackDetection3(Mat src) {
long start = System.currentTimeMillis();
int BLACK_VALUE = 30;
int width = src.width();
int height = src.height();
int rows = 0;
int cols = 0;
int limitRows = 500;
int limitCols = 500;
for (int row = 0; row < height; row++) {
Scalar mean = Core.mean(src.row(row));
if (Convert.toInt(mean.val[0]) < BLACK_VALUE) {
rows++;
}
}
for (int col = 0; col < width; col++) {
Scalar mean = Core.mean(src.col(col));
if (Convert.toInt(mean.val[0]) < BLACK_VALUE) {
cols++;
}
}
log.info("{}, {}, {}, {}", "3333333", rows, cols, System.currentTimeMillis() - start);
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.zq.imgproc.vo.DetectionVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.Imaging;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import java.io.File;
@Slf4j
public class TestUtil {
public static void main(String[] args) throws Exception {
System.load("D:/project/imgproc/lib/opencv_java460.dll");
String src = "C:\\Users\\11419\\Desktop\\4.png";
String dst = "C:\\Users\\11419\\Desktop\\res.png";
// deskew(src, dst);
RemoveBlackUtil2.remove(src, dst);
// bright(src, dst);
// im(src, dst);
}
public static void deskew(String src, String dst) {
Mat mat = Imgcodecs.imread(src);
double skewAngle = Deskew.getDeskewAngle(mat);
Mat rotateMat = Deskew.rotate(mat, skewAngle);
Imgcodecs.imwrite(dst, rotateMat);
}
public static void removeBlack(String src, String dst) {
RemoveBlackUtil2.remove(src, dst);
}
public static void bright(String src, String dst) {
double brightness = ImageUtil.brightness(src);
System.out.println(brightness);
// 亮度异常执行亮度矫正
if (brightness < 100 || brightness > 500) {
// 计算亮度调整值
double alpha = 300 / brightness;
// 执行亮度调整
Mat grayImage = Imgcodecs.imread(src);
Mat adjustedImage = new Mat();
grayImage.convertTo(adjustedImage, -1, alpha, 0);
// 保存调整后的图像
Imgcodecs.imwrite(dst, adjustedImage);
}
}
public static void im(String src, String dst) {
double laplacianScore = ImageUtil.imageSharpnessDetector(src);
System.out.println(laplacianScore);
if (laplacianScore < 500) {
Mat unsharpMaskingMat = ImageUtil.unsharpMasking(src);
// 保存调整后的图像
Imgcodecs.imwrite(dst, unsharpMaskingMat);
}
}
public static DetectionVO detection(String src) throws Exception {
DetectionVO res = new DetectionVO();
Mat image = Imgcodecs.imread(src);
// 检测图片的分辨率
res.setWidthResolution(image.width());
res.setHeightResolution(image.height());
// 检测图片的DPI
res.setDpi(getDpi(FileUtil.file(src)));
// 检测图片清晰度
res.setClarity(ImageUtil.imageSharpnessDetector(image));
// 检测图片的亮度
res.setBrightness(ImageUtil.brightness(image));
// 检测图片倾斜角度
res.setAngle(Deskew.getDeskewAngle(image));
// 检测图片的黑边
res.setBlack(blackDetection(image));
return res;
}
/**
* 获取图片DPI
*
* @param file 图片文件
* @return [水平DPI,垂直DPI]
*/
private static Integer getDpi(File file) throws Exception {
int res;
ImageInfo imageInfo = Imaging.getImageInfo(file);
res = imageInfo.getPhysicalWidthDpi();
if (res == -1) {
Metadata metadata = ImageMetadataReader.readMetadata(file);
for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) {
if ("X Resolution".equals(tag.getTagName())) {
res = Convert.toInt(tag.getDescription());
}
}
}
}
return res;
}
/**
* 黑边像素阈值
*/
private static final Integer THRESHOLD = 30;
/**
* 黑边检测
*
* @param src 图片矩阵
* @return true表示可能存在黑边
*/
public static boolean blackDetection(Mat src) {
int width = src.width();
int height = src.height();
// 定义初始边界
int top = 0;
int left = 0;
int right = width - 1;
int bottom = height - 1;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < THRESHOLD) {
top = row;
} else {
break;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < THRESHOLD) {
left = col;
} else {
break;
}
}
// 右边黑边判断
for (int col = width - 1; col > 0; col--) {
if (ImageUtil.sum(src.col(col)) / height < THRESHOLD) {
right = col;
} else {
break;
}
}
// 下方黑边判断
for (int row = height - 1; row > 0; row--) {
if (ImageUtil.sum(src.row(row)) / width < THRESHOLD) {
bottom = row;
} else {
break;
}
}
// 若是边界没有被更新,认为不存在黑边
return top != 0 || left != 0 || right != width - 1 || bottom != height - 1;
}
}
package com.zq.imgproc.utils;
import cn.hutool.core.io.FileUtil;
import com.zq.common.exception.BusinessException;
import com.zq.common.utils.UuidUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
/**
* @author wilmiam
* @since 2021-07-09 18:05
*/
@Slf4j
public class UploadUtils {
private final static String DATE_FORMAT = "/yyyyMM/dd/";
/**
* 保存临时文件
*
* @return 保存地址
*/
public static String saveTempFile(MultipartFile file, String systemName) {
try {
String originalFilename = file.getOriginalFilename();
//取文件扩展名
String ext = Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf(".") + 1);
//生成新文件名
String name = UuidUtils.uuidNoDash() + "." + ext;
String yyyyMMdd = new SimpleDateFormat(DATE_FORMAT).format(new Date());
File dest = new File("/data/temp/" + systemName + yyyyMMdd + name);
FileUtil.writeBytes(file.getBytes(), dest);
return "/data/temp/" + systemName + yyyyMMdd + name;
} catch (IOException e) {
log.error("文件保存失败:{}", e.getMessage());
throw new BusinessException("文件保存失败");
}
}
}
package com.zq.imgproc.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* <p>
* 弯曲检测结果
* </P>
*
* @author chenhao
* @since 2023/11/26
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BendResult {
/**
* 标签名称
*/
String label;
/**
* 置信度
*/
Double confidence;
/**
* 弯曲位置
*/
List<Double> coord;
}
package com.zq.imgproc.vo;
import lombok.Data;
import java.util.List;
/**
* <p>
* 压缩请求
* </p>
*
* @author chenhao
* @since 2023/3/9 17:35
*/
@Data
public class CompressReq {
String name;
List<ImgVO> pathList;
}
package com.zq.imgproc.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/24
*/
@Data
@EqualsAndHashCode
public class DetectionDTO {
@ExcelProperty("文件名称")
private String filename;
@ExcelProperty("分辨率")
private String resolution;
@ExcelProperty("DPI")
private Integer dpi;
@ExcelProperty("平均亮度值")
private double brightness;
@ExcelProperty("清晰度值")
private double clarity;
@ExcelProperty("倾斜角度")
private double angle;
@ExcelProperty("弯曲置信度")
private double bend;
@ExcelProperty("黑边情况")
private String black;
}
package com.zq.imgproc.vo;
import com.zq.common.vo.PageReqVo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/11/28
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DetectionPageReq extends PageReqVo {
/**
* 用户名称
*/
String nickName;
/**
* 图片名称
*/
String imgName;
/**
* 文件名称
*/
String fileName;
/**
* 上传日期起始
*/
String startTime;
/**
* 上传日期结束
*/
String endTime;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getImgName() {
return imgName;
}
public void setImgName(String imgName) {
this.imgName = imgName;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}
package com.zq.imgproc.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 图片返回结果封装类
* </p>
*
* @author chenhao
* @since 2022/10/27 11:25
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class DetectionResVo {
@ApiModelProperty("图片的水平分辨率")
private Integer widthResolution;
@ApiModelProperty("图片的垂直分辨率")
private Integer heightResolution;
@ApiModelProperty("图片的DPI")
private Integer dpi;
@ApiModelProperty("图片的亮度情况,1表示亮度正常,2表示过亮,3表示过暗")
private Integer brightness;
@ApiModelProperty("图片的清晰度,true表示清晰,false表示模糊")
private Boolean clarity;
@ApiModelProperty("图片的倾斜角度")
private double angle;
@ApiModelProperty("图片的黑边检测,true表示可能存在黑边,false表示不存在黑边")
private Boolean black;
}
package com.zq.imgproc.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 检测结果
* </P>
*
* @author chenhao
* @since 2023/11/26
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DetectionVO {
@ApiModelProperty("图片的水平分辨率")
private Integer widthResolution;
@ApiModelProperty("图片的垂直分辨率")
private Integer heightResolution;
@ApiModelProperty("图片的DPI")
private Integer dpi;
@ApiModelProperty("平均亮度值")
private double brightness;
@ApiModelProperty("图片的清晰度指标,值越大越清晰")
private double clarity;
@ApiModelProperty("图片的倾斜角度")
private double angle;
@ApiModelProperty("图片弯曲检测置信度")
private double bend;
@ApiModelProperty("图片的黑边检测,true表示可能存在黑边,false表示不存在黑边")
private Boolean black;
}
package com.zq.imgproc.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/3/7 16:01
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class ImgVO {
String id;
String fileName;
String url;
DetectionVO detectionRes;
String name;
}
package com.zq.imgproc.vo;
import lombok.Data;
/**
* <p>
*
* </p>
*
* @author yww
* @since 2023/4/20
*/
@Data
public class OptimizationReq {
private String fileContent;
private String filename;
}
package com.zq.imgproc.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 图片优化结果类
* </p>
*
* @author chenhao
* @since 2023/4/20
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class OptimizationVO {
@ApiModelProperty("base64图片")
private String fileContent;
@ApiModelProperty("文件名称")
private String filename;
@ApiModelProperty("是否经过亮度调整")
private boolean isBrightness;
@ApiModelProperty("图片原始平均亮度值")
private double originalBrightnessVal;
@ApiModelProperty("图片修正后的平均亮度值")
private double brightnessVal;
@ApiModelProperty("是否经过清晰度调整")
private boolean isClarity;
@ApiModelProperty("图片原始清晰度值")
private double originalClarityVal;
@ApiModelProperty("图片清晰度调整过后的清晰度值")
private double clarityVal;
@ApiModelProperty("是否经过黑边处理")
private boolean isRemoveBlack;
@ApiModelProperty("图片纠偏的角度,纠偏出错返回-1")
private double deskewAngel;
@ApiModelProperty("图片是否经过弯曲矫正")
private boolean isCorrect;
}
package com.zq.imgproc.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* <p>
* 图片旋转
* </p>
*
* @author chenhao
* @since 2023/3/10 17:32
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class RotateReq {
/**
* 旋转角度
*/
Integer degree;
List<ImgVO> pathList;
}
package com.zq.imgproc.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 亮度和清晰度指标设置
* </p>
*
* @author chenhao
* @since 2023/3/10 17:03
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class SettingVO {
@ApiModelProperty("亮度值起始范围")
private double startValue;
@ApiModelProperty("亮度值结束范围")
private double endValue;
@ApiModelProperty("图片的清晰度指标,值越大越清晰")
private double clarity;
}
......@@ -78,7 +78,17 @@ mybatis-plus:
ip:
local-parsing: true
#imgconfig:
# opencv: /opt/services/tianjin-backend/lib/opencv_java460.so
# deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew
# deskewpy: /opt/services/tianjin-backend/lib/correct.py
#imgconfig:
# opencv: /opt/tianjin/lib/opencv_java460.so
# deskew: /opt/tianjin/lib/Deskew/Bin/deskew
# deskewpy: /opt/tianjin/lib/correct.py
imgconfig:
opencv: /opt/services/tianjin-backend/lib/opencv_java460.so
deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew
deskewpy: /opt/services/tianjin-backend/lib/correct.py
opencv: D:/project/image-backend/config/lib/opencv_java460.dll
deskew: C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
deskewpy: D:/project/imgproc/lib/correct.py
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zq.imgproc.dao.ImageDetectionDao">
<select id="queryPageList" resultType="com.zq.imgproc.entity.ImageDetection">
SELECT *
FROM image_detection AS detection
<where>
<if test="req.nickName != null and req.nickName != ''">
AND detection.nick_name like concat('%', #{req.nickName}, '%')
</if>
<if test="req.imgName != null and req.imgName != ''">
AND detection.img_name like concat('%', #{req.imgName}, '%')
</if>
<if test="req.fileName != null and req.fileName != ''">
AND detection.file_name like concat('%', #{req.fileName}, '%')
</if>
<if test="req.startTime != null and req.startTime != ''">
AND detection.upload_time &gt;= #{req.startTime}
</if>
<if test="req.endTime != null and req.endTime != ''">
AND detection.upload_time &lt;= #{req.endTime}
</if>
</where>
</select>
<insert id="insertBatch" parameterType="java.util.List">
insert into image_detection (user_id, nick_name, img_name, file_name, file_url,
size, width_resolution, height_resolution, dpi, clarity, bend, brightness,
deskew_angel, black, qualified, upload_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.userId}, #{item.nickName}, #{item.imgName}, #{item.fileName},
#{item.fileUrl}, #{item.size}, #{item.widthResolution}, #{item.heightResolution},
#{item.dpi}, #{item.clarity}, #{item.bend}, #{item.brightness},
#{item.deskewAngel}, #{item.black}, #{item.qualified}, #{item.uploadTime})
</foreach>
</insert>
</mapper>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment