ImageController.java 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package com.mokasz.image_pool.restful.controller;
  2. import com.mokasz.image_pool.algorithm.FeatureExtractor;
  3. import com.mokasz.image_pool.restful.entity.ImageQueryResponse;
  4. import com.mokasz.image_pool.restful.entity.ImageSaveResponse;
  5. import org.bytedeco.javacpp.Loader;
  6. import org.bytedeco.opencv.opencv_java;
  7. import org.opencv.core.Mat;
  8. import org.opencv.core.MatOfByte;
  9. import org.opencv.img_hash.PHash;
  10. import org.opencv.imgcodecs.Imgcodecs;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.jdbc.core.JdbcTemplate;
  15. import org.springframework.web.bind.annotation.PostMapping;
  16. import org.springframework.web.bind.annotation.RequestMapping;
  17. import org.springframework.web.bind.annotation.RequestParam;
  18. import org.springframework.web.bind.annotation.RestController;
  19. import org.springframework.web.multipart.MultipartFile;
  20. import java.io.IOException;
  21. import java.sql.ResultSet;
  22. import java.util.List;
  23. @RestController
  24. @RequestMapping("/service/image")
  25. public class ImageController {
  26. // 静态初始化 OpenCV
  27. static {
  28. Loader.load(opencv_java.class);
  29. }
  30. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  31. private final PHash pHash = PHash.create();
  32. @Autowired
  33. private JdbcTemplate jdbcTemplate;
  34. private void calculate(byte[] fileBytes, float[] vector) {
  35. Mat input = Imgcodecs.imdecode(new MatOfByte(fileBytes), Imgcodecs.IMREAD_UNCHANGED);
  36. Mat output = new Mat();
  37. pHash.compute(input, output);
  38. transformMatToArrayOfFloat(output, vector);
  39. }
  40. private void transformMatToArrayOfFloat(Mat mat, float[] floats) {
  41. if (mat.width() != 8 || mat.height() != 1) {
  42. throw new RuntimeException("Matrix dimension is not 1x8");
  43. }
  44. for (int i = 0; i < 8; i++) {
  45. floats[i] = (float) mat.get(0, i)[0];
  46. }
  47. }
  48. private void transformBinaryArray(float[] octets, float[] binaries) {
  49. for (int i = 0; i < 8; i++) {
  50. for (int j = 0; j < 8; j++) {
  51. int v = (int) octets[i];
  52. binaries[i * 8 + j] = (v & (1 << j)) >> j;
  53. }
  54. }
  55. }
  56. private String getReadableHash(float[] floats) {
  57. StringBuilder stringBuilder = new StringBuilder();
  58. for (int i = 0; i < 8; i++) {
  59. stringBuilder.append(String.format("%02X", (int) floats[i]));
  60. }
  61. return stringBuilder.toString();
  62. }
  63. @PostMapping("/store2")
  64. public ImageSaveResponse store2(@RequestParam("file") MultipartFile file) throws IOException {
  65. FeatureExtractor.compute(file.getBytes());
  66. return new ImageSaveResponse("OK", 1L);
  67. }
  68. @PostMapping("/store")
  69. public ImageSaveResponse store(@RequestParam("file") MultipartFile file, @RequestParam("pool") int pool) throws IOException {
  70. logger.info("File size: {} Name: {}", file.getSize(), file.getOriginalFilename());
  71. float[] floats = new float[8];
  72. calculate(file.getBytes(), floats);
  73. float[] binaries = new float[64];
  74. transformBinaryArray(floats, binaries);
  75. logger.info("{}", binaries);
  76. String hash = getReadableHash(floats);
  77. int updated = jdbcTemplate.update("INSERT INTO image_pool.image_hash VALUES (?, NOW(), ?) ON CONFLICT (hash) DO NOTHING", hash, binaries);
  78. logger.info("{}", updated);
  79. jdbcTemplate.update("INSERT INTO image_pool.image_to_pool VALUES (?, ?, NOW()) ON CONFLICT (hash, pool_id) DO NOTHING", hash, pool);
  80. return new ImageSaveResponse(hash, 1L);
  81. }
  82. @PostMapping("/match")
  83. public List<ImageQueryResponse> match(@RequestParam("file") MultipartFile file,
  84. @RequestParam("pool") int pool,
  85. @RequestParam(value = "max", defaultValue = "1") float max,
  86. @RequestParam(value = "min", defaultValue = "1") float min,
  87. @RequestParam(value = "limit", defaultValue = "10") int limit) throws IOException {
  88. float[] floats = new float[8];
  89. calculate(file.getBytes(), floats);
  90. float[] binaries = new float[64];
  91. transformBinaryArray(floats, binaries);
  92. logger.info("{}", floats);
  93. List<ImageQueryResponse> list = jdbcTemplate.query("SELECT * FROM (SELECT hash, (64 - l2_distance(?::FLOAT2[], vector)) / 64 AS similarity FROM image_pool.image_hash WHERE hash IN (SELECT hash FROM image_pool.image_to_pool WHERE pool_id = ?) ORDER BY vector <-> ?::FLOAT2[] ASC) t WHERE similarity >=? and similarity<=? ORDER BY similarity DESC LIMIT ?",
  94. (ResultSet rs, int rowNum) -> new ImageQueryResponse(rs.getFloat("similarity"), rs.getString("hash")), binaries, pool, binaries, min, max, limit);
  95. return list;
  96. }
  97. }