写点什么

最佳实践|用腾讯云 AI 图像搜索打造属于自己的拍立淘

  • 2022 年 9 月 08 日
    广东
  • 本文字数:6099 字

    阅读完需:约 20 分钟

最佳实践|用腾讯云AI图像搜索打造属于自己的拍立淘

最近,在一个论坛交流会上, 有嘉宾提出自己运营多年的微信小程序商城经常收到用户反馈:自己在逛街时候发现别人穿的好看的衣服,很难通过关键字定位到具体的商品,如果能拍照定位相关的商品就好了,问目前小程序里面能否实现这样的功能。作为一名软件开发者, 日常网购也有类似的体会。如果能在小程序里集成商品搜索的功能,就能大大提升用户的体验,嘉宾的问题引发我极大的兴趣。


在调研过程中,发现腾讯云图像分析的图像搜索产品可以基于输入图片,智能识别图片中的商品主体,在自建图片库中搜索相同或相似的商品图片,并给出相似度打分。如果输入检索的图片包含服饰类商品,可智能识别上衣、下装、裙装、鞋、包、配饰等多种服饰的类别、颜色以及其他特征属性,实现电商场景下的以图搜图。


接下来 ,将详细分享一下我是如何在小程序里实现商品搜索的。


一、准备工作

1.1 明确目标

在小程序里,通过输入商品图片来定位相似的商品图,类似于下面这个:


1.2 了解图像搜索

在开始使用之前,还是得对我即将要用的产品进行一个比较详细的了解。


官网文档介绍:


接口文档:


1.3 开通图像分析服务

接下来就可以按照官网文档的指引在腾讯云官网开通图像分析服务:


开通服务后 ,创建图片和检索图片分别会发放一万次免费资源包, 可以在资源包管理页面查看使用情况:


二、开发流程

2.1 获取个人密钥

在腾讯云官网访问管理页面, 新建个人密钥:


2.2 在线调试

下面通过在线调试的方式简单的实现拍立淘的功能。


(1)图库类型选择


首先查看图像搜索的文档,我们选择商品图像搜索的服务类型。


(2)创建图库


创建图片库,指定商品图像搜索, 腾讯云官网提供了在线调用 API Explorer 工具,方便我们可视化调用。


(3)图片入库


创建图片, 将商品图片入库到指定图库中,返回值中包含了主体位置信息。


(4)商品搜索


检索图片, 指定商品图,在图库中进行检索 ,获取相似或者相同的商品图, 返回结果中包含了与输入图类似的商品 ID。


这样就基本上演示出了商品搜索的过程,对于使用而言,可以对输入的参数进行一些微调,满足更多的要求。


2.3 使用 SDK 调用

(1)文档介绍


正式接入的话,需要集成腾讯云官网上提供的 SDK。 在文档的最下方,提供了多个语言的 SDK,可以根据自己熟悉的语言进行接入。


(2)示例


以 go 语言为例,演示下如何使用。

第一步: 安装基础包:

go get -v -u github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common


安装图像分析依赖:

go get -v -u github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tiia


第二步: 实现调用,以创建图库为例。

credential := common.NewCredential(***, ***)clientProfile := profile.NewClientProfile()clientProfile.HttpProfile.Endpoint = "tiia.tencentcloudapi.com"
cli, err := tiia.NewClient(credential, regions.Guangzhou, clientProfile)if err != nil { panic(err)}//实例化一个请求结构req := tiia.NewCreateGroupRequest()//对请求参数进行赋值req.GroupId = common.StringPtr("groupId")req.GroupName = common.StringPtr("groupName")req.Brief = common.StringPtr("brief")req.MaxCapacity = common.Uint64Ptr(uint64(100))req.MaxQps = common.Uint64Ptr(uint64(10))req.GroupType = common.Uint64Ptr(uint64(5))//调用请求resp, err := cli.CreateGroup(req)if err != nil { return}
复制代码


三、小程序实现商品搜索

上面介绍了图像搜索的基本能力,如何应用商品搜索能力,来实现拍立淘的效果呢,接下来以小程序为例,来演示一个简单的应用:


3.1 构建底库

根据上述文档, 我们在服务端使用 sdk 来初始化图片库,然后通过小程序端来访问,这里不再赘述。

  1. 使用 2.1 申请的 secretID,secretkey 来创建图库。

  2. 商品图入库。


3.2 构建小程序


index.js

const app = getApp()
Page({ data: { urls: [], inputValue: "图片URL", buttonStatus: false, category: ['Jacket', 'dress', 'trousers', 'bag', 'shoe', 'accessories'], motto: 'Hello World', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse('button.open-type.getUserInfo'), canIUseGetUserProfile: false, canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName') // 如需尝试获取用户信息可改为false }, // 事件处理函数 bindViewTap() { wx.navigateTo({ url: '../logs/logs' }) }, onLoad() { if (wx.getUserProfile) { this.setData({ canIUseGetUserProfile: true }) } }, getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { console.log(res) this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) }, getUserInfo(e) { // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息 console.log(e) this.setData({ userInfo: e.detail.userInfo, hasUserInfo: true }) },
isUrl (url) { var urlRegExp=/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/; if(urlRegExp.test(url)){ return true; }else{ return false; } },
imageSearch() { var that = this wx.request({ url: 'http://****/search', data: { "GroupId": "***", "ImageUrl": this.data.inputValue }, method: "POST", header: { 'Content-Type': "application/json" }, success (res) { if (res.data == null) { wx.showToast({ icon: "error", title: '请求失败', }) return } console.log(res.data) that.setData({ urls: res.data.Urls, object: res.data.Object }) that.drawInputImage() }, fail(res) { wx.showToast({ icon: "error", title: '请求失败', }) } }) },
drawInputImage: function() { var that = this; wx.downloadFile({ url: that.data.inputValue, success: function(res) { var imagePath = res.tempFilePath // 绘制 wx.getImageInfo({ src: imagePath, success: function(res) { wx.createSelectorQuery() .select('#input_canvas') // 在 WXML 中填入的 id .fields({ node: true, size: true }) .exec((r) => { // Canvas 对象 const canvas = r[0].node // 渲染上下文 const ctx = canvas.getContext('2d') // Canvas 画布的实际绘制宽高 const width = r[0].width const height = r[0].height
// 初始化画布大小 const dpr = wx.getWindowInfo().pixelRatio canvas.width = width * dpr canvas.height = height * dpr ctx.scale(dpr, dpr) // 清空画布 ctx.clearRect(0, 0, width, height)
let radio = height / res.height console.log("radio:", radio) const img = canvas.createImage() var x = width / 2 - (res.width * radio / 2)
img.src = imagePath img.onload = function() { ctx.drawImage(img, x, 0, res.width * radio, res.height * radio) var allBox = that.data.object.AllBox ctx.fillStyle = 'rgba(247, 15, 2, 0.7)'; for (var i in allBox) { if (allBox[i].Rect.X == that.data.object.Box.Rect.X && allBox[i].Rect.Y == that.data.object.Box.Rect.Y) { continue } ctx.fillRect(x + (allBox[i].Rect.X * radio), allBox[i].Rect.Y * radio, allBox[i].Rect.Width * radio, allBox[i].Rect.Height * radio); } // 绘制主体 ctx.fillStyle = 'rgba(159, 255, 125, 0.7)'; //rgba(0, 0, 200, 0.5) ctx.fillRect(x + (that.data.object.Box.Rect.X * radio), that.data.object.Box.Rect.Y * radio, that.data.object.Box.Rect.Width * radio, that.data.object.Box.Rect.Height * radio); console.log(that.data.object.AllBox)
// 绘制文字背景 let text = `${that.data.category[that.data.object.CategoryId]} ${that.data.object.Box.Score}` ctx.fillStyle = '#221329' ctx.fillRect(x + (that.data.object.Box.Rect.X * radio), that.data.object.Box.Rect.Y * radio, that.data.object.Box.Rect.Width * radio, 15) // 绘制文字 ctx.fillStyle = '#fcfafc' console.log(that.data.category[that.data.object.CategoryId]) ctx.fillText(text, x + (that.data.object.Box.Rect.X * radio), that.data.object.Box.Rect.Y * radio + 10) } }) } }) } }) },
handlerInput(e) { console.log(e) this.setData({ inputValue: e.detail.value }) },
handlerSearch(e) { console.log(this.data.inputValue) if (!this.isUrl(this.data.inputValue)) { console.log("error url: ", this.data.inputValue) wx.showToast({ icon: "error", title: '请输入正确url', }) return } this.setData({buttonStatus: true}) this.imageSearch() this.setData({buttonStatus: false}) }, handlerInputImage(e) { console.log(e) }})

复制代码


index.wxss:

/**index.wxss**/.userinfo {  display: flex;  flex-direction: column;  align-items: center;  color: #aaa;}
.userinfo-avatar { overflow: hidden; width: 128rpx; height: 128rpx; margin: 20rpx; border-radius: 50%;}
.usermotto { margin-top: 200px;}
page { background: rgb(255, 255, 255);}.page-section-spacing { width: 100%;}.container { padding: 0;}
.button_container { margin-top: 600rpx;}.flex-wrp{ margin-top: 60rpx; margin: auto; width: 90%; overflow: hidden;}
.form-item { margin-bottom: 10px; margin-top: 10px; width: 90%; overflow: hidden;}.flex-item{ float: left; width: 300rpx; height: 400rpx; font-size: 26rpx; margin: 10rpx 10rpx 0rpx 20rpx; overflow: hidden;}.flex-item-V{ margin: 0 auto; width: 300rpx; height: 200rpx;}

.input { width: 75%; float: left; height: 36px; border: 1px solid #ddd;}
.button { float: right; height: 36px; background: rgb(236, 111, 61); color: white;}
.view_line { width: 96%; height: 50rpx; display: flex; flex-direction: row; align-items: center; justify-content: left; margin: 0rpx 2% 0rpx 2%;}
.view_line view { width: 75%; height: 2rpx; background: linear-gradient(to right,#706f70, #706f70);}
.text_line { font-size: 25rpx; color: rgb(66, 66, 66); margin: 0rpx 2% 0rpx 2%;}.image { width: 100%;}.image image { width: 100%; background-size: auto 100%; background-repeat: no-repeat;}
复制代码


index.wxml:

<view class="container">   <div class="form-item">    <input placeholder=" 商品URL" class="input" bindinput="handlerInput" />    <button class="button"  loading="{{buttonStatus}}" bindtap="handlerSearch" size="mini">搜索 </button>   </div>   <view class="view_line">    <text class="text_line">输入图片</text>    <view></view>   </view>   <canvas      type="2d"      id="input_canvas"      style="background:rgb(228, 228, 225); width: 360px; height: 240px;"    ></canvas>   <view class="view_line">    <text class="text_line">搜索结果</text>    <view></view>   </view>     <view class="page-section-spacing">        <view  class="flex-wrp" style="flex-direction:row;">          <view wx:for="{{urls}}" class="flex-item">          <view class="image">             <image mode="aspectFit" style="background-color: #eeeeee;" src="{{item}}"></image>            </view>          </view>        </view>       </view>  </view>
复制代码


编译即可:


3.3 构建运行

输入商品图,图搜服务会进行主体识别,然后再进行相似图搜索,可以看到效果如下:


如果不做主体识别,可能搜不到我们想要的结果,例如,我们想要这个墨镜,但是不开主体识别的话可能会定位到衣服,导致没有结果:


看下图搜主体识别的文档介绍:


118px 使用商品图像搜索默认会开启主体识别,涉及到以下两个参数:


默认情况下主体识别是打开的,服务会针对输入图中的服饰进行主体位置识别,以实现更好的搜索效果,如下:


按照上述主体识别的描述,指定下配饰类目,服务会定位到墨镜的位置,就可以搜索到预期的商品了:


到这一步就实现了小程序中商品搜索的基本功能,涉及到商品搜索的场景,都可以参考下。后面给朋友展示了小程序 demo, 体验下来,使用效果相当满意。


3.4 查看调用量

在后续观察中,可以在腾讯云官网, 进入到图像分析控制台,可以查看最近的调用情况。


了解更多图像搜索功能:https://cloud.tencent.com/product/imagesearch

发布于: 刚刚阅读数: 8
用户头像

还未添加个人签名 2020.11.02 加入

还未添加个人简介

评论

发布
暂无评论
最佳实践|用腾讯云AI图像搜索打造属于自己的拍立淘_腾讯_牵着蜗牛去散步_InfoQ写作社区