阿里云图像搜索
什么是图像搜索
引用阿里官方文档的话:
- 图像搜索服务 (Image Search)是以深度学习和大规模机器学习技术为核心,通过图像识别和搜索功能,实现以图搜图的智能图像搜索产品。图像搜索服务在基于图像识别技术基础上,结合不同行业应用和业务场景,帮助用户实现相同或相似图片搜索。图片搜索匹配到的相同或类似的照片按照阈值大小排序进行返回,最多可返回前500张图片参数,越靠前相似度越高。
- 图像搜索服务内容:
商品图像搜索:通过输入商品图片,可以在商品库中准确地找到图片中商品的同款或者相似款,返回对应的商品信息。
布料图像搜索:通过输入布料图片,可以在布料库中准确地找到与输入相同或相似纹理的布料,返回对应的布料信息。
商标图像搜索:通过输入商标图片,可以在商标库中准确地找到与输入相同或相似的商标,返回对应的商标信息。
通用图像搜索:通过输入具有相同元素或主体内容的图片,在海量图片库中查找相似或相同的图片。
前期工作
- 登录图像搜索控制台。
- 在左侧导航栏里单击相应的 业务场景 (商品图片搜索、布料图片搜索、商标图片搜索、通用图片搜索),并在列表左上方单击 创建实例。
- 在售卖页面上选择相应的 地域。
注意:不同地域内的实例网络不互通,且购买后不能更换地域,请谨慎选择。
- 选择 访问频次 和 最大容量。
注意:不同的访问频次和最大容量对应的独享的资源数量不同,相应的价格也不同,目前最大可支持10QPS和5000万图片(注:对于商品图片搜索,该处指的是图片数量,而不是商品数量)容量,如果超出最大容量需单独申请。
- 填写实例名称。
注意:实例名称由小写英文字母和数字组成,且首字符为小写英文字母,名称长度为4-32个字符,如: imagesearch1022。
- 选择套餐类型、套餐包以及购买时长。
- 确认费用并单击立即购买。
注意:购买完成后,系统会自动进行实例的初始化操作,如果购买后实例为 新建 或者 初始化失败 状态,请在 实例列表 或者 实例详情页 单击
初始化 按钮进行初始化操作。
完成之后在图像搜索控制台可以看到
子账号控制台离线导入图片权限配置
- 在RAM控制台上,选择 策略管理 > 新建授权策略。
- 在 RAM 控制台上,选择 角色管理 > 新建角色。
- 给RAM角色授权ImagesSearchAccessOSS
- 给RAM角色授权操作OSSAliyunImageSearchFullAccess
离线图片上传
在OSS中新建文件夹目录/increment,在/increment目录下上传照片文件,和increment.meta文件
#increment.meta
{"operator":"ADD","item_id":"test1", "cust_content":"123", "pic_list":["test1.jpg"]}
{"operator":"ADD","item_id":"test2", "cust_content":"456", "pic_list":["test2.jpg"]}
{"operator":"ADD","item_id":"test3", "cust_content":"789", "pic_list":["text3.png"]}
图片搜索控制台中,点击通用照片实例。点击增量。这边使用的图像搜索是试用版本,所以不支持清空实例中的数据的功能。
下图中 资源名称是在上面创建RAM角色步骤中可以进行查看(ImageSearchRole)
Bucket名称就是OSS中的Bucket名称
数据路径就是/increment
上传完成
与项目集成(需要结合前面说到的OSS)
图像搜索与项目的集成在oss项目的基础上进行集成
pom依赖
# pom.xml
<!-- aliyun imagessearch-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-imagesearch</artifactId>
<version>2.0.0</version>
</dependency>
配置文件
以下文件中的子账号就是之前sts用到的子账号。
但是至于图像搜索能否集成OSS+STS,我倒是没有看到相关文档,尝试了一下使用STS获取到的临时AccessKeyID和AccessKeySecret和SecurityToken但是失败了,可是查看源代码确实是有发现public static synchronized DefaultProfile getProfile(String regionId, String accessKeyId, String secret, String stsToken)这样一个方法。这个方法是否如我所想能够结合ststoken,这点还不是很明确,还望有了解的大神赐教。
# imagesearch.properties
#阿里云imageSearch配置
imageSearch.regionId=cn-shanghai
imageSearch.product=ImageSearch
imageSearch.endpoint=imagesearch.cn-shanghai.aliyuncs.com
#子账号
imageSearch.accessKeyId=LTAI4F*******KYrpsp93Jy
imageSearch.accessKeySecret=WJPEEnybNO*********zSc0AnbsP
imageSearch.roleArn=acs:ram::1********9809:role/********chrole
imageSearch.instanceName=imagessear******
# 注意:
目前阿里云图像搜索只有华东(上海),而图像搜索要求用到的OSS在同一区域中才能使用
所以之前用到的oss最好选择华东(上海)区域的服务。
工具类的使用
这个工具类主要用到的sdk SDK官网中的,结合配置文件。
# ImageSearchUtil.java
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.imagesearch.model.v20190325.*;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.UUID;
/**
* @Author Qizy
* @Date 2019/12/18 14:34
* @Version 1.0
**/
@Getter
@Setter
@Component
@Configuration
@PropertySource(value = { "classpath:imagesearch.properties" })
public class ImageSearchUtil {
@Value("${imageSearch.regionId}")
private String regionId;
@Value("${imageSearch.product}")
private String product;
@Value("${imageSearch.endpoint}")
private String endpoint;
@Value("${imageSearch.accessKeyId}")
private String accessKeyId;
@Value("${imageSearch.accessKeySecret}")
private String accessKeySecret;
@Value("${imageSearch.instanceName}")
private String instanceName;
@Value("${imageSearch.roleArn}")
private String roleArn;
private IAcsClient initClient() {
//创建 Profile。生成 IClientProfile 的对象 profile,该对象存放 AccessKeyID 和 AccessKeySecret 和地域信息。
IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
//设置图像搜索服务访问的 EndPoint,假定实例是在上海区域。
DefaultProfile.addEndpoint(regionId, product, endpoint);
//创建 Client。从 IClientProfile 类中再生成 IAcsClient 的对象 client,后续获得 response 都需要从 IAcsClient 中获得。
IAcsClient client = new DefaultAcsClient(profile);
return client;
}
/**
* 添加图片
* @param filePath 文件路径
* @param categoryId 类别类目(选填)
* 对于商品搜索:若设置类目,则以设置的为准;若不设置类目,将由系统进行类目预测,预测的类目结果可在Response中获取 。
* 对于布料、商标、通用搜索:不论是否设置类目,系统会将类目设置为 88888888。
* @param crop 选填,是否需要进行主体识别,默认为true。
* @param region 选填,图片的主体区域,格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点,x2,y2是右下角的点。
* @param intAttr 选填,整数类型属性,可用于查询时过滤,查询时会返回该字段。
* @param strAttr 选填,字符串类型属性,最多支持 128个字符。可用于查询时过滤,查询时会返回该字段。
* @param customContent 选填,用户自定义的内容,最多支持 4096个字符。
* @return
*/
public AddImageResponse add(String filePath, Integer categoryId, String region, Boolean crop, Integer intAttr, String strAttr, String customContent) {
AddImageResponse response = null;
IAcsClient client = initClient();
AddImageRequest request = new AddImageRequest();
if(filePath == null || StringUtils.equals(filePath.trim(), "")){
return response;
}
// 必填,图像搜索实例名称。
request.setInstanceName(instanceName);
// 必填,商品id,最多支持 512个字符。
// 一个商品可有多张图片。
String uuid = UUID.randomUUID().toString().replaceAll("-","");
request.setProductId(uuid);
// 必填,图片名称,最多支持 512个字符。
// 1. ProductId + PicName唯一确定一张图片。
// 2. 如果多次添加图片具有相同的ProductId + PicName,以最后一次添加为准,前面添加的的图片将被覆盖。
request.setPicName(uuid + getFileName(filePath.trim()));
// 选填,图片类目。
// 1. 对于商品搜索:若设置类目,则以设置的为准;若不设置类目,将由系统进行类目预测,预测的类目结果可在Response中获取 。
// 2. 对于布料、商标、通用搜索:不论是否设置类目,系统会将类目设置为 88888888。
if(categoryId != null){
request.setCategoryId(categoryId);
}else {
request.setCategoryId(88888888);
}
// 必填,图片内容,Base64编码。
// 最多支持 2MB大小图片以及5s的传输等待时间。当前仅支持jpg和png格式图片;
// 对于商品、商标、通用图片搜索,图片长和宽的像素必须都大于等于200,并且小于等于1024;
// 对于布料搜索,图片长和宽的像素必须都大于等于448,并且小于等于1024;
// 图像中不能带有旋转信息。
byte[] bytes2 = getBytes(filePath);
Base64 base64 = new Base64();
String encodePicContent = base64.encodeToString(bytes2);
request.setPicContent(encodePicContent);
// 选填,是否需要进行主体识别,默认为true。
// 1.为true时,由系统进行主体识别,以识别的主体进行搜索,主体识别结果可在Response中获取。
// 2. 为false时,则不进行主体识别,以整张图进行搜索。
// 3.对于布料图片搜索,此参数会被忽略,系统会以整张图进行搜索。
if(crop != null){
request.setCrop(crop);
}
// 选填,图片的主体区域,格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点,x2,y2是右下角的点。
// 若用户设置了Region,则不论Crop参数为何值,都将以用户输入Region进行搜索。
// 对于布料图片搜索,此参数会被忽略,系统会以整张图进行搜索。
if(region != null && !StringUtils.equals(region.trim(), "")){
request.setRegion(region);
}/*else {
request.setRegion("280,486,232,351");
}*/
// 选填,整数类型属性,可用于查询时过滤,查询时会返回该字段。
// 例如不同的站点的图片/不同用户的图片,可以设置不同的IntAttr,查询时通过过滤来达到隔离的目的
if(intAttr != null){
request.setIntAttr(intAttr);
}
// 选填,字符串类型属性,最多支持 128个字符。可用于查询时过滤,查询时会返回该字段。
if(strAttr != null && !StringUtils.equals(strAttr.trim(), "")){
request.setStrAttr(strAttr);
}
// 选填,用户自定义的内容,最多支持 4096个字符。
// 查询时会返回该字段。例如可添加图片的描述等文本。
if(customContent != null && !StringUtils.equals(customContent.trim(), "")){
request.setCustomContent(customContent);
}
try {
response = client.getAcsResponse(request);
} catch (ClientException e) {
// 抛出异常,例如参数无效,或者实例不可用等情况
e.printStackTrace();
}
return response;
}
/**
* 根据图片搜索图片
* @param filePath 文件路径
* @param categoryId 类别类目(选填)
* 对于商品搜索:若设置类目,则以设置的为准;若不设置类目,将由系统进行类目预测,预测的类目结果可在Response中获取 。
* 对于布料、商标、通用搜索:不论是否设置类目,系统会将类目设置为 88888888。
* @param crop 选填,是否需要进行主体识别,默认为true。
* @param region 选填,图片的主体区域,格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点,x2,y2是右下角的点。
* @param num 选填,返回结果的数目。取值范围:1-100。默认值:10。
* @param start 选填,返回结果的起始位置。取值范围:0-499。默认值:0。
* @param filter 选填,过滤条件
* int_attr支持的操作符有>、>=、<、<=、=,str_attr支持的操作符有=和!=,多个条件之支持AND和OR进行连接
* @return
*/
public SearchImageResponse searchByPic(String filePath, Integer categoryId, String region, Boolean crop, Integer num, Integer start, String filter) {
SearchImageResponse response = null;
IAcsClient client = initClient();
SearchImageRequest request = new SearchImageRequest();
// 必填,图像搜索实例名称。
request.setInstanceName(instanceName);
// 选填,搜索类型,取值范围:
// 1. SearchByPic(默认):根据图片搜索相似图片。
// 2. SearchByName,根据已添加的图片搜索相似图片。
request.setType("SearchByPic");
// 图片内容,Base64编码。最多支持 2MB大小图片以及5s的传输等待时间。当前仅支持jpg和png格式图片;
// 对于商品、商标、通用图片搜索,图片长和宽的像素必须都大于等于200,并且小于等于1024;
// 对于布料搜索,图片长和宽的像素必须都大于等于448,并且小于等于1024;
// 图像中不能带有旋转信息。
// 1. Type=SearchByPic时,必填
// 2. Type=SearchByName时,无需填写。
byte[] bytes2 = getBytes(filePath);
Base64 base64 = new Base64();
String encodePicContent = base64.encodeToString(bytes2);
request.setPicContent(encodePicContent);
// 选填,商品类目。
// 1. Type=SearchByPic时,选填:
// 1)对于商品搜索,若设置类目,则以设置的为准;若不设置类目,将由系统进行类目预测,预测的类目结果可在Response中获取 。
// 2)对于布料、商标、通用搜索:不论是否设置类目,系统会将类目设置为88888888。
// 2. Type=SearchByName时,无需填写,本参数不生效。
if(categoryId != null){
request.setCategoryId(categoryId);
}else {
request.setCategoryId(88888888);
}
// 选填,图片的主体区域,格式为 x1,x2,y1,y2, 其中 x1,y1 是左上角的点,x2,y2是右下角的点。
// 1. Type=SearchByPic时,选填:若用户设置了Region,则不论Crop参数为何值,都将以用户输入Region进行搜索。
// 2. Type=SearchByName时,无需填写,本参数不生效。
// 3. 对于布料图片搜索,此参数会被忽略,系统会以整张图进行搜索。
if(region != null && !StringUtils.equals(region.trim(), "")){
request.setRegion(region);
}
// 选填,是否需要进行主体识别,默认为true。
// 1.Type=SearchByPic时,选填:
// 1)为true时,由系统进行主体识别,以识别的主体进行搜索,主体识别结果可在Response中获取。
// 2)为false时,则不进行主体识别,以整张图进行搜索。
// 2. Type=SearchByName时,无需填写,本参数不生效。
// 3. 对于布料图片搜索,此参数会被忽略,系统会以整张图进行搜索。
if(crop != null){
request.setCrop(crop);
}
// 选填,返回结果的数目。取值范围:1-100。默认值:10。
if(num != null){
request.setNum(num);
}
// 选填,返回结果的起始位置。取值范围:0-499。默认值:0。
if(start != null){
request.setStart(start);
}
// 选填,过滤条件
// int_attr支持的操作符有>、>=、<、<=、=,str_attr支持的操作符有=和!=,多个条件之支持AND和OR进行连接。
// 示例:
// 1. 根据IntAttr过滤结果,int_attr>=100
// 2. 根据StrAttr过滤结果,str_attr!="value1"
// 3. 根据IntAttr和StrAttr联合过滤结果,int_attr=1000 AND str_attr="value1"
//request.setFilter("int_attr=1");
if(filter != null && !StringUtils.equals(filter.trim(), "")){
request.setFilter(filter);
}
try {
response = client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
return response;
}
/**
* 根据已入库的图片(ProductId+PicName)搜索图片
* @param productId 必填,已添加图片的ProductId。
* @param picName 必填,已添加图片的PicName。
* @param num 选填,返回结果的数目。取值范围:1-100。默认值:10。
* @param start 选填,返回结果的起始位置。取值范围:0-499。默认值:0。
* @param filter 选填,过滤条件
* int_attr支持的操作符有>、>=、<、<=、=,str_attr支持的操作符有=和!=,多个条件之支持AND和OR进行连接
* @return
*/
public SearchImageResponse searchByName(String productId, String picName, Integer num, Integer start, String filter) {
SearchImageResponse response = null;
IAcsClient client = initClient();
SearchImageRequest request = new SearchImageRequest();
// 必填,图像搜索实例名称。
request.setInstanceName(instanceName);
// 选填,搜索类型,取值范围:
// 1. SearchByPic(默认):根据图片搜索相似图片。
// 2. SearchByName,根据已添加的图片搜索相似图片。
request.setType("SearchByName");
// 商品id。
// 1. Type=SearchByPic时,无需填写
// 2. Type=SearchByName时,必填,已添加图片的ProductId。
request.setProductId(productId);
// 图片名称。
// 1. Type=SearchByPic时,无需填写
// 2. Type=SearchByName时,必填,已添加图片的PicName。
request.setPicName(picName);
// 选填,返回结果的数目。取值范围:1-100。默认值:10。
if(num != null){
request.setNum(num);
}
// 选填,返回结果的起始位置。取值范围:0-499。默认值:0。
if(start != null){
request.setStart(start);
}
// 选填,过滤条件
// int_attr支持的操作符有>、>=、<、<=、=,str_attr支持的操作符有=和!=,多个条件之支持AND和OR进行连接。
// 示例:
// 1. 根据IntAttr过滤结果,int_attr>=100
// 2. 根据StrAttr过滤结果,str_attr!="value1"
// 3. 根据IntAttr和StrAttr联合过滤结果,int_attr=1000 AND str_attr="value1"
//request.setFilter("int_attr=1");
if(filter != null && !StringUtils.equals(filter.trim(), "")){
request.setFilter(filter);
}
try {
response = client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
return response;
}
/**
* 根据ID和name,删除图片搜索中的图片
* @param productId id 必填
* @param picName name 若不指定本参数,则删除ProductId下所有图片;若指定本参数,则删除ProductId+PicName指定的图片。
* @Description: id必填 name选填
* @return
*/
public DeleteImageResponse delete(String productId, String picName) {
DeleteImageResponse response = null;
IAcsClient client = initClient();
DeleteImageRequest request = new DeleteImageRequest();
if(productId == null || StringUtils.equals(productId.trim(), "")){
return null;
}
// 必填,图像搜索实例名称。
request.setInstanceName(instanceName);
// 必填,商品id。
request.setProductId(productId);
// 选填,图片名称。若不指定本参数,则删除ProductId下所有图片;若指定本参数,则删除ProductId+PicName指定的图片。
request.setPicName(picName);
try {
response = client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
return response;
}
private static byte[] getBytes(String filePath) {
byte[] buffer = null;
try {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
// picture max size is 2MB
ByteArrayOutputStream bos = new ByteArrayOutputStream(2000 * 1024);
byte[] b = new byte[1000];
int n;
while ((n = fis.read(b)) != -1) {
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
private String getFileName(String filePath){
String back = null;
try{
File tempFile =new File(filePath.trim());
back = tempFile.getName();
} catch (Exception e){
e.printStackTrace();
}
return back;
}
}
做一下简单的测试
测试的控制层代码集成了swagger2 如果没有集成则需要删除@Api @ApiOperation等注解
# ImagesSearchController.java
import com.aliyuncs.imagesearch.model.v20190325.AddImageResponse;
import com.aliyuncs.imagesearch.model.v20190325.DeleteImageResponse;
import com.aliyuncs.imagesearch.model.v20190325.SearchImageResponse;
import com.learning.ssm_swagger.util.ImageSearchUtil;
import com.learning.ssm_swagger.util.STSUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.File;
import java.util.LinkedList;
/**
* @Author Qizy
* @Date 2019/12/11 9:43
* @Version 1.0
**/
@Controller
@RequestMapping("/imageSearch")
@Api(value="图片搜索 Service Controller", description = "阿里云图片搜索测试")
public class ImagesSearchController {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
private static final String TO_PATH="upLoad";
private static final String RETURN_PATH="success";
@Autowired
private ImageSearchUtil imageSearchUtil;
@ApiOperation(value = "图片搜索-上传图片", notes = "图片搜索-上传图片", httpMethod = "POST")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public AddImageResponse upload(String filePath) throws Exception {
AddImageResponse add = imageSearchUtil.add(filePath, 88888888, null, false, null, null, null);
return add;
}
@ApiOperation(value = "图片搜索-删除图片", notes = "图片搜索-删除图片", httpMethod = "POST")
@RequestMapping(value = "/delete", method = RequestMethod.POST)
@ResponseBody
public DeleteImageResponse delete(String productId, String picName) throws Exception {
DeleteImageResponse delete = imageSearchUtil.delete(productId, picName);
return delete;
}
@ApiOperation(value = "图片搜索-搜索图片-根据图片", notes = "图片搜索-搜索图片", httpMethod = "POST")
@RequestMapping(value = "/searchByPic", method = RequestMethod.POST)
@ResponseBody
public SearchImageResponse searchByPic(String filePath) throws Exception {
SearchImageResponse searchImageResponse = imageSearchUtil.searchByPic(filePath, 88888888, null, false, null, null, null);
return searchImageResponse;
}
@ApiOperation(value = "图片搜索-搜索图片-根据名字", notes = "图片搜索-搜索图片", httpMethod = "POST")
@RequestMapping(value = "/searchByName", method = RequestMethod.POST)
@ResponseBody
public SearchImageResponse searchByName(String productId, String picName) throws Exception {
SearchImageResponse searchImageResponse = imageSearchUtil.searchByName(productId, picName, null, null, null);
return searchImageResponse;
}
@ApiOperation(value = "图片搜索-搜索图片-批量", notes = "图片搜索-搜索图片", httpMethod = "POST")
@RequestMapping(value = "/loadfiles", method = RequestMethod.POST)
@ResponseBody
public void loadfiles() throws Exception {
int fileNum = 0, folderNum = 0;
File file = new File("C:\\Users\\Administrator.DESKTOP-H1HGKGC\\Desktop\\upload");
if (file.exists()) {
File[] files = file.listFiles();
for (File file2 : files) {
if (file2.isDirectory()) {
folderNum++;
} else {
System.out.println("文件:" + file2.getAbsolutePath());
fileNum++;
AddImageResponse add = imageSearchUtil.add(file2.getAbsolutePath(), 88888888, null, false, null, null, null);
}
}
} else {
System.out.println("文件不存在!");
}
System.out.println("文件夹共有:" + folderNum + ",文件共有:" + fileNum);
}
}
图片查询返回数据
根据图片搜索机器学习的性质,查询的图片根据匹配阈值高低进行返回,第一个返回的数据匹配度最高。
返回的json数据各个字段的意思。
官方文档
{
"requestId": "0FA6726E-85E5-4D70-B871-697AFF256AAA",
"success": true,
"code": 0,
"msg": "success",
"auctions": [
{
"categoryId": 88888888,
"productId": "ocrtest",
"picName": "ocrtest.jpg",
"customContent": "k1:v1,k2:v2,k3:v3",
"sortExprValues": "25.7281875610352;69",
"intAttr": null,
"strAttr": null
},
{
"categoryId": 88888888,
"productId": "test2",
"picName": "test2.jpg",
"customContent": "456",
"sortExprValues": "5.51136159896851;173",
"intAttr": null,
"strAttr": null
},
...
],
"head": {
"docsReturn": 10,
"docsFound": 372,
"searchTime": 112
},
"picInfo": {
"categoryId": 88888888,
"region": null,
"allCategories": [
{
"id": 0,
"name": "Tops"
},
...
]
}
}