Commit 003b7634 by 陈皓

init

parent cae76626
...@@ -56,4 +56,19 @@ public class AccessController { ...@@ -56,4 +56,19 @@ public class AccessController {
.body(new FileSystemResource(file)); .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: ...@@ -33,6 +33,10 @@ spring:
uri: lb://FILE-SERVER uri: lb://FILE-SERVER
predicates: predicates:
- Path=/file/** - Path=/file/**
- id: data
uri: lb://FILE-SERVER
predicates:
- Path=/data/**
- id: user - id: user
uri: lb://USER-SERVER uri: lb://USER-SERVER
predicates: predicates:
......
...@@ -81,6 +81,86 @@ ...@@ -81,6 +81,86 @@
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </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> </dependencies>
<build> <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.server;
import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import com.alibaba.excel.EasyExcel;
import com.zq.common.utils.UuidUtils;
import com.zq.imgproc.utils.*;
import com.zq.imgproc.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>
* 图片检测服务
* </p>
*
* @author chenhao
* @since 2022/10/24 15:02
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ImgProcService {
@Value("${imgconfig.deskew}")
String deskewUrl;
@Value("${imgconfig.deskewpy}")
String deskewpyUrl;
public List<ImgVO> detection(List<ImgVO> pathList) throws Exception {
List<ImgVO> res = new ArrayList<>(pathList.size());
for (ImgVO one : pathList) {
DetectionVO vo = new DetectionVO();
String path = one.getUrl();
Mat image = Imgcodecs.imread(path);
// 检测图片的分辨率
vo.setWidthResolution(image.width());
vo.setHeightResolution(image.height());
// 检测图片的DPI
vo.setDpi(ImageUtil.getDpi(FileUtil.file(path)));
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(image));
vo.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
// 检测图片的亮度
vo.setBrightness(Convert.toInt(ImageUtil.brightness(image)));
// 检测图片倾斜角度
vo.setAngle(Deskew.getDeskewAngle(image));
// 检测图片的黑边
vo.setBlack(ImageUtil.blackDetection(image));
// 图片弯曲检测
BendResult bendResult = BendUtil.getBendResult(path);
vo.setBend(bendResult.getConfidence());
one.setDetectionRes(vo);
res.add(one);
}
return res;
}
public DetectionResVo detection(MultipartFile file) throws Exception {
DetectionResVo res = new DetectionResVo();
Mat image = ImageUtil.getMat(file);
// 检测图片的分辨率
res.setWidthResolution(image.width());
res.setHeightResolution(image.height());
// 检测图片的DPI
res.setDpi(ImageUtil.getDpi(file));
// 检测图片清晰度
res.setClarity(clarityDetection(image) > 10);
// 检测图片的亮度
res.setBrightness(brightness(brightnessDetection(image)));
// 检测图片倾斜角度
res.setAngle(Deskew.getDeskewAngle(image));
// 检测图片的黑边
res.setBlack(ImageUtil.blackDetection(image));
return res;
}
public DetectionVO detection2(OptimizationReq req) throws Exception {
// 1. 临时保存图片
String ext = FileUtil.extName(req.getFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String filePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath);
if ("heic".equals(ext)) {
BufferedImage bufferedImage = ImageIO.read(new File(filePath));
ext = "jpg";
filePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
ImageIO.write(bufferedImage, "jpg", new File(filePath));
}
DetectionVO res = new DetectionVO();
Mat image = Imgcodecs.imread(filePath);
// 检测图片的分辨率
res.setWidthResolution(image.width());
res.setHeightResolution(image.height());
// 检测图片的DPI
res.setDpi(ImageUtil.getDpi(FileUtil.file(filePath)));
// 检测图片清晰度
res.setClarity(ImageUtil.imageSharpnessDetector(image));
// 检测图片的亮度
res.setBrightness(ImageUtil.brightness(image));
// 检测图片倾斜角度
res.setAngle(Deskew.getDeskewAngle(image));
// 检测图片的黑边
res.setBlack(ImageUtil.blackDetection(image));
// 图片弯曲检测
BendResult bendResult = BendUtil.getBendResult(filePath);
res.setBend(bendResult.getConfidence());
image.release();
FileUtil.del(filePath);
return res;
}
/**
* 图片亮度检测
* cast为计算出的偏差值,小于1表示比较正常,大于1表示存在亮度异常;当cast异常时,da大于0表示过亮,da小于0表示过暗
*
* @param src 图片矩阵
* @return [cast, da]
*/
private double[] brightnessDetection(Mat src) {
// 灰度化,转为灰度图
Mat gray = src.clone();
// 获取原图的列数,RGB图像为3列
if (3 == src.channels()) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
} else {
log.error("该图片不是RGB图像,灰度化失败!");
return null;
}
double a = 0;
int[] hist = new int[256];
for (int i = 0; i < gray.rows(); i++) {
for (int j = 0; j < gray.cols(); j++) {
a += gray.get(i, j)[0] - 128;
int index = (int) gray.get(i, j)[0];
hist[index]++;
}
}
double da = a / (gray.rows() * gray.cols());
double ma = 0;
for (int i = 0; i < hist.length; i++) {
ma += Math.abs(i - 128 - da) * hist[i];
}
ma = ma / (gray.rows() * gray.cols());
double cast = Math.abs(da) / Math.abs(ma);
return new double[]{cast, da};
}
/**
* 用默认标准判断图片亮度情况
*
* @return 图片的亮度情况,1表示亮度正常,2表示过亮,3表示过暗
*/
private Integer brightness(double[] arr) {
if (arr == null) {
return -1;
}
double cast = arr[0];
double da = arr[1];
if (cast < 1) {
return 1;
} else {
if (da > 0) {
return 2;
} else {
return 3;
}
}
}
/**
* 图像清晰度,求出灰度图的平均灰度和标准差
* 平均值和方差越大,代表图片越清晰
*
* @param src 图片矩阵
* @return [mean, std] 灰度图的平均灰度和标准差
*/
private double clarityDetection(Mat src) {
Mat gray = src.clone();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
// 拉普拉斯平滑处理
// 阈值太低会导致正常图片被误断为模糊图片,阈值太高会导致模糊图片被误判为正常图片
Mat laplacImg = new Mat();
Imgproc.Laplacian(gray, laplacImg, CvType.CV_64F);
// 标准差,stddev.get(0,0)[0]
MatOfDouble stddev = new MatOfDouble();
// 平均灰度值,mean.get(0,0)[0]
MatOfDouble mean = new MatOfDouble();
Core.meanStdDev(laplacImg, mean, stddev);
double[] stds = stddev.get(0, 0);
return stds[0];
}
/**
* 灰度化图片后进行canny边缘检测
*
* @param src 图片矩阵
* @return 边缘检测之后的图片矩阵
*/
private Mat canny(Mat src) {
// 灰度化
Mat gray = src.clone();
if (src.channels() == 4 || src.channels() == 3) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
} else if (src.channels() == 2) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR5652GRAY);
} else {
gray = src;
}
Mat mat = gray.clone();
Imgproc.Canny(gray, mat, 60, 200);
return mat;
}
/**
* 通过矫正工具获取的倾斜角度
*
* @param imageUrl 图片路径
* @return 图片倾斜角度
*/
private double getAngle2(String imageUrl) {
// 纠正文件的保存路径
String ext = FileUtil.extName(imageUrl);
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
String deskewRes = ImageCorrectionUtil.deskew(imageUrl, savePath, deskewUrl, true);
if (deskewRes.contains("Error")) {
return 0.0d;
} else {
Pattern pattern = Pattern.compile("Skew angle found \\[deg\\]: (-?\\d+(?:\\.\\d+)?)");
Matcher matcher = pattern.matcher(deskewRes);
if (matcher.find()) {
return Double.parseDouble(matcher.group(1));
} else {
return 0.0d;
}
}
}
/**
* 通过矫正工具获取的倾斜角度
*
* @param imageUrl 图片路径
* @return 图片倾斜角度
*/
private Double getAngle3(String imageUrl) {
// 纠正文件的保存路径
String ext = FileUtil.extName(imageUrl);
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
String deskewRes = ImageCorrectionUtil.deskew2(imageUrl, savePath, deskewpyUrl);
Double angle = Convert.toDouble(deskewRes);
return angle == null ? getAngle2(imageUrl) : angle;
}
/**
* 求数组众数
*
* @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 int calculateAngle(double x1, double y1, double x2, double y2) {
if (Math.abs(x2 - x1) < 1e-4) {
return 90;
} else if (Math.abs(y2 - y1) < 1e-4) {
return 0;
} else {
double k = -(y2 - y1) / (x2 - x1);
double res = Math.atan(k) * 57.29577;
return Convert.toInt(Math.round(res));
}
}
public List<ImgVO> imageCorrection(List<ImgVO> pathList) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (ImgVO one : pathList) {
String path = one.getUrl();
String ext = FileUtil.extName(one.getFileName());
ImageCorrectionUtil.correctImg(path);
Mat image = Imgcodecs.imread(path);
double angle = Deskew.getDeskewAngle(canny(image));
// 通过倾斜角度旋转图片
ImgUtil.rotate(FileUtil.file(path), Convert.toInt(angle) , FileUtil.file(savePath + index + "." + ext));
one.setUrl(savePath + index + "." + ext);
index++;
}
return pathList;
}
public List removeBlack(List<ImgVO> pathList) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
// 除黑边
for (ImgVO one : pathList) {
String ext = FileUtil.extName(one.getFileName());
RemoveBlackUtil2.remove(one.getUrl(), savePath + index + "." + ext);
one.setUrl(savePath + index + "." + ext);
index++;
}
// 修正
return pathList;
}
public List<ImgVO> deskew(List<ImgVO> pathList) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (ImgVO one : pathList) {
String ext = FileUtil.extName(one.getFileName());
if ("heic".equals(ext)) {
Mat mat = Imgcodecs.imread(one.getUrl());
double angle = Deskew.getDeskewAngle(mat);
ImageUtil.rotateImage(one.getUrl(), savePath + index + "." + ext, angle);
} else {
ImageCorrectionUtil.deskew(one.getUrl(), savePath + index + "." + ext, deskewUrl, false);
}
one.setUrl(savePath + index + "." + ext);
index++;
}
return pathList;
}
public List<ImgVO> rotate(RotateReq req) {
List<ImgVO> pathList = req.getPathList();
Integer degree = req.getDegree();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (ImgVO one : pathList) {
String ext = FileUtil.extName(one.getFileName());
ImageUtil.rotateImage(one.getUrl(), savePath + index + "." + ext, degree);
one.setUrl(savePath + index + "." + ext);
index++;
}
return pathList;
}
public List<ImgVO> gray(List<ImgVO> pathList) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (ImgVO one : pathList) {
String ext = FileUtil.extName(one.getFileName());
ImageUtil.gray(one.getUrl(), savePath + index + "." + ext);
one.setUrl(savePath + index + "." + ext);
index++;
}
return pathList;
}
public List<ImgVO> canny(List<ImgVO> pathList) {
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
int index = 1;
for (ImgVO one : pathList) {
String ext = FileUtil.extName(one.getFileName());
Mat src = Imgcodecs.imread(one.getUrl());
Mat cannyImg = canny(src);
ImageUtil.saveImage(cannyImg, savePath + index + "." + ext);
one.setUrl(savePath + index + "." + ext);
index++;
}
return pathList;
}
public OptimizationVO optimization(OptimizationReq req) throws IOException {
OptimizationVO res = new OptimizationVO();
// 1. 临时保存图片
String ext = FileUtil.extName(req.getFilename());
String filename = req.getFilename();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename;
FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath);
log.info("{}图片进行优化", filePath);
// 处理heic图片
if ("heic".equals(ext)) {
BufferedImage bufferedImage = ImageIO.read(new File(filePath));
ext = "jpg";
filename = FileUtil.mainName(filename) + ".jpg";
filePath = savePath + filename;
ImageIO.write(bufferedImage, "jpg", new File(filePath));
log.info("{}图片从heic转换为jpg", filePath);
}
String newPath = filePath;
// 2. 黑边处理
long start = System.currentTimeMillis();
String blackUrl = savePath + "(1)." + ext;
Mat aMat = Imgcodecs.imread(newPath);
if (ImageUtil.blackDetection(aMat)) {
res.setRemoveBlack(true);
RemoveBlackUtil2.remove(newPath, blackUrl);
newPath = blackUrl;
} else {
res.setRemoveBlack(false);
}
aMat.release();
log.info("黑边处理消耗的时间【{}】", System.currentTimeMillis() - start);
// 3. 执行纠偏
start = System.currentTimeMillis();
String deskerFile = savePath + "(2)." + ext;
Mat bMat = Imgcodecs.imread(newPath);
double skewAngle = Deskew.getDeskewAngle(bMat);
if (Double.compare(skewAngle, 1) < 0 && Double.compare(skewAngle, -1) > 0) {
res.setDeskewAngel(0);
} else {
Mat rotateMat = Deskew.rotate(bMat, skewAngle);
Imgcodecs.imwrite(deskerFile, rotateMat);
newPath = deskerFile;
res.setDeskewAngel(skewAngle);
rotateMat.release();
}
bMat.release();
log.info("纠偏所需时间【{}】", System.currentTimeMillis() - start);
// 4。 图片亮度检测
// 计算图像的平均亮度
start = System.currentTimeMillis();
Mat cMat = Imgcodecs.imread(newPath);
double brightness = ImageUtil.brightness(cMat);
res.setOriginalBrightnessVal(brightness);
// 亮度异常执行亮度矫正
if (brightness < 100 || brightness > 500) {
res.setBrightness(true);
// 计算亮度调整值
double alpha = 300 / brightness;
// 执行亮度调整
Mat grayImage = Imgcodecs.imread(newPath);
Mat adjustedImage = new Mat();
grayImage.convertTo(adjustedImage, -1, alpha, 0);
// 保存调整后的图像
Imgcodecs. imwrite(newPath, adjustedImage);
res.setBrightnessVal(ImageUtil.brightness(newPath));
adjustedImage.release();
grayImage.release();
} else {
res.setBrightness(false);
res.setBrightnessVal(0);
}
cMat.release();
log.info("亮度检测所需时间【{}】", System.currentTimeMillis() - start);
// 5. 图片清晰度检测
start = System.currentTimeMillis();
Mat dMat = Imgcodecs.imread(newPath);
double laplacianScore = ImageUtil.imageSharpnessDetector(dMat);
res.setOriginalClarityVal(laplacianScore);
if (laplacianScore < 500) {
res.setClarity(true);
Mat unsharpMaskingMat = ImageUtil.unsharpMasking(dMat);
// 保存调整后的图像
Imgcodecs.imwrite(newPath, unsharpMaskingMat);
res.setClarityVal(ImageUtil.imageSharpnessDetector(unsharpMaskingMat));
unsharpMaskingMat.release();
} else {
res.setClarity(false);
res.setClarityVal(0);
}
dMat.release();
log.info("图片清晰度所需时间【{}】", System.currentTimeMillis() - start);
res.setFilename(filename);
res.setFileContent(Base64Encoder.encode(FileUtil.readBytes(newPath)));
res.setCorrect(false);
return res;
// 6. 图片弯曲矫正
// String correctUrl = savePath + "(3)." + ext;
// HashMap<String, Object> map = new HashMap<>();
// map.put("file", FileUtil.file(newPath));
// String response = HttpUtil.post("http://ddns.gxmailu.com:18888/api/correct", map);
// JSONObject correctRes = JSONUtil.parseObj(response);
// if (ObjectUtil.isNotNull(correctRes) && correctRes.get("success", Boolean.class)) {
// String base64 = correctRes.get("data", String.class);
// res.setCorrect(true);
// res.setFileContent(base64);
// res.setFilename(filename);
// } else {
// res.setCorrect(false);
// res.setFilename(filename);
// res.setFileContent(Base64Encoder.encode(FileUtil.readBytes(newPath)));
// }
// return res;
}
public Boolean recognizeRed(OptimizationReq req) {
// 暂时保存
String filename = req.getFilename();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename;
FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath);
Mat mat = Imgcodecs.imread(filePath);
// 转为HSV空间
Mat hsv = new Mat();
Imgproc.cvtColor(mat, hsv, Imgproc.COLOR_BGR2HSV);
int nums = 0;
for (int i = 0; i < hsv.rows(); i++) {
for (int j = 0; j < hsv.cols(); j++) {
double[] clone = hsv.get(i, j).clone();
double h = clone[0];
double s = clone[1];
double v = clone[2];
// 红色的hsv范围判断
if ((h > 0 && h < 10) || (h > 156 && h < 180)) {
if (s > 43 && s < 255) {
if (v < 255 && v > 46) {
nums++;
}
}
}
}
}
FileUtil.del(FileUtil.file(filePath));
return nums > 8000;
}
public String removeRed(OptimizationReq req) {
// 暂时保存
String ext = FileUtil.extName(req.getFilename());
String filename = req.getFilename();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename;
FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath);
Mat mat = Imgcodecs.imread(filePath);
List<Mat> mats = new ArrayList<>();
// 分离图片通道
Core.split(mat, mats);
// 获取红色通道的矩阵
Mat red = mats.get(2);
// 二值化
Mat threshRed = new Mat();
Imgproc.threshold(red, threshRed, 120, 255, Imgproc.THRESH_BINARY);
String dst = savePath + "test." + ext;
Imgcodecs.imwrite(dst, threshRed);
return Base64Encoder.encode(FileUtil.readBytes(dst));
}
public ResponseEntity<Resource> getDetection(List<ImgVO> pathList) throws Exception {
if (pathList == null || pathList.isEmpty()) {
return ResponseEntity.badRequest().build();
}
pathList = detection(pathList);
String filePath = "/data/temp/" + UuidUtils.uuidNoDash() + ".xlsx";
EasyExcel.write(filePath, DetectionDTO.class).sheet("图片检测报告").doWrite(getData(pathList));
BufferedInputStream stream = FileUtil.getInputStream(filePath);
if (stream == null) {
return ResponseEntity.notFound().build();
}
String contentDisposition = ContentDisposition
.builder("attachment")
.filename("图片检测报告.xlsx")
.build().toString();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(stream));
}
private List<DetectionDTO> getData(List<ImgVO> pathList) {
List<DetectionDTO> dtos = new ArrayList<>(pathList.size());
for (ImgVO imgVO : pathList) {
DetectionVO detectionRes = imgVO.getDetectionRes();
DetectionDTO dto = new DetectionDTO();
dto.setFilename(imgVO.getFileName());
dto.setResolution(detectionRes.getWidthResolution() + " x " + detectionRes.getHeightResolution());
dto.setDpi(detectionRes.getDpi());
dto.setBrightness(detectionRes.getBrightness());
dto.setClarity(detectionRes.getClarity());
dto.setAngle(detectionRes.getAngle());
dto.setBend(detectionRes.getBend());
dto.setBlack(detectionRes.getBlack() ? "是" : "否");
dtos.add(dto);
}
return dtos;
}
}
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 cn.hutool.core.convert.Convert;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.zq.common.exception.BusinessException;
import com.zq.common.utils.UuidUtils;
import com.zq.imgproc.vo.ImgVO;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.io.FileUtils;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.web.multipart.MultipartFile;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* <p>
* 图像处理工具类
* </p>
*
* @author chenhao
* @since 2023/3/10 15:06
*/
public class ImageUtil {
/**
* 去黑边"全黑"阈值
*/
private static final Integer BLACK_VALUE = 40;
/**
* 使用Graphics2D进行旋转图片
*
* @param image 需要旋转的图片
* @param degree 旋转的角度
* @return 旋转后的图片
*/
public static BufferedImage rotateImage(BufferedImage image, double degree) {
int width = image.getWidth();
int height = image.getHeight();
int type = image.getType();
BufferedImage res = new BufferedImage(width, height, type);
Graphics2D graphics = res.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// 设置图片底色
graphics.setBackground(Color.BLACK);
// 填充底图
graphics.fillRect(0, 0, width, height);
// 按中心点旋转图片
graphics.rotate(Math.toRadians(degree), width >> 1, height >> 1);
graphics.drawImage(image, 0, 0, null);
// 关闭Graphics2D
graphics.dispose();
return res;
}
/**
* 使用Graphics2D进行旋转图片
*
* @param src 输入路径
* @param dst 输出路径
* @param degree 旋转角度
*/
public static void rotateImage(String src, String dst, double degree) {
BufferedImage image = ImgUtil.read(src);
ImgUtil.write(rotateImage(image, degree), FileUtil.file(dst));
}
/**
* 将图片转换为BufferedImage.TYPE_3BYTE_BGR
*
* @param image 图片流
* @return 图片流
*/
public static BufferedImage convert(BufferedImage image) {
BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_3BYTE_BGR);
convertedImage.getGraphics().drawImage(image, 0, 0, null);
return convertedImage;
}
/**
* 根据文件转换为mat对象
*
* @param file 文件
* @return Mat
*/
public static Mat getMat(MultipartFile file) throws IOException, ImageReadException {
ImageInfo imageInfo = Imaging.getImageInfo(file.getInputStream(), file.getOriginalFilename());
InputStream inputStream = file.getInputStream();
BufferedImage image = ImgUtil.read(inputStream);
image = ImageUtil.convert(image);
DataBuffer data = image.getRaster().getDataBuffer();
byte[] bytes = ((DataBufferByte) data).getData();
Mat mat = new Mat(imageInfo.getHeight(), imageInfo.getWidth(), CvType.CV_8UC3);
mat.put(0, 0, bytes);
return mat;
}
/**
* 保存图片到指定位置
*
* @param mat 图片文件
* @param filePath 文件路径
*/
public static void saveImage(Mat mat, String filePath) {
MatOfByte matOfByte = new MatOfByte();
Imgcodecs.imencode(".png", mat, matOfByte);
byte[] byteArray = matOfByte.toArray();
FileUtil.writeBytes(byteArray, filePath);
}
/**
* 像素求和
*
* @param mat mat
* @return sum
*/
public static int sum(Mat mat) {
int sum = 0;
for (int row = 0; row < mat.height(); row++) {
for (int col = 0; col < mat.width(); col++) {
sum += mat.get(row, col)[0];
}
}
return sum;
}
/**
* 图片灰度化
*
* @param src 图片地址
* @param dst 灰度化图片保存地址
*/
public static void gray(String src, String dst) {
// 灰度化
Mat mat = Imgcodecs.imread(src);
Mat gray = mat.clone();
// if (mat.channels() == 4 || mat.channels() == 3) {
// Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR2GRAY);
// } else if (mat.channels() == 2) {
// Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR5652GRAY);
// } else {
// gray = mat;
// }
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR2GRAY);
saveImage(gray, dst);
gray.release();
mat.release();
}
/**
* 边缘检测
*
* @param src 图片地址
* @param dst 边缘检测图片保存地址
*/
public static void canny(String src, String dst) {
Mat mat = Imgcodecs.imread(src);
Mat cannyImg = canny(mat);
ImageUtil.saveImage(cannyImg, dst);
}
/**
* 灰度化图片后进行canny边缘检测
*
* @param src 图片矩阵
* @return 边缘检测之后的图片矩阵
*/
private static Mat canny(Mat src) {
// 灰度化
Mat gray = src.clone();
if (src.channels() == 4 || src.channels() == 3) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
} else if (src.channels() == 2) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR5652GRAY);
} else {
gray = src;
}
Mat mat = gray.clone();
Imgproc.Canny(gray, mat, 60, 200);
return mat;
}
/**
* 灰度化图片后进行canny边缘检测
*/
public static Mat canny(Mat src, int threshold1, int threshold2, int apertureSize) {
// 灰度化
Mat gray = new Mat();
if (src.channels() == 4 || src.channels() == 3) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
} else if (src.channels() == 2) {
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR5652GRAY);
} else {
gray = src;
}
Mat mat = new Mat();
Imgproc.Canny(gray, mat, threshold1, threshold2, apertureSize);
gray.release();
return mat;
}
/**
* 保存临时图片
*
* @param file 图片文件
* @return 图片保存地址
*/
public static String saveTempFile(MultipartFile file) {
try {
String originalFilename = file.getOriginalFilename();
//取文件扩展名
String ext = Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf(".") + 1);
//生成新文件名
String name = UuidUtils.uuidNoDash() + "." + ext;
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
File dest = new File("/data/temp/" + yyyyMMdd + name);
FileUtils.writeByteArrayToFile(dest, file.getBytes());
return "/data/temp" + yyyyMMdd + name;
} catch (IOException e) {
throw new BusinessException("文件保存失败");
}
}
/**
* 图片弯曲矫正
*
* @param pathList 图片列表
* @return 弯曲矫正之后的图片列表
*/
public static List correct(List<ImgVO> pathList) {
String url = "http://ddns.gxmailu.com:18888/imgproc/correct";
String body = JSONUtil.toJsonStr(pathList);
String res = HttpUtil.post(url, body);
JSONObject jsonObject = JSONUtil.parseObj(res);
if (jsonObject.getByPath("success", Boolean.class)) {
return jsonObject.getByPath("data", ArrayList.class);
} else {
return pathList;
}
}
/**
* 计算图片清晰度
*
* @param path 图片路径
* @return 图片清晰度
*/
public static double imageSharpnessDetector(String path) {
Mat image = Imgcodecs.imread(path);
Mat grayImage = new Mat();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// 计算拉普拉斯
Mat laplacian = new Mat();
Imgproc.Laplacian(grayImage, laplacian, CvType.CV_64F);
// 计算清晰度
MatOfDouble mean = new MatOfDouble();
MatOfDouble stdDev = new MatOfDouble();
Core.meanStdDev(laplacian, mean, stdDev);
laplacian.release();
// 计算拉普拉斯分数
return Math.pow(stdDev.get(0,0)[0], 2.0);
}
/**
* Tenengrad梯度方法计算清晰度
* Tenengrad梯度方法利用Sobel算子分别计算水平和垂直方向的梯度,同一场景下梯度值越高,图像越清晰。
*
* @param image 图片矩阵
* @return 图片清晰度
*/
public static double tenengrad(Mat image) {
// 图片灰度化
Mat grayImage = image.clone();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// Sobel算子
Mat sobelImage = new Mat();
Imgproc.Sobel(grayImage, sobelImage, CvType.CV_16U, 1, 1);
Scalar mean = Core.mean(sobelImage);
double meanValue = mean.val[0];
// 释放内存
sobelImage.release();
grayImage.release();
return meanValue;
}
/**
* Laplacian方法计算清晰度
*
* @param image 图片矩阵
* @return 图片清晰度
*/
public static double laplacian(Mat image) {
// 图片灰度化
Mat grayImage = image.clone();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// Laplacian算子
Mat laplacian = new Mat();
Imgproc.Laplacian(grayImage, laplacian, CvType.CV_16U);
Scalar mean = Core.mean(laplacian);
double meanValue = mean.val[0];
// 释放内存
laplacian.release();
grayImage.release();
return meanValue;
}
/**
* 通过灰度方差获取图片清晰度
* 对焦清晰的图像相比对焦模糊的图像,它的数据之间的灰度差异应该更大,即它的方差应该较大,可以通过图像灰度数据的方差来衡量图像的清晰度,方差越大,表示清晰度越好。
*
* @param image 图片矩阵
* @return 图片清晰度
*/
public static double variance(Mat image) {
// 图片灰度化
Mat grayImage = image.clone();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// 计算灰度图像的标准差
MatOfDouble mean = new MatOfDouble();
MatOfDouble stdDev = new MatOfDouble();
Core.meanStdDev(grayImage, mean, stdDev);
double meanValue = stdDev.get(0, 0)[0];
// 释放内存
mean.release();
stdDev.release();
grayImage.release();
return meanValue;
}
/**
* 计算图片清晰度
*
* @param image 图片矩阵
* @return 图片清晰度
*/
public static double imageSharpnessDetector(Mat image) {
// 图片灰度化
Mat grayImage = image.clone();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// 拉普拉斯计算
Mat laplacian = new Mat();
Imgproc.Laplacian(grayImage, laplacian, CvType.CV_64F);
// 计算清晰度
MatOfDouble mean = new MatOfDouble();
MatOfDouble stdDev = new MatOfDouble();
Core.meanStdDev(laplacian, mean, stdDev);
// 释放内存
laplacian.release();
grayImage.release();
// 计算拉普拉斯分数
return Math.pow(stdDev.get(0,0)[0], 2.0);
}
/**
* 计算图片平均亮度,范围是0到255
*
* @param path 图片路径
* @return 图片平均亮度
*/
public static double brightness(String path) {
Mat grayImage = Imgcodecs.imread(path, Imgcodecs.IMREAD_GRAYSCALE);
// 计算图像的平均亮度
Scalar mean = Core.mean(grayImage);
grayImage.release();
return mean.val[0];
}
/**
* 计算图片平均亮度
*
* @param image 图片路径
* @return 图片平均亮度
*/
public static double brightness(Mat image) {
// 图片灰度化
Mat grayImage = image.clone();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
// 计算图像的平均亮度
Scalar mean = Core.mean(grayImage);
// 释放内存
grayImage.release();
return mean.val[0];
}
/**
* 执行高斯模糊, UnsharpMasking
*
* @param path 图片路径
* @return 执行后的结果
*/
public static Mat unsharpMasking(String path) {
Mat image = Imgcodecs.imread(path);
// 高斯模糊
Mat blurred = new Mat();
Imgproc.GaussianBlur(image, blurred, new Size(3, 3), 0);
// UnsharpMasking
Mat unsharpMasked = new Mat();
Core.addWeighted(image, 1.5, blurred, -0.5, 0, unsharpMasked);
image.release();
blurred.release();
return unsharpMasked;
}
/**
* 执行高斯模糊, UnsharpMasking
*
* @param image 图片路径
* @return 执行后的结果
*/
public static Mat unsharpMasking(Mat image) {
// 高斯模糊
Mat blurred = new Mat();
Imgproc.GaussianBlur(image, blurred, new Size(3, 3), 0);
// UnsharpMasking
Mat unsharpMasked = new Mat();
Core.addWeighted(image, 1.5, blurred, -0.5, 0, unsharpMasked);
blurred.release();
return unsharpMasked;
}
/**
* 黑边检测
*
* @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 < 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 int blackDetection2(Mat src) {
int blackValue = 40;
int width = src.width();
int height = src.height();
int rows = 0;
int cols = 0;
for (int row = 0; row < height; row++) {
Scalar mean = Core.mean(src.row(row));
if (Convert.toInt(mean.val[0]) < blackValue) {
rows++;
}
}
for (int col = 0; col < width; col++) {
Scalar mean = Core.mean(src.col(col));
if (Convert.toInt(mean.val[0]) < blackValue) {
cols++;
}
}
return Math.max(rows, cols);
}
/**
* 获取图片DPI
*
* @param file 图片文件
* @return [水平DPI,垂直DPI]
*/
public static Integer getDpi(MultipartFile file) throws Exception {
int res;
ImageInfo imageInfo = Imaging.getImageInfo(file.getInputStream(), file.getOriginalFilename());
res = imageInfo.getPhysicalWidthDpi();
if (res == -1) {
Metadata metadata = ImageMetadataReader.readMetadata(file.getInputStream());
for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) {
if ("X Resolution".equals(tag.getTagName())) {
res = Convert.toInt(tag.getDescription());
}
}
}
}
return res;
}
/**
* 获取图片DPI
*
* @param file 图片文件
* @return [水平DPI,垂直DPI]
*/
public 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;
}
}
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: ...@@ -78,7 +78,17 @@ mybatis-plus:
ip: ip:
local-parsing: true 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: imgconfig:
opencv: /opt/services/tianjin-backend/lib/opencv_java460.so opencv: D:/project/image-backend/config/lib/opencv_java460.dll
deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew deskew: C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
deskewpy: /opt/services/tianjin-backend/lib/correct.py 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