导语:前端智能化,就是通过 AI/CV 技术,使前端工具链具备理解能力,进而辅助开发晋升研发效率,比方实现基于设计稿智能布局和组件智能辨认等。
本文要介绍的是我在前端智能化的实际:通过算法实现主动提取图片中的 UI 款式的能力。
具体成果如上图,当用户框选图片中蕴含组件的区域,算法能精确定位组件地位,并无效辨认组件的 UI 款式。
款式提取计划
对图像的款式检测波及计算机视觉畛域常识,本文基于 OpenCV 进行代码实现,次要分为三步:
- 从图片检测并拆散组件区域;
- 基于组件区域进行形态检测;
- 对合乎规定形态的组件进行款式计算。
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 = img.copy()
new_img = cv2.Canny(new_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)
取到线段汇合后,咱们再判断是否满足矩形边的特色:
- 存在两条程度方向线段和两条垂直方向线段
- 上线段到下线段间隔≈组件高度,左线段到右线段间隔≈组件宽度
代码实现
"""判断是否为矩形"""
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 块进行逐块剖析。
一开始,咱们采纳间接对圆弧点进行圆的曲线拟合,但因为圆角点的数据过于集中,拟合圆的误差很大,如图:
咱们晓得,圆角通过十字对称后能结构出一个圆形,因而,只有咱们确定了“圆角”的候选区域,结构十字轴对称图,就能够依据圆形拟合精确判断是否为满足圆角特色了。具体步骤如下:
- 假如存在圆角,用面积推算圆角半径,确定“候选区域”
- 结构“候选区域”程度 - 竖直轴对称图形,对图形进行霍夫圆环检测,验证是否为圆角
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. 外轮廓和内轮廓是形态类似的。基于这个特色,我制订了以下步骤:
- 色块拆散:对图像基于色彩聚类,相近色区聚类同一色块
- 遍历不同色块,提取每个色块内外轮廓,并计算其类似度
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,还须要借助深度卷积网络进行组件检测与辨认。
更多文章欢送关注:https://www.zhihu.com/people/yonechen/posts
相干材料
最全综述 | 图像宰割算法:https://zhuanlan.zhihu.com/p/…
pHash 图像类似度比拟算法汇总:https://blog.csdn.net/mago201…
机器学习算法实际——K-Means 算法与图像宰割:https://blog.csdn.net/google1…
霍夫变换:https://en.wikipedia.org/wiki…
Suzuki85 轮廓跟踪算法:https://blog.csdn.net/yiqiudr…