导语:前端智能化,就是通过AI/CV技术,使前端工具链具备理解能力,进而辅助开发提升研发效率,比如实现基于设计稿智能布局和组件智能识别等。

本文要介绍的是我在前端智能化的实践:通过计算机视觉技术实现自动提取图片中的UI样式的能力。


具体效果如上图,当用户框选图片中包含组件的区域,算法能准确定位组件位置,并有效识别组件的UI样式。

样式提取方案

本文基于OpenCV-Python实现图像的样式检测,主要分为三步:1. 从图片检测并分离组件区域;2. 基于组件区域进行形状检测;3. 对符合规则形状的组件进行样式计算。

1. 从图片分离组件区域

组件区域分离主要是通过图像分割算法,识别组件区域(前景)和背景区域,本文主要从用户框选操作上考虑,采用了可交互可迭代的Grab Cut算法。Grab cut 算法允许用户框选作为前景输入,利用混合高斯模型GMM,找到前景和背景的最佳分割路径,具体可参考文章:图像分割——Grab Cut算法


如上图,通过调用OpenCV的cv2.grabCut方法时,我们将组件前景框(x, y, width, height)作为方法入参,识别出的组件像素被存储在mask遮罩。

代码实现

def extract(img, rect):    """输入框选区,输出GrabCut遮罩"""    x, y, w, h = rect    roi_img = img[y:y+h, x:x+h]    mask = np.zeros(roi_img.shape[:2], np.uint8) # 初始化遮罩层    bgdModel = np.zeros((1, 65), np.float64)    fgdModel = np.zeros((1, 65), np.float64)    # 函数的返回值是更新的 mask, bgdModel, fgdModel    cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 4, cv2.GC_INIT_WITH_RECT)    mask = np.where((mask == 2) | (mask == 0), 0, 255).astype("uint8")    return mask

通过这一步,我们从背景分离出目标遮罩,它是包含了N个组件区域的二值图。

2. 组件的形状检测

接下来,我们需要通过形状检测从遮罩区筛选出多个可用样式还原的组件,比如矩形、带圆角矩形和圆形。具体分为两步:1) 提取组件外轮廓 2) 霍夫检测识别轮廓形状

2.1 外轮廓提取

第一步是通过前面图割遮罩进行外轮廓提取,排除组件内部其它线条带来的影响。轮廓提取主要使用Suzuki85轮廓跟踪算法,该算法基于二值图像拓补,能确定连通域的包含关系。


这里采用的是Canny边缘检测来得到图像边缘图,再通过Suzuki85算法cv2.findContours从图像边缘提取外轮廓。

代码实现

def separate(img, th=5):    """输入组件区域遮罩,输出多个组件外轮廓列表"""    new_img = cv2.Canny(img, 50, 150)    new_img = image_morphology(new_img)    cnts, _ = cv2.findContours(new_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)    data = []    for cnt in cnts:        x, y, w, h = cv2.boundingRect(cnt)        if (w         """剔除噪点"""            continue        data.append((cnt, x, y, w, h))    return data

这一步我们得到了图像中所有组件的外轮廓以及具体的坐标x,y和宽高w,h

2.2 形状检测

第二步则是对每个组件外轮廓进行图形类型识别,其中除了矩形、圆形是样式可还原图形,其它都不可还原,我们的目标就是检测出这两种基本图形。


这里运用霍夫变换(Hough Transform)方法,它是一种识别几何形状的算法,主要采用投票机制从多个特征点拟合图像中线段和曲线的参数方程。


2.2.1 矩形检测

检测矩形主要分两步:1)通过霍夫直线变换检测外轮廓的边;2)根据边(线段)集合判断是否符合矩形特征。

OpenCV提供线段检测方法cv2.HoughLinesP,输入外轮廓,输出检测到的线段,具体代码实现如下:

# 检测矩形def detectRectangle(img, width, height):    minLineLength = 10    maxLineGap = 4    # 霍夫直线变换输出检测到的线段数组    lines = cv2.HoughLinesP(img, 1, np.pi/180, 100, minLineLength, maxLineGap)    segments = lines.reshape(lines.shape[0], 4)    # 将线段数组进行进一步检测,判断是否命中矩形规则    return judgeRectangle(segments, width, height)

取到线段集合后,我们再判断是否满足矩形边的特征:1. 存在两条水平方向线段和两条垂直方向线段 1. 上线段到下线段距离≈组件高度,左线段到右线段距离≈组件宽度

代码实现

"""判断是否为矩形"""def judgeRectangle(lines, width, height, x=0, y=0):    th = 2    horizontal_segments = lines[np.where(abs(lines[:, 1] - lines[:, 3])     vertical_segments = lines[np.where(abs(lines[:, 0] - lines[:, 2])     isRect = False    h = w = None    if horizontal_segments.size != 0:        horizontal_centers = (            horizontal_segments[:, 1] / 2 + horizontal_segments[:, 3] / 2        )        top = horizontal_centers.min()        bottom = horizontal_centers.max()        h = bottom - top        if abs(h - height) > th:            return False, None, None  # 如果两线间隔非图形高度,则不规则图片        isRect = True        h = int(round(h))    if vertical_segments.size != 0:        vertical_centers = vertical_segments[:, 0] / 2 + vertical_segments[:, 2] / 2        left = vertical_centers.min()        right = vertical_centers.max()        w = right - left        if abs(w - width) > th:            return False, None, None        isRect = True        w = int(round(w))    return isRect, w, h

2.2.2 圆形检测

圆形检测可使用霍夫圆环检测法,对应OpenCV的HoughCircles方法,输入二值图,如果存在圆形,则返回圆形和半径。代码实现如下:

# 检测圆形def detectCircle(img, width, height):    circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,param1=30,param2=15,minRadius=10,maxRadius=0)    if circles is None: return False    [radius, rx, ry] = circles[0]    return judgeCircle(radius, rx, ry, width, height)

def judgeCircle(r, rx, ry, w, h, x=0, y=0, th=4):    return (        (abs(w - h)         & (abs(r - w / 2)         & (abs(rx - x - w / 2)         & (abs(ry - y - h / 2)     )

通过这一步,我们筛选出属于矩形或圆形的组件,以及组件的宽高、圆形以及对应的半径,下一步,我们将针对这两种基本图形进行样式检测。

3. 组件的样式计算

组件样式计算主要对边框、圆角、背景三种常用样式分别计算。

3.1 圆角计算

在样式定义中,圆角被限制在矩形的四个顶点处,圆角弧度取决于它的半径,因此圆角计算的主要目标就是识别圆角的半径。根据圆角的4个方位,我们将组件区域划分为4块进行逐块分析。一开始,我们采用直接对圆弧点进行圆的曲线拟合,但由于圆角点的数据过于集中,拟合圆的误差很大,如图:


我们知道,圆角经过十字对称后能构造出一个圆形,因此,只要我们确定了“圆角”的候选区域,构造十字轴对称图,就可以根据圆形拟合准确判断是否为满足圆角特征了。具体步骤如下:

  1. 假设存在圆角,用面积推算圆角半径,确定“候选区域”
  2. 构造“候选区域”水平-竖直轴对称图形,对图形进行霍夫圆环检测,验证是否为圆角

3.1.1 圆角半径推算

我们假设存在圆角,半径为R,如下图黄色色块区域,是组件框与填充组件的差集。

同时,黄色块也是以边长R为正方形与半径R为1/4圆的差集,即s = R² - π × R² × ¼,于是联立方程,可求解圆角半径R,代码如下:

这一步我们根据面积差集计算出半径R,通过R,我们裁剪出“候选区域”,进行下一步验证。

3.1.2 候选区域验证

这一步先构造轴对称图像,主要是在水平和竖直方向依次做翻转+拼接操作。


如图,得到对称图形后,我们沿用上文的霍夫圆环变换来检测是否存在圆形,如果存在,则圆角也存在,反之亦然。

代码实现

# 推算可能的圆角半径def getCornerRadius(img):    cornerRadius = 0    corner_mask_size = img[img[:, :, 3] != 1].size    #     if corner_mask_size >= 0:        cornerRadius = round(math.sqrt((corner_mask_size / 3) / (1 - np.pi / 4)))    return cornerRadius

# 验证候选区域是否为圆角,以左上圆角为例def vertifyCorner(img, cornerRadius):    cornerArea = img[:cornerRadius, :cornerRadius] # 裁剪出候选区域    binary_image = np.zeros(cornerArea.shape[0:2],dtype=np.uint8) # 构造二值图    binary_image[cornerArea[:,:,3] != 0] = 255    horizontal = cv2.flip(img, 1, dst=None) # 水平镜像    img=cv2.hconcat([img, horizontal]) # 水平拼接    vertical = cv2.flip(img, 0, dst=None) # 垂直镜像    img=cv2.vconcat([img, vertical]) # 垂直拼接    img = cv2.copyMakeBorder(img, 5, 5, 5, 5, cv2.BORDER_CONSTANT, value = [0])    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT,1,20)    if circles is None: return False    else: return True

3.2 边框计算

对于边框的计算,我们同样是先确定边框的描述特征:A. 边框内的颜色连续与相近;B. 外轮廓和内轮廓是形状相似的。基于这个特征,我制定了以下步骤:

  1. 色块分离:对图像基于颜色聚类,相近色区聚类同一色块
  2. 内外轮廓相似度计算:遍历不同色块,提取每个色块内外轮廓,并计算其相似度

3.2.1 色块分离

边框具有颜色相近的特征,我们通过聚类算法对目标图像让颜色相近的区域归类,这里采用k-means算法聚类,聚类特征基于图像的HSV色彩空间。

代码实现

"""k-means聚类"""def image_kmeansSegement(img, k=6):    # 将图片从RGB空间转为HSV    img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)    data = img.reshape((-1, 3))    data = np.float32(data)

    # MAX_ITER最大迭代次数,EPS最高精度    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)    num_clusters = k    ret, label, center = cv2.kmeans(        data, num_clusters, None, criteria, num_clusters, cv2.KMEANS_RANDOM_CENTERS    )

    center = cv2.cvtColor(np.array([center], dtype=np.uint8), cv2.COLOR_HSV2BGR)[0]    labels = label.flatten()    return labels, center

3.2.2 内外轮廓相似度计算

这一步是遍历k个候选色块,对色块分别进行外轮廓和内轮廓提取,再判断色块内外轮廓是否形状相似。其中外轮廓的提取直接复用前面的cv2.findContours方法,输入色块,输出外轮廓填充图。内轮廓则需要分两步,首先对外轮廓填充图与色块填充图进行差运算得到“内域”,再对内域进行cv2.findContours


拿到内外轮廓后,我使用感知哈希pHash + 汉明距离进行相似度计算,它主要通过颜色低采样将图片统一缩小到32×32尺寸并输出图像签名,很好地解决相似形状中大小不一致带来的误差。

代码实现

"""验证每个色块是否存在边框特征B"""def borderExtract(labels, center, img_filled):    # 遍历k-means分离的k个色块    for i in range(labels.max()):        area = np.zeros((labels.size), dtype=np.uint8)        area[labels == i] = 255        area = area.reshape(img_filled.shape)        # 获取当前色块外轮廓,用白色填充        outter_filled, *_ = image_contours(area)        # 获取当前色块内轮廓,用白色填充        result = outter_filled - area        result[result 0] = 0        inner_filled, *_ = image_contours(result)        # 判断外轮廓和内轮廓是否相似        if isSimilar(outter_filled, inner_filled) & isSimilar(img_filled, filled1):            s1 = np.where(filled1 > 0)[0].size            s2 = np.where(filled2 > 0)[0].size            scale = (1.0 - math.sqrt(s2 / s1)) * 0.5            _drawBorder(filled1 - filled2, center[i])            return scale, center[i], filled2    return None

"""使用pHash算法计算轮廓之间相似度"""def isSimilar(img1, img2, th=0.8):    HASH1 = PHash.pHash(img1)    HASH2 = PHash.pHash(img2)    distance, score = PHash.hammingDist(HASH1, HASH2)    print(score)    return score > th

总结

本文通过OpenCV系列算法分别实现简单组件区域的分离和样式的检测,对于组件的区域检测,目前是通过手工框选的手段确定组件区域,如果要完全自动化实现Pixels to Code,还需要借助深度卷积网络进行组件检测与识别。

本人将于9月5号参与腾讯live开发者大会,届时将介绍更多前端智能化实践内容,欢迎有兴趣童鞋前来观摩腾讯Live开发者大会2020.tlc.ivweb.io更多文章欢迎关注WebXR技术庄园zhuanlan.zhihu.com

相关资料

最全综述 | 图像分割算法:https://zhuanlan.zhihu.com/p/70758906

pHash图像相似度比较算法汇总:https://blog.csdn.net/mago2015/article/details/81137089

机器学习算法实践——K-Means算法与图像分割:https://blog.csdn.net/google19890102/article/details/52911835

霍夫变换:https://en.wikipedia.org/wiki/Hough_transform

Suzuki85轮廓跟踪算法:https://blog.csdn.net/yiqiudream/article/details/76864722

opencv裁剪图片_前端智能化实践——从图片识别UI样式相关推荐

  1. blob 图片_前端JS实现字符串/图片/excel文件下载

    编者按:本文转载自SF专栏,由作者 赵帅强 授权奇舞周刊转载 在web开发中,如果你想让用户下载或者导出一个文件,应该怎么做呢? 传统的做法是在后端存储或者即时生成一个文件来提供下载功能,这样的优势是 ...

  2. 前端智能化实践(附:D2 前端技术论坛 PPT 合集)

    大家好,我们是来自阿里巴巴淘系技术部的狼叔.卓风.感谢 D2 组委会,让我们有机会在这里分享,关于<前端智能化实践-- P2C 从需求文档生成代码>. 淘系技术微信公众号后台回复「D2」即 ...

  3. 【PPT】阿里@甑子:《前端智能化实践》——逻辑代码生成

    在第十四届D2前端技术论坛阿里高级前端技术专家@甄子做了<前端智能化实践>-逻辑代码生成的分享. 本PPT由@甄子授权分享.

  4. alert()的功能_前端实现简单的图片上传小图预览功能

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  5. vue 图片拖动加载 类似于地图_前端性能优化之图片懒加载(附vue自定义指令)...

    作者:lzg9527 链接:https://juejin.cn/post/6903774214780616718 在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列 ...

  6. 单元测试用例_前端单元测试实践

    一说到单元测试,可能对于业务一线同学来说,心理立马就会无形中有一种压迫感,心想 "业务都做不完了,写个球的单元测试,先保证功能完备,赶紧上线才是王道",这句话的核心是以业务为重,没 ...

  7. jquery 获取元素图片_【jQuery】获取图片真实宽高

    jQuery有没有获取图片实际尺寸的方法? 就是图片文件的实际尺寸,而不是添加了css样式之后的尺寸. 肥肠真实,如假包换! //code from http://caibaojian.com/jqu ...

  8. 后台返回的base64的图片格式,前端如何转为普通的图片格式

    在上一篇的博客当中,写了前端如何将普通的图片格式转为base64的图片,今天开发的时候遇到了后台返回的图片格式是base64的,我这边需要把base64的图片格式转为普通的,搜了一下js的方法,感觉很 ...

  9. java word 转换 图片_怎样把手机上的图片转换成word?

    在当今这个快速发展的时代,人手一部手机是件很正常的事,小编的手机上往往会存有许多喜欢的文字图片,可是那么多的图片又会占用很多手机的空间,这样就有些不好了.庆幸的是最近小编学习到一个可以将手机上的图片转 ...

最新文章

  1. 4. Brackets 前端编辑器试用
  2. pycharm打开python文件如何自动创建项目_pycharm 怎么自动创建python头文件?
  3. 雅虎卖身Verizon对微软必应有什么影响?
  4. 为什么SQL正在击败NoSQL,这对未来的数据意味着什么
  5. 使用alembic进行openstack数据库版本管理
  6. stl字符串去除空格_在列表中推送字符并在C ++ STL中将它们打印为空格
  7. PX4环境git submodule update --init --recursive失败的解决办法
  8. leetcode[541]翻转字符串里的单词/reverse words in a string 综合考察了字符串的多种操作
  9. BZOJ3309 DZY Loves Math
  10. 大学c语言程序设计上机题库,c语言上机题库(c语言程序设计基础题库)
  11. 易赛自动充值软件介绍
  12. 安卓基于图像识别和CNN做出一个通用的斗地主记牌器(一)
  13. 关于C语言颜色改变方法
  14. 西安交大计算机2000年山西专业录取分数,西安交大在山西各专业录取分数.doc
  15. 视频2-视频文件解析和格式说明
  16. Excel教程视频《Excel大神上分攻略》50个工作场景,从案例到实践
  17. Keil:Undefined symbol ......(referred from......)解决
  18. 微信小程序开发之小米商城
  19. 信息学奥赛一本通(c++):2024:【例4.10】末两位数
  20. 移动端地图开发技术分享交流会PPT

热门文章

  1. WebService 的CXF框架 WS方式Spring开发
  2. 按钮下载Eclipse Color Theme
  3. myeclipse的优化
  4. vs2008+sp1
  5. document.write vs document.getElementById
  6. SVM中的间隔最大化
  7. Spring Boot Questions- Part 1
  8. MySQL用户管理和权限设置
  9. 控件包含代码块(即% ... %),因此无法修改控件集合解决
  10. 【Java从0到架构师】SSM 整合(XML、纯注解)