package com.mokasz.image_pool.restful.controller; import com.mokasz.image_pool.restful.entity.ImageQueryResponse; import com.mokasz.image_pool.restful.entity.ImageSaveResponse; import org.bytedeco.javacpp.Loader; import org.bytedeco.opencv.opencv_java; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.img_hash.PHash; import org.opencv.imgcodecs.Imgcodecs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @RestController @RequestMapping("/service/image") public class ImageController { // 静态初始化 OpenCV static { Loader.load(opencv_java.class); } private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final PHash pHash = PHash.create(); @Autowired private JdbcTemplate jdbcTemplate; private void calculate (byte[] fileBytes, float[] vector) { Mat input = Imgcodecs.imdecode(new MatOfByte(fileBytes), Imgcodecs.IMREAD_UNCHANGED); Mat output = new Mat(); pHash.compute(input, output); transformMatToArrayOfFloat(output, vector); } private void transformMatToArrayOfFloat (Mat mat, float[] floats) { if (mat.width() != 8 || mat.height() != 1) throw new RuntimeException("Matrix dimension is not 1x8"); for (int i = 0; i < 8; i++) { floats[i] = (float) mat.get(0, i)[0]; } } private void transformBinaryArray (float[] octets, float[] binaries) { for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { int v = (int) octets[i]; binaries[i * 8 + j] = (v & (1 << j)) >> j; } } } private String getReadableHash (float[] floats) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 8; i++) { stringBuilder.append(String.format("%02X", (int) floats[i])); } return stringBuilder.toString(); } @PostMapping("/store") public ImageSaveResponse store (@RequestParam("file") MultipartFile file) throws IOException { logger.info("File size: {} Name: {}", file.getSize(), file.getOriginalFilename()); float[] floats = new float[8]; calculate(file.getBytes(), floats); float[] binaries = new float[64]; transformBinaryArray(floats, binaries); logger.info("{}", binaries); String hash = getReadableHash(floats); int updated = jdbcTemplate.update("INSERT INTO image_pool.image_hash VALUES (?, NOW(), ?) ON CONFLICT (hash) DO NOTHING", hash, binaries); logger.info("{}", updated); return new ImageSaveResponse(hash, 1L); } @PostMapping("/match") public List match (@RequestParam("file") MultipartFile file, @RequestParam("pool") String pool) throws IOException { logger.info("Not implemented pool: {}", pool); float[] floats = new float[8]; calculate(file.getBytes(), floats); float[] binaries = new float[64]; transformBinaryArray(floats, binaries); logger.info("{}", floats); List list = jdbcTemplate.query("SELECT hash, l2_distance(?::FLOAT2[], vector) AS similarity FROM image_pool.image_hash ORDER BY vector <-> ?::FLOAT2[] DESC LIMIT 2", (ResultSet rs, int rowNum) -> new ImageQueryResponse(rs.getFloat("similarity"), rs.getString("hash")), binaries, binaries); return list; } }