在網上購物時候,不止可以通過名稱搜索商品,也可以拍照上傳圖片搜索商品。比如某寶上拍個圖片就能搜索到對應的商品。
騰訊、阿里都提供了類似的圖像搜索服務,這類服務原理都差不多:
- 在一個具體的圖庫上,新增或者刪除圖片。
- 通過圖片搜索相似的圖片。
本文對接的是騰訊云的圖像搜索。
添加配置
添加 maven 依賴:
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.1129</version>
</dependency>
引入配置:
tencentcloud:
tiia:
secretId: ${SECRET_ID}
secretKey: ${SECRET_KEY}
endpoint: tiia.tencentcloudapi.com
region: ap-guangzhou
groupId: test1
secretId 和 secretKey 都是在 API秘鑰 地址:https://console.cloud.tencent.com/cam/capi
,groupId 是圖庫 id。
配置 bean
@Data
@Configuration
@ConfigurationProperties("tencentcloud.tiia")
public class TencentCloudTiiaProperties {
private String secretId;
private String secretKey;
private String endpoint = "tiia.tencentcloudapi.com";
private String region = "ap-guangzhou";
private String groupId;
}
@Configuration
@ConditionalOnClass(TencentCloudTiiaProperties.class)
public class TencentCloudTiiaConfig {
@Bean
public TiiaClient tiiaClient(TencentCloudTiiaProperties properties) {
Credential cred = new Credential(properties.getSecretId(), properties.getSecretKey());
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint(properties.getEndpoint());
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
TiiaClient client = new TiiaClient(cred, properties.getRegion(), clientProfile);
return client;
}
}
tiiaClient 是搜圖的核心,在后面新增、刪除、搜索圖片都會使用到。
圖庫更新
新建圖庫之后,需要將圖片批量的導入到圖庫中。一般開始會批量將上架的圖片批量導入到圖片庫,一般只需要操作一次。
商品有修改、新增、下架操作時,圖片也需要有對應的更新操作。但是每次都更新都同步更新操作,可能會導致數(shù)據庫頻繁更新,服務器壓力增加,需要改成,每次更新圖片后,同步到緩存中,然后定時處理緩存的數(shù)據:
騰訊圖像搜索沒有圖像更新接口,只有圖像刪除和新增的接口,那就先調用刪除,再調用新增的接口
刪除圖片
圖片刪除調用 tiiaClient.DeleteImages
方法,主要注意請求頻率限制。
默認接口請求頻率限制:10次/秒
這里就簡單處理,使用線程延遲處理 Thread.sleep(100)
,刪除圖片只要指定 EntityId:
@Data
public class DeleteImageDTO {
private String entityId;
private List<String> picName;
}
如果指定 PicName 就刪除 EntityId 下面的具體的圖片,如果不指定 PicName 就刪除整個 EntityId。
刪除圖片代碼如下:
public void deleteImage(List<DeleteImageDTO> list) {
if (CollectionUtils.isEmpty(list)) {
return;
}
list.stream().forEach(deleteImageDTO -> {
List<String> picNameList = deleteImageDTO.getPicName();
if (CollectionUtils.isEmpty(picNameList)) {
DeleteImagesRequest request = new DeleteImagesRequest();
request.setGroupId(tiiaProperties.getGroupId());
request.setEntityId(deleteImageDTO.getEntityId());
try {
Thread.sleep(100);
tiiaClient.DeleteImages(request);
} catch (TencentCloudSDKException | InterruptedException e) {
log.error("刪除圖片失敗, entityId {} 錯誤信息 {}", deleteImageDTO.getEntityId(), e.getMessage());
}
} else {
picNameList.stream().forEach(picName -> {
DeleteImagesRequest request = new DeleteImagesRequest();
request.setGroupId(tiiaProperties.getGroupId());
request.setEntityId(deleteImageDTO.getEntityId());
request.setPicName(picName);
try {
Thread.sleep(100);
tiiaClient.DeleteImages(request);
} catch (TencentCloudSDKException | InterruptedException e) {
log.error("刪除圖片失敗, entityId {}, 錯誤信息 {}", deleteImageDTO.getEntityId(), picName, e.getMessage());
}
});
}
});
}
新增圖片
新增圖片調用 tiiaClient.CreateImage
方法,這里也需要注意調用頻率的限制。除此之外還有兩個限制:
- 限制圖片大小不可超過 5M
- 限制圖片分辨率不能超過分辨率不超過 4096*4096
既然壓縮圖片需要耗時,那就每次上傳圖片先壓縮一遍,這樣就能解決調用頻率限制的問題。壓縮圖片引入 thumbnailator:
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
壓縮工具類:
public static byte[] compress(String url, double scale, long targetSizeByte) {
if (StringUtils.isBlank(url)) {
return null;
}
long targetSizeKB = targetSizeByte * 1024;
try {
URL u = new URL(url);
double quality = 0.8;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
do {
Thumbnails.of(u).scale(scale)
.outputQuality(quality)
.toOutputStream(outputStream);
long fileSize = outputStream.size();
if (fileSize <= targetSizeKB) {
return outputStream.toByteArray();
}
outputStream.reset();
if (quality > 0.1) {
quality -= 0.1;
} else {
scale -= 0.1;
}
} while (quality > 0 || scale > 0);
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
通過縮小圖片尺寸和降低圖片質量將圖片壓縮到固定的大小,這里都會先壓縮一遍。解決調用頻率限制的問題。
限制圖片的分辨率也是使用到 thumbnailator 里面的 size 方法。
thumbnailator 壓縮圖片和限制大小,不能一起使用,只能分來調用。
設置尺寸方法:
public static byte[] compressSize(byte[] imageData,String outputFormat,int width,int height) {
if (imageData == null) {
return null;
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
try {
BufferedImage bufferedImage = ImageIO.read(inputStream);
int imageWidth = bufferedImage.getWidth();
int imageHeight = bufferedImage.getHeight();
if (imageWidth <= width && imageHeight <= height) {
return imageData;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
Thumbnails.of(bufferedImage)
.size(width,height)
.outputFormat(outputFormat)
.toOutputStream(outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
這里的 width 和 height 并不是直接設置圖片的長度和長度,而是不會超過這個長度和寬度。如果有一個超過限制大小,壓縮尺寸,長寬比保持不變。
新增圖片需要指定 EntityId、url 以及 picName。
@Data
public class AddImageDTO {
private String entityId;
private String imgUrl;
private String picName;
}
解決了圖片壓縮問題,上傳圖片就比較簡單了:
public void uploadImage(List<AddImageDTO> list) {
if (CollectionUtils.isEmpty(list)) {
return;
}
list.stream().forEach(imageDTO -> {
String imgUrlStr = imageDTO.getImgUrl();
if (StringUtils.isBlank(imgUrlStr)) {
return;
}
CreateImageRequest request = new CreateImageRequest();
request.setGroupId(tiiaProperties.getGroupId());
request.setEntityId(imageDTO.getEntityId());
String imageUrl = imageDTO.getImgUrl();
byte[] bytes = ImageUtils.compress(imageUrl,0.6,1024 * 5);
String imageFormat = imageUrl.substring(imageUrl.lastIndexOf(".") + 1);
bytes = ImageUtils.compressSize(bytes,imageFormat,4096,4096);
request.setImageBase64(new String(Base64.encodeBase64(bytes), StandardCharsets.UTF_8));
request.setPicName(imageDTO.getPicName());
try {
tiiaClient.CreateImage(request);
} catch (TencentCloudSDKException e) {
log.error("圖像上傳失敗 error:{}", e.getMessage());
}
});
}
Tags 圖片自定義標簽,設置圖片的參數(shù),搜索的時候就可以根據參數(shù)搜索到不同的圖片。
更新圖片
一般商品更新,將數(shù)據存入緩存中:
String value = "demo key";
SetOperations<String, Object> opsForSet = redisTemplate.opsForSet();
opsForSet.add(RedisKeyConstant.PRODUCT_IMAGE_SYNC_CACHE_KEY, value);
再定時執(zhí)行任務:
public void syncImage() {
while (true) {
SetOperations<String, Object> operations = redisTemplate.opsForSet();
Object obj = operations.pop(RedisKeyConstant.PRODUCT_IMAGE_SYNC_CACHE_KEY);
if (obj == null) {
log.info("暫未發(fā)現(xiàn)任務數(shù)據");
return;
}
String pop = obj.toString();
if (StringUtils.isBlank(pop)) {
continue;
}
DeleteImageDTO deleteImageDTO = new DeleteImageDTO();
deleteImageDTO.setEntityId(pop);
try {
this.deleteImage(Collections.singletonList(deleteImageDTO));
} catch (Exception e) {
log.error("刪除圖片失敗,entityId {}",pop);
}
String imageUrl="";
String picName="";
AddImageDTO addImageDTO = new AddImageDTO();
addImageDTO.setEntityId(pop);
addImageDTO.setImgUrl(imageUrl);
addImageDTO.setPicName(picName);
try {
this.uploadImage(Collections.singletonList(addImageDTO));
} catch (Exception e) {
log.error("上傳圖片失敗,entityId {}",pop);
}
}
}
operations.pop
從集合隨機取出一個數(shù)據并移除數(shù)據,先刪除圖片,再從數(shù)據庫中查詢是否存在數(shù)據,如果存在就新增圖片。
搜索圖片
圖像搜索調用 tiiaClient.SearchImage
方法,需要傳圖片字節(jié)流,壓縮圖片需要文件后綴。
@Data
public class SearchRequest {
private byte[] bytes;
private String suffix;
}
public ImageInfo [] analysis(SearchRequest searchRequest) throws IOException, TencentCloudSDKException {
SearchImageRequest request = new SearchImageRequest();
request.setGroupId(tiiaProperties.getGroupId());
byte[] bytes = searchRequest.getBytes();
bytes = ImageUtils.compressSize(bytes,searchRequest.getSuffix(),4096,4096);
String base64 = Base64.encodeBase64String(bytes);
request.setImageBase64(base64);
SearchImageResponse searchImageResponse = tiiaClient.SearchImage(request);
return searchImageResponse.getImageInfos();
}
根據返回的 ImageInfos 數(shù)組獲取到 EntityId,就能獲取對應的商品信息了。
總結
對接圖像搜索,主要是做圖像的更新和同步操作。相對于每次更新就同步接口,這種方式對于服務器的壓力也比較大,先將數(shù)據同步到緩存中,然后在定時的處理數(shù)據,而搜索圖片對于數(shù)據一致性相對比較寬松,分散庫寫入的壓力。
新增圖片使用 thumbnailator 壓縮圖片和縮小圖片,對于調用請求頻率限制,新增圖片每次都會壓縮一次圖片,每次壓縮時間大概都大于 100ms,解決了請求頻率限制的問題。而刪除圖片,就簡單使用線程休眠的方式休眠 100ms。
做好圖片更新的操作之后,搜索圖庫使用 tiiaClient.SearchImage
方法就能獲取到對應的結果信息了。
Github示例
https://github.com/jeremylai7/springboot-learning/blob/master/springboot-test/src/main/java/com/test/controller/ImageSearchController.java
轉自https://www.cnblogs.com/jeremylai7/p/18551697
該文章在 2024/11/18 10:19:28 編輯過