百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

前端智能化实践:从图片识别UI样式

toyiye 2024-07-04 09:14 11 浏览 0 评论

前端智能化,就是通过 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 < th) | (h < th):
        """剔除噪点"""
            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. 存在两条水平方向线段和两条垂直方向线段;
  2. 上线段到下线段距离≈组件高度,左线段到右线段距离≈组件宽度。

代码实现

"""判断是否为矩形"""
def judgeRectangle(lines, width, height, x=0, y=0):
    th = 2
    horizontal_segments = lines[np.where(abs(lines[:, 1] - lines[:, 3]) < th)]
    vertical_segments = lines[np.where(abs(lines[:, 0] - lines[:, 2]) < th)]
    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) < th)
        & (abs(r - w / 2) < th)
        & (abs(rx - x - w / 2) < th)
        & (abs(ry - y - h / 2) < th)
    )

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

3. 组件的样式计算

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

3.1 圆角计算

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

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

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

3.1.1 圆角半径推算

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

同时,黄色块也是以边长 R 为正方形与半径 R 为 1/4 圆的差集,即s = R2 - π × R2 × ?,于是联立方程,可求解圆角半径 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. 外轮廓和内轮廓是形状相似的。基于这个特征,我制定了以下步骤:

色块分离:对图像基于颜色聚类,相近色区聚类同一色块;

内外轮廓相似度计算:遍历不同色块,提取每个色块内外轮廓,并计算其相似度。

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,还需要借助深度卷积网络进行组件检测与识别。

本文转载自公众号 YONE 小栈。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码