Commit a0252026 by 陈皓

init

parent 7f1d964c
...@@ -9,7 +9,7 @@ spring: ...@@ -9,7 +9,7 @@ spring:
enabled: true enabled: true
service-id: CONFIG-SERVER service-id: CONFIG-SERVER
username: admin username: admin
password: 123456 password: GXfy2022
eureka: eureka:
instance: instance:
......
...@@ -37,4 +37,8 @@ spring: ...@@ -37,4 +37,8 @@ spring:
uri: lb://USER-SERVER uri: lb://USER-SERVER
predicates: predicates:
- Path=/user/** - Path=/user/**
- id: imgproc
uri: lb://IMGPROC-SERVER
predicates:
- Path=/imgproc/**
...@@ -9,7 +9,7 @@ spring: ...@@ -9,7 +9,7 @@ spring:
enabled: true enabled: true
service-id: CONFIG-SERVER service-id: CONFIG-SERVER
username: admin username: admin
password: 123456 password: GXfy2022
eureka: eureka:
instance: instance:
......
...@@ -2,30 +2,42 @@ ...@@ -2,30 +2,42 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.zq</groupId> <groupId>com.zq</groupId>
<artifactId>image-backend</artifactId> <artifactId>image-backend</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imgproc-server</artifactId> <artifactId>imgproc-server</artifactId>
<version>1.0.0</version>
<name>imgproc-server</name>
<description>imgproc-server</description>
<properties> <properties>
<java.version>1.8</java.version> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.zq</groupId>
<artifactId>img-common-utils</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.zq</groupId>
<artifactId>logging-server</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> </dependency>
<!--Spring boot 安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 连接配置中心 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId> <artifactId>spring-cloud-starter-config</artifactId>
...@@ -41,86 +53,33 @@ ...@@ -41,86 +53,33 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- 远程调用cloud feign -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>commons-io</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>
<version>1.3.2</version>
</dependency>
<!-- OpenCv(Linux) -->
<!-- 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>
<!--Spring boot Redis-->
<dependency> <dependency>
<groupId>org.tukaani</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>xz</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
<version>1.9</version>
</dependency> </dependency>
<!--Mybatis plus-->
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>com.baomidou</groupId>
<artifactId>hutool-all</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>5.8.19</version> <version>${mybatis.plus.version}</version>
</dependency> </dependency>
<!-- druid数据源驱动 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
<version>3.3.2</version> <version>${alibaba.druid.version}</version>
</dependency>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.18.0</version>
</dependency> </dependency>
<!-- mysql -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>mysql</groupId>
<artifactId>lombok</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>1.18.28</version> <scope>runtime</scope>
</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> </dependency>
</dependencies> </dependencies>
...@@ -129,16 +88,22 @@ ...@@ -129,16 +88,22 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
<!-- 使用@profiles.active@需要添加以下内容 --> <!-- 使用@profiles.active@需要添加以下内容 -->
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<excludes>
<exclude>**/*.jar</exclude>
</excludes>
<!--开启过滤,用指定的参数替换directory下的文件中的参数--> <!--开启过滤,用指定的参数替换directory下的文件中的参数-->
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
</resources> </resources>
</build> </build>
</project>
</project>
\ No newline at end of file
package com.zq.imgproc; package com.zq.imgproc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/** /**
* @author wilmiam * @author chenhao
* @since 2022/10/13 11:11 * @since 2022/10/13 11:11
*/ */
@EnableFeignClients
@MapperScan({"com.zq.imgproc.dao", "com.zq.logging.mapper"})
@EnableDiscoveryClient @EnableDiscoveryClient
@SpringBootApplication @SpringBootApplication(scanBasePackages = {"com.zq.imgproc", "com.zq.logging", "com.zq.common.config"})
public class ImgprocApplication { public class ImgprocApplication {
public static void main(String[] args) { public static void main(String[] args) {
......
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 wilmiam
* @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 加载成功...");
}
}
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.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("测试")
@PostMapping("/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 cn.hutool.core.util.URLUtil;
import com.zq.imgproc.utils.AssertUtils;
import io.swagger.annotations.Api;
import org.springframework.core.io.FileSystemResource;
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.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
/**
* <p>
*
* </p>
*
* @author zq
* @since 2023/11/15
*/
@Api(tags = "访问文件")
@RestController
@CrossOrigin
@RequestMapping("/")
public class FileController {
@GetMapping(value = "/file/**")
public ResponseEntity<Resource> archive(HttpServletRequest request) {
String requestURI = request.getRequestURI();
requestURI = URLUtil.decode(requestURI);
File file = new File(requestURI);
AssertUtils.isTrue(file.exists(), "访问文件不存在");
String contentDisposition = ContentDisposition
.builder("attachment")
.filename(requestURI)
.build().toString();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new FileSystemResource(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.imgproc.constant.Threshold;
import com.zq.imgproc.server.ImgProcService;
import com.zq.imgproc.utils.AssertUtils;
import com.zq.imgproc.utils.DecompressUtil;
import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.utils.ResultVo;
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.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 图片检测接口
* </p>
*
* @author chenhao
* @since 2022/10/24 14:58
*/
@Api(tags = "图片检测接口")
@RequestMapping("/imgproc")
@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("文件解压")
@PostMapping ("/decompress")
public ResultVo<?> decompress(@RequestPart MultipartFile[] file) throws Exception {
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));
} else {
String url = ImageUtil.saveTempFile(one);
imgs.add(ImgVO.builder().fileName(one.getOriginalFilename()).url(url).build());
}
}
return ResultVo.success(imgs);
}
@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("获取配置")
@GetMapping("/getSetting")
public ResultVo<?> getSetting() {
Double temp1 = cache.getIfPresent(Threshold.START_VALUE.getKey());
Double temp2 = cache.getIfPresent(Threshold.END_VALUE.getKey());
Double temp3 = cache.getIfPresent(Threshold.CLARITY.getKey());
double start = temp1 == null ? Threshold.START_VALUE.getValue() : temp1;
double end = temp2 == null ? Threshold.END_VALUE.getValue() : temp2;
double clarity = temp3 == null ? Threshold.CLARITY.getValue() : temp3;
return ResultVo.success(SettingVO.builder().startValue(start).endValue(end).clarity(clarity).build());
}
@ApiOperation("设置配置")
@PostMapping("/setSetting")
public ResultVo<?> setSetting(@RequestBody SettingVO setting) {
if (setting == null) {
return ResultVo.fail("配置不能为空!");
}
cache.put(Threshold.START_VALUE.getKey(), setting.getStartValue());
cache.put(Threshold.END_VALUE.getKey(), setting.getEndValue());
cache.put(Threshold.CLARITY.getKey(), setting.getClarity());
return ResultVo.success();
}
@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.exception;
/**
* 业务错误
*
* @author wilmiam
* @since 2021-07-09 17:58
*/
public class BusinessException extends RuntimeException {
private int code = 400;
public BusinessException(String message) {
super(message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public int getCode() {
return code;
}
}
\ No newline at end of file
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.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.util.StrUtil;
import com.zq.imgproc.exception.BusinessException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/**
* 断言验证帮助类
*
* @author wilmiam
* @since 2018-04-03
*/
@SuppressWarnings("all")
public final class AssertUtils {
private static final String[] IMG_EXTS = {"png", "jpg", "jpeg"};
/**
* Don't let anyone instantiate this class
*/
private AssertUtils() {
}
/**
* 判断给定的文件名后缀是否为图片
*
* @param ext 文件名后缀, 不带点
* @param errMsg 错误信息
*/
public static void isImgExt(String ext, String errMsg) {
isImgExt(ext, 400, errMsg);
}
/**
* 判断给定的文件名后缀是否为图片
*
* @param ext 文件名后缀, 不带点
* @param errCode 断言失败的错误代码
* @param errMsg 错误信息
*/
public static void isImgExt(String ext, int errCode, String errMsg) {
if (StrUtil.isBlank(ext) || Arrays.stream(IMG_EXTS).noneMatch(img -> img.equalsIgnoreCase(ext))) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* UnifiedExceptionHandler
* 判断一个布尔表达式, 若表达式为{@code true}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notTrue(boolean expression, String message) throws BusinessException {
notTrue(expression, 400, message);
}
/**
* 判断一个布尔表达式, 若表达式为{@code true}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param errCode 断言失败时的错误代码
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notTrue(boolean expression, int errCode, String message) throws BusinessException {
if (expression) {
throw new BusinessException(errCode, message);
}
}
/**
* 判断一个布尔表达式, 若表达式为{@code false}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void isTrue(boolean expression, String message) throws BusinessException {
isTrue(expression, 400, message);
}
/**
* 判断一个布尔表达式, 若表达式为{@code false}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param errCode 断言失败时的错误代码
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void isTrue(boolean expression, int errCode, String message) throws BusinessException {
if (!expression) {
throw new BusinessException(errCode, message);
}
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @throws BusinessException
*/
public static void notNull(Object object) throws BusinessException {
notNull(object, "不能处理空对象");
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notNull(Object object, String message) throws BusinessException {
notNull(object, 400, message);
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @param errCode 断言失败时的错误代码
* @param errMsg 断言失败时的错误信息
* @throws BusinessException
*/
public static void notNull(Object object, int errCode, String errMsg) throws BusinessException {
if (object == null) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @throws BusinessException
*/
public static void hasText(String text) throws BusinessException {
hasText(text, 400, "参数不能为空字符串");
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void hasText(String text, String message) throws BusinessException {
hasText(text, 400, message);
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void hasText(String text, int errCode, String errMsg) throws BusinessException {
if (StrUtil.isBlank(text)) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果数组为{@code null}或长度为0, 则抛出异常
*
* @param array 要进行检查的数组
* @param message 断言失败时的错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void notEmpty(T[] array, String message) throws BusinessException {
notEmpty(array, 400, message);
}
/**
* 如果数组为{@code null}或长度为0, 则抛出异常
*
* @param array 要进行检查的数组
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void notEmpty(T[] array, int errCode, String errMsg) throws BusinessException {
if (array == null || array.length == 0) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果数组里包含有{@code null}的元素, 则抛出异常. 注意: 若数组本身为{@code null}则不会进行处理, 直接返回
*
* @param array 要进行检查的数组
* @param message 断言失败时的错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void noNullElements(T[] array, String message) throws BusinessException {
noNullElements(array, 400, message);
}
/**
* 如果数组里包含有{@code null}的元素, 则抛出异常. 注意: 若数组本身为{@code null}则不会进行处理, 直接返回
*
* @param array 要进行检查的数组
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void noNullElements(T[] array, int errCode, String errMsg) throws BusinessException {
if (array != null) {
for (T element : array) {
if (element == null) {
throw new BusinessException(errCode, errMsg);
}
}
}
}
/**
* 如果集合为{@code null},或者不包含任何元素,则抛出异常
*
* @param collection 要进行检查的集合
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notEmpty(Collection<?> collection, String message) throws BusinessException {
notEmpty(collection, 400, message);
}
/**
* 如果集合为{@code null},或者不包含任何元素,则抛出异常
*
* @param collection 要进行检查的集合
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void notEmpty(Collection<?> collection, int errCode, String errMsg) throws BusinessException {
if (collection == null || collection.isEmpty()) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果键值对为{@code null},或者不包含任何键值,则抛出异常
*
* @param map 要进行检查的键值对
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notEmpty(Map<?, ?> map, String message) throws BusinessException {
notEmpty(map, 400, message);
}
/**
* 如果键值对为{@code null},或者不包含任何键值,则抛出异常
*
* @param map 要进行检查的键值对
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void notEmpty(Map<?, ?> map, int errCode, String errMsg) throws BusinessException {
if (map == null || map.isEmpty()) {
throw new BusinessException(errCode, errMsg);
}
}
}
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.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.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) 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).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;
import static org.opencv.imgproc.Imgproc.LINE_AA;
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.imgproc.exception.BusinessException;
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;
}
/**
* 获取图片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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* 统一的接口响应信息
*
* @author wilmiam
* @since 2021-07-09 17:45
*/
@ApiModel("API响应消息")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResultVo<T> implements Serializable {
@ApiModelProperty(value = "成功标记", example = "true")
private boolean success;
@ApiModelProperty("错误码")
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
private int errCode;
@ApiModelProperty("错误信息")
private String errMsg;
@ApiModelProperty("响应的数据")
private T data;
public static ResultVo success() {
return success(null);
}
public static <E> ResultVo<E> success(E data) {
ResultVo<E> result = new ResultVo<>();
result.setSuccess(true);
result.setData(data);
return result;
}
public static ResultVo fail(String errMsg) {
return fail(500, errMsg);
}
public static ResultVo fail(int errCode, String errMsg) {
ResultVo result = new ResultVo<>();
result.setSuccess(false);
result.setErrCode(errCode);
result.setErrMsg(errMsg);
return result;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
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.imgproc.exception.BusinessException;
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.utils;
import java.util.UUID;
/**
* Utility class that handles the uuid stuff.
*
* @author wilmiam
* @since 2021-07-09 18:05
*/
public class UuidUtils {
private static final String URN_UUID_REGEX = "^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$";
private static final String NODASH_UUID_REGEX = "^[a-f0-9]{32}$";
/**
* Don't let anyone instantiate this class
*/
private UuidUtils() {
}
/**
* 返回一个随机的带有分隔符"-"的36位UUID字符串
*
* @return
*/
public static String uuid() {
return UUID.randomUUID().toString();
}
/**
* 返回一个随机的没有分隔符"-"的32位UUID字符串
*
* @return
*/
public static String uuidNoDash() {
return uuid().replaceAll("-", "");
}
/**
* 判断一个字符串是否是一个UUID字符串
*
* @param str 要进行判断的字符串
* @return
*/
public static boolean isUuid(String str) {
return ValidateUtil.isNotBlank(str) && str.matches(URN_UUID_REGEX);
}
/**
* 判断一个字符串是否是一个没有分隔符的uuid字符串
*
* @param str 要进行判断的字符串
* @return
*/
public static boolean isNoDashUuid(String str) {
return ValidateUtil.isNotBlank(str) && str.matches(NODASH_UUID_REGEX);
}
}
package com.zq.imgproc.utils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 验证工具类
*
* @author wilmiam
* @since 2021-07-09 18:05
*/
public class ValidateUtil {
/**
* URL验证正则表达式
*/
private static final String URL_REGEX = "^(http|https)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{1,10}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$";
/**
* 安全SQL验证正则表达式
*/
private static final String SAFE_SQL_REGEX = "[-|;|,|\\/|\\(|\\)|\\[|\\]|\\}|\\{|%|@|\\*|!|\\']";
/**
* 身份证验证加权因子数组,将前17位加权因子保存在数组里
*/
private static final int[] IDCARD_WI = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
/**
* 身份证验证校验码数组,这是除以11后,可能产生的11位余数、验证码,也保存
*/
private static final int[] IDCARD_Y = new int[]{1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2};
/**
* 身份证验证,最后一位为X时,校验码所在数组的位置
*/
private static final int IDCARD_MOD_X = 2;
/**
* 身份证验证,当校验码为10时,最后一位必须是X
*/
private static final String IDCARD_LAST_X = "X";
/**
* 身份证校验正则表达式
*/
private static final String IDCARD_REGEX = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$";
/**
* 身份证号码长度
*/
private static final int IDCARD_LENG = 18;
/**
* 单个IP的正则表达式
*/
private static final String SINGLE_IP_REGEX = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
/**
* 以逗号分隔的多个IP的正则表达式
*/
private static final String IP_REGEX = SINGLE_IP_REGEX + "(," + SINGLE_IP_REGEX + ")*";
private static final Class<?>[] NUMBER_TYPES = {
Byte.class, Short.class, Integer.class, Long.class, BigInteger.class, Float.class, Double.class, BigDecimal.class};
/**
* 判断对象是否为空
*
* @param obj 要判断的对象
* @return 判断结果
*/
public static boolean isBlank(final Object obj) {
if (obj == null) {
return true;
}
if (obj instanceof String) {
return "".equals(((String) obj).trim());
}
if (obj instanceof Collection) {
final Collection collection = (Collection) obj;
return collection.isEmpty();
}
return false;
}
/**
* 判断对象是否不为空
*
* @param obj
* @return
*/
public static boolean isNotBlank(final Object obj) {
return !isBlank(obj);
}
/**
* 检查参数是否有空
*
* @param objects 待验证对象,可以是多个
* @return 验证结果
*/
public static boolean hasBlank(final Object... objects) {
for (final Object obj : objects) {
if (isBlank(obj)) {
return true;
}
}
return false;
}
/**
* 判断是否是Base64字符串
*
* @param str Base64字符串
* @return 判断结果
*/
public static boolean isBase64(final String str) {
return isMatch(str, "[A-Za-z0-9\\+\\/\\=]");
}
/**
* 判断是否是Email地址字符串
*
* @param strEmail URL地址字符串
* @return 判断结果
*/
public static boolean isEmail(final String strEmail) {
return isMatch(strEmail, "^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$");
}
/**
* 验证手机号码正确性
*
* @param strMobilePhone 手机号码
* @return
*/
public static boolean isMobilePhoneNo(final String strMobilePhone) {
return isMatch(strMobilePhone, "^0?(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|19[8-9])[0-9]{8}$");
}
/**
* 是否为手机号码
*
* @param phone
* @return
*/
public static boolean isInternationalPhoneNo(final String phone) {
return isMatch(phone, "^[0-9]{4,14}$");
}
/**
* 判断是否为正整数字符串
*
* @param intStr 数字字符串
* @return 判断结果
*/
public static boolean isInt(final String intStr) {
return isMatch(intStr, "^[0-9]*$");
}
/**
* 判断是否为IP字符串
*
* @param ipStr ip字符串
* @return 判断结果
*/
public static boolean isIp(final String ipStr) {
return isMatch(ipStr, "^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$");
}
/**
* 判断字符串是否和正则表达式相匹配,大小写敏感
*
* @param str 字符串
* @param regEx 正则表达式
* @return 判断结果
*/
public static boolean isMatch(final String str, final String regEx) {
return isMatch(str, regEx, false);
}
/**
* 判断字符串是否和正则表达式相匹配
*
* @param str 字符串
* @param regEx 正则表达式
* @param caseInsensetive 是否不区分大小写, true为不区分, false为区分
* @return 判断结果
*/
public static boolean isMatch(final String str, final String regEx, final boolean caseInsensetive) {
if (ValidateUtil.isNotBlank(str) && ValidateUtil.isNotBlank(regEx)) {
Pattern pattern;
if (caseInsensetive) {
pattern = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
} else {
pattern = Pattern.compile(regEx);
}
final Matcher matcher = pattern.matcher(str);
return matcher.find();
}
return false;
}
/**
* 判断是否是数字
*
* @param strNumber 数字字符串
* @return 判断结果
*/
public static boolean isNumber(final String strNumber) {
return isMatch(strNumber, "^\\d+$");
}
/**
* 判断是否是Sql危险字符
*
* @param sqlStr sql字符串
* @return 判断结果
*/
public static boolean isSafeSqlString(final String sqlStr) {
return isMatch(sqlStr, SAFE_SQL_REGEX);
}
/**
* 判断是否是URL地址字符串
*
* @param strUrl URL地址字符串
* @return 判断结果
*/
public static boolean isURL(final String strUrl) {
return isMatch(strUrl, URL_REGEX);
}
/**
* 检查对象类型是不是Number类型
*
* @param type
* @return
*/
public static boolean isNumber(Class<?> type) {
// Class为final类,其equals不能重写,而Class的equals方法是直接继承自Object类
// return (this == obj)
// 所以在比较Class是否相等时,使用equals和使用==是一样的
return Arrays.stream(NUMBER_TYPES).anyMatch(numberType -> numberType.equals(type));
}
/**
* 身份证15位编码规则:dddddd yymmdd xx p
* dddddd:6位地区编码
* yymmdd: 出生年(两位年)月日,如:910215
* xx: 顺序编码,系统产生,无法确定
* p: 性别,奇数为男,偶数为女
* <p>
* 身份证18位编码规则:dddddd yyyymmdd xxx y
* dddddd:6位地区编码
* yyyymmdd: 出生年(四位年)月日,如:19910215
* xxx:顺序编码,系统产生,无法确定,奇数为男,偶数为女
* y: 校验码,该位数值可通过前17位计算获得
* <p>
* 前17位号码加权因子为 Wi = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]
* 验证位 Y = [ 1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2 ]
* 如果验证码恰好是10,为了保证身份证是十八位,那么第十八位将用X来代替
* 校验位计算公式:Y_P = mod( ∑(Ai×Wi),11 )
* i为身份证号码1...17 位; Y_P为校验码Y所在校验码数组位置
*/
public static boolean isIdCardNo(final String idCardNo) {
if (!isMatch(idCardNo, IDCARD_REGEX)) {
return false;
}
if (idCardNo.length() == IDCARD_LENG) {
// 用来保存前17位各自乖以加权因子后的总和
int idCardWiSum = 0;
for (int i = 0; i < IDCARD_WI.length; i++) {
idCardWiSum += Integer.parseInt(idCardNo.substring(i, i + 1)) * IDCARD_WI[i];
}
// 计算出校验码所在数组的位置
int idCardMod = idCardWiSum % 11;
// 得到最后一位身份证号码
String idCardLast = idCardNo.substring(17);
if (idCardMod == IDCARD_MOD_X) {
return IDCARD_LAST_X.equalsIgnoreCase(idCardLast);
} else {
//用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
return String.valueOf(IDCARD_Y[idCardMod]).equals(idCardLast);
}
}
return true;
}
/**
* 判断字符串是否是以逗号分隔的IP字符串。
* <p>
* 逗号必须是英文半角, 单个ip也认为符合要求, 空字符串返回false
* </p>
*
* @param ipStr
* @return
*/
public static boolean isComaSplitIp(String ipStr) {
boolean result = false;
if (ValidateUtil.isNotBlank(ipStr)) {
result = ipStr.matches(IP_REGEX);
}
return result;
}
}
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 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;
}
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;
}
server: server:
port: 8900 port: 9999
# 配置数据源
spring: spring:
application: application:
name: IMGPROC-SERVER name: IMGPROC-SERVER
jackson: jackson:
date-format: yyyy-MM-dd HH:mm:ss date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 time-zone: GMT+8
mvc: freemarker:
format: check-template-location: false
date-time: yyyy-MM-dd HH:mm:ss redis:
servlet: # 数据库索引
multipart: database: 0
max-file-size: 100MB host: ${redis.url}
max-request-size: 150MB port: ${redis.port}
password:
# 连接超时时间
timeout: 5000
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: ${db.cloud.driver-class-name}
username: ${db.cloud.username}
password: ${db.cloud.password}
url: ${db.cloud.url.cloud}
# 初始连接数
initial-size: 5
# 最小连接数
min-idle: 10
# 最大连接数
max-active: 20
# 超时时间(以秒数为单位)
remove-abandoned-timeout: 180
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
# 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
test-while-idle: true
# 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个
test-on-borrow: true
# 是否在归还到池中前进行检验
test-on-return: false
# 检测连接是否有效
validation-query: select 1
# 配置监控统计
webStatFilter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /admin/druid/*
reset-enable: false
allow: ""
filter:
stat:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# mybatis plus 配置
mybatis-plus:
global-config:
db-config:
where-strategy: not_empty
update-strategy: not_empty
# IP 本地解析 # IP 本地解析
ip: ip:
...@@ -22,13 +82,3 @@ imgconfig: ...@@ -22,13 +82,3 @@ imgconfig:
opencv: /opt/services/tianjin-backend/lib/opencv_java460.so opencv: /opt/services/tianjin-backend/lib/opencv_java460.so
deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew
deskewpy: /opt/services/tianjin-backend/lib/correct.py deskewpy: /opt/services/tianjin-backend/lib/correct.py
#imgconfig:
# opencv: /opt/tianjin/lib/opencv_java460.so
# deskew: /opt/tianjin/lib/Deskew/Bin/deskew
# deskewpy: /opt/tianjin/lib/correct.py
#imgconfig:
# opencv: D:/project/imgproc/lib/opencv_java460.dll
# deskew: C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
# deskewpy: D:/project/imgproc/lib/correct.py
...@@ -8,13 +8,27 @@ spring: ...@@ -8,13 +8,27 @@ spring:
discovery: discovery:
enabled: true enabled: true
service-id: CONFIG-SERVER service-id: CONFIG-SERVER
username: admin
password: 123456
eureka: eureka:
instance: instance:
prefer-ip-address: true prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 #每间隔1s,向服务端发送一次心跳,证明自己依然"存活" lease-renewal-interval-in-seconds: 2 #向服务端发送心跳间隔
lease-expiration-duration-in-seconds: 6 #告诉服务端,如果我2s之内没有给你发心跳,就代表我"死"了,将我踢出掉。 lease-expiration-duration-in-seconds: 6 #告诉服务端多少秒没收到心跳将我踢出掉
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client: client:
service-url: registry-fetch-interval-seconds: 2 #从服务端注册表中获取注册信息的时间间隔
defaultZone: @eureka.server.url@ serviceUrl:
defaultZone: @register.url@
feign:
client:
config:
default:
connect-timeout: 2000 #连接超时时间
read-timeout: 10000 #读超时时间
ribbon:
# ribbon服务列表刷新间隔(单位ms)
ServerListRefreshInterval: 2000
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