• 拍立淘抠图体验优化总结


    d0e2a746fa5d46dd672f40dbe95f8938.gif

    本文将介绍拍立淘结果页从上线至今出现的体验问题及解决方案。 

    前言

    拍立淘结果页从上线至今,经历了许多年的迭代,始终保留了一个抠图的功能——用户可以在图片上自主选择要搜索的图片区域

    5fb8f958643f6c0be0dd619f0efcf2cc.gif

    功能经过这么多年使用,由于当初的实现没有那么精细,导致一直存在一些体验上的问题,用户重点反馈的内容包含以下几个部分:

    1. 抠图不易操作

    2. 找不到抠图框

    3. 小物件搜索不出来

    4. ....

    基于以上这些问题,我们进行了一一解决,下面进入正题。

    交互体验

      找不到框

    用户在进入结果页后,会在背景处显示一整张原图,由于底部存在结果面板,导致图片显示区域被压缩,所以会有一部分图片被隐藏,对于不了解功能的用户来说,不知道图片是可以滑动的,假如主体框刚好就在隐藏的图片区域里,用户就没法框选主体了。

    e457aa9fb4e036caaa90f46a4d968f1e.gif

      抠图不易操作

    原先的抠图组件存在一些bug

    • 触摸冲突

    d77b1854abbfef74dbf83eea57be3282.gif

    • 主动抠图后主体圆点缺失

    ba423db27a524b5a94590ed46c3ded34.gif

    • 主体点击无效

    0c64dc325cbe8184c0597645493fde3e.gif

      交互优化

    基于以上问题,我们对抠图交互进行了重构,具体改动如下

    1. 图片等比例缩放,整张图可以完整显示于面板上方

    2. 基础能力重构实现

    3. 新增图片缩放能力

    4. 新增图片移动能力

    6af52a7ae12a8d3b14990a6114e23608.gif

    ‍抠图组件包含以下几种基础操作

    1. 边框四个角调整

    2. 边框四个边调整

    3. 边框整体位置调整

    4. 主体圆点选中

    原先的抠图组件对于触摸的逻辑缺少封装设计,所有逻辑都糅合在一个类中,代码不易维护。重构后的组件对于抠图操作进行了抽象,基于手指的动作,抽象了以下接口

    1. /**
    2. * 前置判断
    3. */
    4. fun judge(fingerArray: SparseArray<FingerPos>, selectedObjectRegion: RegionPart, totalObjects: List<RegionPart>, selfDefinedObject: RegionPart): Boolean
    5. /**
    6. * 手指松开
    7. */
    8. fun onTouchRelease(selectedObjectRegion: RegionPart, totalObjects: List<RegionPart>, selfDefinedObject: RegionPart)
    9. /**
    10. * move 回调
    11. */
    12. fun onTouchEvent(fingerArray: SparseArray<FingerPos>, selectedObjectRegion: RegionPart, totalObjects: List<RegionPart>, selfDefinedObject: RegionPart): Boolean

    基于组件提供的操作,进行了以下封装

    cc55884b366488a298e6fb83c17cd73d.png

    后续若有新的操作,继承拓展即可

    

    对于触摸操作,我们定义了以下优先级,优先级从高到低排列

    1. 主体点击

    2. 图片缩放

    3. 四角调整

    4. 四边调整

    5. 框体位置调整

    6. 图片移动

    • 触摸流程如下

    e17a9b4d4fa9b7b584139f460c3b1433.png

    • 图片缩放

    由于图片整体缩放到了结果面板上方,如果用户想要搜一些特别小的物件,例如模特手上的手表,那么必然不好操作,因此我们增加了图片缩放的功能,图片缩放功能完全还原iOS系统相册的实现。

    78d2208a155be4ae37d8b2c99f3fc1b6.gif

    下面介绍一下缩放的实现。

    

    观察 iOS系统相册的缩放能力,可以发现,当双指缩放图片时,图片会基于双指中间那一点进行缩放,保证双指中间的内容是不变的。

    abf0969567c190c22a66bfa900077e96.gif

    并且当手指距离不变,围绕一个点旋转时,手指中间的那个像素是始终不变的。

    • 旋转

    101c3f61e396648b476bf5b7435bd9b1.gif

    • 平移

    1fad3d94f970129367073e160d2fba28.gif

    基于以上观察,我们可以得出基本的实现方案

    1. 双指按下时,计算出当前双指中间的像素点在图片上的绝对坐标

    2. 双指移动时,基于手指移动的距离,放大或者缩小图片

    3. 双指移动时,保证按下时的中间像素点始终保持在手指中间

    1. override fun onTouchEvent(fingerArray: SparseArray<FingerPos>, selectedObjectRegion: RegionPart, totalObjects: List<RegionPart>,selfDefinedObject : RegionPart): Boolean {
    2. targetView ?: return false
    3. if (fingerArray.size() < 2) {
    4. scale = -1f
    5. return true
    6. }
    7. val pos1 = fingerArray[0] ?: return true
    8. val pos2 = fingerArray[1] ?: return true
    9. if (scale < 0) {
    10. //刚触发双指缩放,记录当前scale 值
    11. scale = targetView!!.getImageView().scaleX
    12. ...
    13. //记录手指中间的像素坐标
    14. centerPosition = getCenterPosition(pos1, pos2)
    15. }
    16. //计算当前双指距离
    17. val currentDistance = sqrt((sqr(pos1.currX - pos2.currX) + sqr(pos1.currY - pos2.currY)).toDouble()).toFloat()
    18. //计算上一次触摸时的双指距离
    19. val lastDistance = sqrt((sqr(pos1.lastX - pos2.lastX) + sqr(pos1.lastY - pos2.lastY)).toDouble()).toFloat()
    20. //计算第一次进入双指缩放时的手指距离
    21. val startDistance = sqrt((sqr(pos1.startX - pos2.startX) + sqr(pos1.startY - pos2.startY)).toDouble()).toFloat()
    22. val delta = (currentDistance - lastDistance) / startDistance
    23. zoomScale += delta
    24. zoomScale = maxDelta.coerceAtMost(minDelta.coerceAtLeast(zoomScale))
    25. val currScale = scale * zoomScale
    26. targetView!!.setImageScale(currScale)
    27. //计算缩放后的手指中间像素坐标
    28. val currPos = getCenterPosition(pos1, pos2)
    29. //根据坐标和 scale,移动图片,保证图片在正确的位置
    30. moveImage((currPos[0] - centerPosition!![0]) * currScale, (currPos[1] - centerPosition!![1]) * currScale, false)
    31. return true
    32. }

    抠图流程

      流程梳理
    • 结果页流程

    1755c465738e4790ad87ce7f31222f34.png

    可以看到,在发送请求时,会先缩放一次图片(长边最长640,短边最长320),然后服务端处理完成后,同时将图片保存到存储服务上,生成链接,连同内容数据打包回传给端上,然后端上交付给前端进行渲染。

    • 用户抠图流程

    

    bf76b496365498b5b384080875c8e61b.png

      现有问题

    分析以上流程就会发现,客户端初始化请求时将图片进行了缩小,上传到服务端以后会得到一个图片链接,后续前端的所有请求都会基于这个链接。因此,如果用户想要搜索一个模特手上的手表,对于用户来说,手表看着挺大的,但是如果是缩放后的图,手表那一点大小,基本是不可识别的。

    6a9d7d3c5bfd9beb567e1a669c1ef820.gif

    可以看到,此时搜索后的结果和图片内容是完全无关的。

      优化


    我们对抠图后的流程做了调整,当用户想要搜索的图片区域做了调整后,端上将图片从原始图片(未缩放)中切割出来,重新走一遍图片上传流程,基于切割后的图进行商品召回,增大 query 图像的尺寸,提高准确度。

    847c8cda6583aaaada3a96cf746e187b.gif


    可以看到效果十分明显,优化后的搜索结果和商品相关度大幅度上涨。

    总结

    对于老代码,我们要勇于去优化重构,勿以点小而不为(手动滑稽~)目前整体改动已经跟随 10.15.0 版本上线,并且增加了 ab,相信数据上会有较大的提升~

    总结

    我们是大淘宝技术搜索推荐移动端团队,负责集团核心电商搜索推荐,图像视频搜索业务研发、技术平台建设、新业务和前沿技术探索等工作,我们负责的业务拥有亿级流量,能为您提供巨大的机遇和成长空间,期待您的加入。

    感兴趣的同学可将简历发送到 taozi.ly@taobao.com

    ✿  拓展阅读

    4bd17f317e13d5e5774733494e4912f5.jpeg

    848976c43104f8de99d1736006e8dc6d.jpeg

    作者| 薛贤俊(隽弦)

    编辑|橙子君

    b50ea526526968731d6642e6c9ddeeea.png

  • 相关阅读:
    DDS的一点理解
    代码随想录day42|背包问题基础|416. 分割等和子集|Golang
    源码漏洞扫描
    『MySQL快速上手』-⑤-数据类型
    Angular main 中的enableProdMode
    利用Nginx正向代理实现局域网电脑访问外网
    Vue Vuex模块化编码
    肝通宵写了三万字把SQL数据库的所有命令,函数,运算符讲得明明白白讲解,内容实在丰富,建议收藏+三连好评!
    20231016比赛总结
    如何在PostgreSQL中使用pg_stat_statements插件进行SQL性能统计和分析?
  • 原文地址:https://blog.csdn.net/Taobaojishu/article/details/126476395