前端+ AI ——從圖片識別UI樣式

blank

前端+ AI ——從圖片識別UI樣式

導語:前端智能化,就是通過AI/CV技術,使前端工具鏈具備理解能力,進而輔助開發提升研發效率,比如實現基於設計稿智能佈局和組件智能識別等。

本文要介紹的是前端智能化的一類實踐:通過計算機視覺和機器學習實現自動提取圖片中的UI樣式的能力。

blank

具體效果如上圖,當用戶框選圖片中包含組件的區域,算法能準確定位組件位置,並有效識別組件的UI樣式。

樣式提取方案

本文基於OpenCV -Python實現圖像的樣式檢測,主要分為三步: 1.從圖片檢測並分離組件區域; 2.基於組件區域進行形狀檢測; 3.對符合規則形狀的組件進行樣式計算。

1. 從圖片分離組件區域

組件區域分離主要是通過圖像分割算法,識別組件區域(前景)和背景區域,本文主要從用戶框選操作上考慮,採用了可交互可迭代的Grab Cut算法。 Grab cut算法允許用戶框選作為前景輸入,利用混合高斯模型GMM,找到前景和背景的最佳分割路徑,具體可參考文章: 圖像分割——Grab Cut算法

blank

如上圖,通過調用OpenCV的cv2.grabCut方法時,我們將組件前景框(x, y, width, height)作為方法入參,識別出的組件像素被存儲在mask遮罩。

代碼實現

defextract(img,rect):"""輸入框選區,輸出GrabCut遮罩"""x,y,w,h=rectroi_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, fgdModelcv2.grabCut(img,mask,rect,bgdModel,fgdModel,4,cv2.GC_INIT_WITH_RECT)mask=np.where((mask==2)|(mask==0),0,255).astype("uint8")returnmask

通過這一步,我們從背景分離出目標遮罩,它是包含了N個組件區域的二值圖。

2. 組件的形狀檢測

接下來,我們需要通過形狀檢測從遮罩區篩選出多個可用樣式還原的組件,比如矩形、帶圓角矩形和圓形。具體分為兩步:1) 提取組件外輪廓2) 霍夫檢測識別輪廓形狀

2.1 外輪廓提取

第一步是通過前面圖割遮罩進行外輪廓提取,排除組件內部其它線條帶來的影響。輪廓提取主要使用Suzuki85輪廓跟踪算法,該算法基於二值圖像拓補,能確定連通域的包含關係。

blank

這裡採用的是Canny邊緣檢測來得到圖像邊緣圖,再通過Suzuki85算法cv2.findContours從圖像邊緣提取外輪廓。

代碼實現

defseparate(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=[]forcntincnts:x,y,w,h=cv2.boundingRect(cnt)if(w<th)|(h<th):"""剔除噪點"""continuedata.append((cnt,x,y,w,h))returndata

這一步我們得到了圖像中所有組件的外輪廓以及具體的坐標x,y和寬高w,h

2.2 形狀檢測

第二步則是對每個組件外輪廓進行圖形類型識別,其中除了矩形、圓形是樣式可還原圖形,其它都不可還原,我們的目標就是檢測出這兩種基本圖形。

blank

這裡運用霍夫變換(Hough Transform)方法,它是一種識別幾何形狀的算法,主要採用投票機制從多個特徵點擬合圖像中線段和曲線的參數方程。

blank

2.2.1 矩形檢測

檢測矩形主要分兩步:1)通過霍夫直線變換檢測外輪廓的邊;2)根據邊(線段)集合判斷是否符合矩形特徵。

blank

OpenCV提供線段檢測方法cv2.HoughLinesP ,輸入外輪廓,輸出檢測到的線段,具體代碼實現如下:

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

取到線段集合後,我們再判斷是否滿足矩形邊的特徵: 1. 存在兩條水平方向線段和兩條垂直方向線段1. 上線段到下線段距離≈組件高度,左線段到右線段距離≈組件寬度

代碼實現

"""判斷是否為矩形"""defjudgeRectangle(lines,width,height,x=0,y=0):th=2horizontal_segments=lines[np.where(abs(lines[:,1]-lines[:,3])<th)]vertical_segments=lines[np.where(abs(lines[:,0]-lines[:,2])<th)]isRect=Falseh=w=Noneifhorizontal_segments.size!=0:horizontal_centers=(horizontal_segments[:,1]/2+horizontal_segments[:,3]/2)top=horizontal_centers.min()bottom=horizontal_centers.max()h=bottom-topifabs(h-height)>th:returnFalse,None,None#如果兩線間隔非圖形高度,則不規則圖片isRect=Trueh=int(round(h))ifvertical_segments.size!=0:vertical_centers=vertical_segments[:,0]/2+vertical_segments[:,2]/2left=vertical_centers.min()right=vertical_centers.max()w=right-leftifabs(w-width)>th:returnFalse,None,NoneisRect=Truew=int(round(w))returnisRect,w,h

2.2.2 圓形檢測

圓形檢測可使用霍夫圓環檢測法,對應OpenCV的HoughCircles方法,輸入二值圖,如果存在圓形,則返回圓形和半徑。代碼實現如下:

#檢測圓形defdetectCircle(img,width,height):circles=cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,param1=30,param2=15,minRadius=10,maxRadius=0)ifcirclesisNone:returnFalse[radius,rx,ry]=circles[0]returnjudgeCircle(radius,rx,ry,width,height)defjudgeCircle(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. 組件的樣式計算

組件樣式計算主要對邊框、圓角、背景三種常用樣式分別計算。

blank

3.1 圓角計算

在樣式定義中,圓角被限制在矩形的四個頂點處,圓角弧度取決於它的半徑,因此圓角計算的主要目標就是識別圓角的半徑。根據圓角的4個方位,我們將組件區域劃分為4塊進行逐塊分析。一開始,我們採用直接對圓弧點進行圓的曲線擬合,但由於圓角點的數據過於集中,擬合圓的誤差很大,如圖:

blank

我們知道,圓角經過十字對稱後能構造出一個圓形,因此,只要我們確定了“圓角”的候選區域,構造十字軸對稱圖,就可以根據圓形擬合準確判斷是否為滿足圓角特徵了。具體步驟如下:

  1. 假設存在圓角,用面積推算圓角半徑,確定“候選區域”
  2. 構造“候選區域”水平-豎直軸對稱圖形,對圖形進行霍夫圓環檢測,驗證是否為圓角

3.1.1 圓角半徑推算

我們假設存在圓角,半徑為R,如下圖黃色色塊區域,是組件框與填充組件的差集。

blank

同時,黃色塊也是以邊長R為正方形與半徑R為1/4圓的差集,即s = R² - π × R² × ¼ ,於是聯立方程,可求解圓角半徑R,代碼如下:

這一步我們根據面積差集計算出半徑R,通過R,我們裁剪出“候選區域”,進行下一步驗證。

3.1.2 候選區域驗證

這一步先構造軸對稱圖像,主要是在水平和豎直方向依次做翻轉+拼接操作。

blank

如圖,得到對稱圖形後,我們沿用上文的霍夫圓環變換來檢測是否存在圓形,如果存在,則圓角也存在,反之亦然。

代碼實現

#推算可能的圓角半徑defgetCornerRadius(img):cornerRadius=0corner_mask_size=img[img[:,:,3]!=1].size#ifcorner_mask_size>=0:cornerRadius=round(math.sqrt((corner_mask_size/3)/(1-np.pi/4)))returncornerRadius#驗證候選區域是否為圓角,以左上圓角為例defvertifyCorner(img,cornerRadius):cornerArea=img[:cornerRadius,:cornerRadius]#裁剪出候選區域binary_image=np.zeros(cornerArea.shape[0:2],dtype=np.uint8)#構造二值圖binary_image[cornerArea[:,:,3]!=0]=255horizontal=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)ifcirclesisNone:returnFalseelse:returnTrue

3.2 邊框計算

對於邊框的計算,我們同樣是先確定邊框的描述特徵:A. 邊框內的顏色連續與相近;B. 外輪廓和內輪廓是形狀相似的。基於這個特徵,我制定了以下步驟:

  1. 色塊分離:對圖像基於顏色聚類,相近色區聚類同一色塊
  2. 內外輪廓相似度計算:遍歷不同色塊,提取每個色塊內外輪廓,併計算其相似度

3.2.1 色塊分離

邊框具有顏色相近的特徵,我們通過聚類算法對目標圖像讓顏色相近的區域歸類,這裡採用k-means算法聚類,聚類特徵基於圖像的HSV色彩空間

blank

代碼實現

"""k-means聚類"""defimage_kmeansSegement(img,k=6):#將圖片從RGB空間轉為HSVimg=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=kret,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()returnlabels,center

3.2.2 內外輪廓相似度計算

這一步是遍歷k個候選色塊,對色塊分別進行外輪廓和內輪廓提取,再判斷色塊內外輪廓是否形狀相似。其中外輪廓的提取直接復用前面的cv2.findContours方法,輸入色塊,輸出外輪廓填充圖。內輪廓則需要分兩步,首先對外輪廓填充圖與色塊填充圖進行差運算得到“內域”,再對內域進行cv2.findContours

blank

拿到內外輪廓後,我使用感知哈希pHash +漢明距離進行相似度計算,它主要通過顏色低採樣將圖片統一縮小到32×32尺寸並輸出圖像簽名,很好地解決相似形狀中大小不一致帶來的誤差。

代碼實現

"""驗證每個色塊是否存在邊框特徵B"""defborderExtract(labels,center,img_filled):#遍歷k-means分離的k個色塊foriinrange(labels.max()):area=np.zeros((labels.size),dtype=np.uint8)area[labels==i]=255area=area.reshape(img_filled.shape)#獲取當前色塊外輪廓,用白色填充outter_filled,*_=image_contours(area)#獲取當前色塊內輪廓,用白色填充result=outter_filled-arearesult[result<0]=0inner_filled,*_=image_contours(result)#判斷外輪廓和內輪廓是否相似ifisSimilar(outter_filled,inner_filled)&isSimilar(img_filled,filled1):s1=np.where(filled1>0)[0].sizes2=np.where(filled2>0)[0].sizescale=(1.0-math.sqrt(s2/s1))*0.5_drawBorder(filled1-filled2,center[i])returnscale,center[i],filled2returnNone"""使用pHash算法計算輪廓之間相似度"""defisSimilar(img1,img2,th=0.8):HASH1=PHash.pHash(img1)HASH2=PHash.pHash(img2)distance,score=PHash.hammingDist(HASH1,HASH2)print(score)returnscore>th

總結

本文通過OpenCV系列算法分別實現簡單組件區域的分離和样式的檢測,對於組件的區域檢測,目前是通過手工框選的手段確定組件區域,如果要完全自動化實現Pixels to Code,還需要藉助深度卷積網絡進行組件檢測與識別。

本人將於9月5號參與騰訊live開發者大會,屆時將介紹更多前端智能化實踐內容,歡迎有興趣童鞋前來觀摩

更多文章歡迎關注

相關資料

最全綜述|圖像分割算法: zhuanlan.zhihu.com/p/70

pHash圖像相似度比較算法彙總: blog.csdn.net/mago2015/

機器學習算法實踐——K-Means算法與圖像分割: blog.csdn.net/google198

霍夫變換: en.wikipedia.org/wiki/H

Suzuki85輪廓跟踪算法: blog.csdn.net/yiqiudrea

What do you think?

Written by marketer

blank

一個簡易的預渲染自動骨架屏方案

blank

字節跳動-廣告研發| 2021秋季校園人才召集令!