Golang+Elasticsearch轻松搭建AI时代的图片搜索服务

1、图片搜索的产品形式

图片搜索的产品形式非常简单,上传一张图,搜索出与原图相似度较高的图,比如百度的图搜:

https://image.baidu.com/

接下来,我们就搭建一个简易版的图搜服务(受限于没有服务器,我就在mac上跑了)

2、golang webserver框架选型

golang的webserver框架,小而美的首推gin,也是当下golang圈子里最流行的框架,官方文档如下:


https://gin-gonic.com/zh-cn/docs/

github地址:

https://github.com/gin-gonic/gin

 安装gin:

go get -u github.com/gin-gonic/gin

gin是一个使用golang开发的web框架,性能极佳,开发成本低,中文文档丰富,github上拥有近80k的star,是golang开发者的首选。

gin框架开发hello world版本:

package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

框架选型完成后,接下来就是要设计Restful api了。 

 3、图搜restful api设计

图像检索服务吗,肯定是以图搜图,所以api的设计上,api输入为图片,单张多张技术上都没有问题,我们本次的教程就设置为一张,之后返回和这张图片类似的top k张图。

api url设计为post请求,url定义如下:

mianhuatang/image_search

路由设置代码参考如下:

func Register(engine *gin.Engine) {
    engine.Use(middlewares.Print)
    engine.POST("mianhuatang/image_search", controllers.ImageSearch)
}

接口输入,只接收一张图:

参数名称

类型

说明

file

文件类型

用于上传图片

接口输出,返回所有与输入图片相似的图片的链接,这个链接最好是对象存储的地址,这样方便前端直接展示搜索结果,百度的BOS,阿里的OSS都是不错的选择,接口返回的数据结构如下:

{
    "err_num": 0,
    "err_msg": "操作成功",
    "data": [
        {
            "image_url": "cat.353.jpg",
            "name": "cat_823"
        },
        {
            "image_url": "cat.127.jpg",
            "name": "cat_579"
        }
    ]
}

ok,接口设计完成,接下来看如何抽取图片特征。

4、图片特征在线提取

在我的另一篇文章《吭哧一天才搞定:elasticsearch向量检索需要的数据集以及768维向量生成》,我们找了一个openai的开源模型,使用python实现了特征的提取,在这里我们仍旧使用这个方案,只不过土鳖一点,在go的代码里以命令行的方式去调用这个python脚本(后面尝试用tritonserver工程化这个模型,我去实现一个自定义的bankend,大家可以蹲个后续),首先对这个脚本稍加修改: 

1、脚本支持命令行参数,输入一个路径。2、服务把api上传的图片保存到某个地方,之后调用脚本,输入路径,提取图片特征。

要增加的代码主要就是接收命令行参数,其他变化不大:

if __name__ == "__main__":
    args = sys.argv[1:]  # 获取所有传递给脚本的参数
    image_path = args[0]
    main(image_path)

完整代码我会放到文章最后。

5、golang部分实现逻辑

当server端将图片保存到本地后,调用python脚本抽取特征,因为是在golang里调用python脚本运行,go只能去捕获脚本输出以得到图片特征向量,在python脚本里通过print将向量进行打印,golang捕获字符串,之后再转换为float64类型的切片,python脚本的输出类似这样:

[[-0.011099960654973984, 0.05776820704340935, 0.03730260953307152, 0.03493053838610649]]

go核心部分代码如下:

func ImageSearch(ctx *gin.Context, fileName string, response *result.JsonResponseString) {
    floatVec := make([]float64, 0)
    curPath, _ := os.Getwd()
    // 运行 Python 脚本并捕获输出
    cmd := exec.Command("python3", curPath+"/features/feature.py", curPath+"/images/")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        fmt.Println("Error running Python script:", err)
        return
    }
    // 打印 Python 脚本的输出
    vecs := out.String()
    start := strings.LastIndex(vecs, "[")
    end := strings.LastIndex(vecs, "]")
    content := vecs[start+1 : end-1]
    sliVecs := strings.Split(content, ", ")
    // 将字符串切片转换为浮点数切片,拿到特征向量
    for _, v := range sliVecs {
        valu, _ := strconv.ParseFloat(v, 64)
        floatVec = append(floatVec, valu)
    }
}

6、执行向量检索

上面章节已经获取到检索图片的特征向量,那么执行检索就简单了,无非就是构造向量检索语句,去es检索即可,核心代码:

func ImageSearch(client *elasticsearch.Client, query string) []map[string]any {
    results := make([]map[string]any, 0)
    docs := Documents{}
    search := esapi.SearchRequest{
        Index: []string{"vector_search_202412"},
        Body:  strings.NewReader(query),
    }
    resp, err := search.Do(context.Background(), client)
    if err != nil {
        fmt.Println("search err:", err.Error())
        return results
    }
    err = json.NewDecoder(resp.Body).Decode(&docs)
    if err != nil {
        fmt.Println("decode err:", err.Error())
        return results
    }
    for _, v := range docs.Hits.Hits {
        result := make(map[string]any)
        result["name"] = v.Source["name"]
        result["image_url"] = v.Source["path"]
        results = append(results, result)
    }
    return results
}

构建dsl代码:

mapQuery := map[string]any{
	"size": 10,
	"query": map[string]any{
		"knn": map[string]any{
			"field":        "IFV",
			"k":            10,
			"query_vector": floatVec,
		},
	},
}
dsl, _ := json.MarshalToString(mapQuery)
results := data.ImageSearch(resource.ElasticClient, dsl)

 接口的返回示例:

到这里,整个图片检索的服务就开发完成了,实际项目中,完善下代码健壮性可以直接拿去用。

代码已上传到github,地址:

https://github.com/liupengh3c/career

最后欢迎各位小伙伴关注公众号【码农夜读】,可以免费获得一份《c++ primer plus》视频教程一份。

另外可以帮助下载 csdn 上 vip 资源(不要钱,😄),可以在公众号加微信,发链接,我来下载,之后发给你。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值