介紹:大三上做一個醫學影像識別的項目,醫生在原圖上用紅筆標記病竈點,通過記錄紅色的坐標位置可以得到病竈點的外接矩形,但是後續會涉及到紅圈內的面積在外接矩形下的占比問題,有些外接矩形內有多個紅色標記,在使用網上的opencv的fillPoly填充效果非常不理想,還有類似python計算任意多邊形方法也 ...
介紹:大三上做一個醫學影像識別的項目,醫生在原圖上用紅筆標記病竈點,通過記錄紅色的坐標位置可以得到病竈點的外接矩形,但是後續會涉及到紅圈內的面積在外接矩形下的占比問題,有些外接矩形內有多個紅色標記,在使用網上的opencv的fillPoly填充效果非常不理想,還有類似python計算任意多邊形方法也不理想的情況下,自己探索出的一種效果還不錯的計算多圈及不規則圖形的面積的演算法。
醫生提供的病竈標記圖和原圖,大部分長這樣
但也有一些多圈情況
很明顯,這些圖片都是非常需要計算面積占比的,對樣本需要篩選
通過百度,用opencv的填充來計算面積,一部分效果很差,單圈畫不全,多圈都是錯(用將面積計算結果上色,方便觀察)
通過此演算法之後,無論單圈,多圈,面積計算準確度提高許多
能較為準確的計算出不規則圖形的面積
正文:演算法的思想很簡單,遍歷圖片每一列,通過色差判斷是否遇到標記圈,將坐標全部記錄,對每一列的坐標都進行最小行和最大行記錄,確定每一列的最小和最大的坐標,然後上色(類似opencv的fillPoly的實現,但是細節有些區別),只是這樣效果並不好,將圖片旋轉90度,再做一邊,將兩個圖片的結果放在一起做與操作,得到結果就能很好的處理多圈的標記問題和多算面積的問題(比如上面的08-LM),
演算法實現
全程只用pillow庫
首先先用屏幕拾色器獲取目標顏色的rgb值,我這種情況下就是(237,28,36),前期截取外接矩形也是要這一步的,顏色也一致
1 def pixel_wanted(pix): 2 return pix==(237,28, 36)
每一列都設定翻轉位初始為False,如果上一個像素點不是目標色,當前是目標色則開始記錄,一旦不是目標色,停止檢測
top_Pixel都設定為黑色(0,0,0)因為有圖片最上方就是目標色,導致判定出問題,直接讓最上面的像素初始化是黑色
coordinate_List記錄了所有符合的點坐標
1 coordinate_List = [] 2 top_Pixel = (0,0,0) 3 for x in range(im.size[0]): 4 flag = False #初始化每一列翻轉位為False 5 for y in range(im.size[1]): 6 current_pixel = im.getpixel((x,y)) 7 last_pixel = im.getpixel((x,y-1)) if y>0 else top_Pixel 8 #翻轉判定 9 if pixel_wanted(current_pixel) and \ 10 not pixel_wanted(last_pixel): 11 flag = True 12 if flag and not pixel_wanted(current_pixel): 13 flag = False 14 if(flag): 15 coordinate_List.append((x,y))
coordinate_List中的點如下圖
然後就是將上面獲得coordinate列表進行處理
將coordinate列表中每一列的最小坐標和最大坐標進行記錄
因為每一列記錄的數量並不確定(應該可以在上一步改進一下),所以需要遍歷多次
首先找到第一個列出現的坐標,將它的行信息記錄(行信息最小確定),
然後遍歷出全部的同列的坐標,比較行坐標,如果大的就將最大的代替(行信息最大確定),用一個新的列表記錄數據
1 coordinate_Min_Max_List = [] 2 #找最小最大 3 for i in range(im.size[0]): 4 min=-1 5 max=-1 6 for coordinate in coordinate_List: 7 if coordinate[0] == i: 8 min = coordinate[1] 9 max = coordinate[1] 10 break 11 for coordinate in coordinate_List: 12 if coordinate[0] == i: 13 if coordinate[1]>max: 14 max = coordinate[1] 15 coordinate_Min_Max_List.append(min) 16 coordinate_Min_Max_List.append(max)
其中要將min和max都初始化為一個坐標不存在的值比如-1,為了在下一步多圈且有空隙情況下,不會出現殘影現象,如下圖
上一步的最後得到一個列表,第n列的最小行和最大行分別是第2n和2n+1元素,結果中的-1,為了讓下一步不會畫進去
然後就是繪製圖片了,每一列將列表中對應的最小行到最大行塗滿
1 #上色 2 for x in range(im.size[0]): 3 for y in range(im.size[1]): 4 min = coordinate_Min_Max_List[x*2] 5 max = coordinate_Min_Max_List[x*2+1] 6 if min<y<max: 7 im.putpixel((x,y),(0,255,0)) 8 else: 9 #可以把非紅圈的上掩膜遮住 10 pass
至此,就是類似opencv的演算法實現,雖然還差翻轉做與操作,但是已經比opencv生成的效果好,寫成函數後續調用,
然後就是簡單的翻轉90度,再調用一次這個函數再做一遍
1 def Cal_S(im): 2 im_0 = im.rotate(0) 3 im_90 = im.rotate(90, expand=True) 4 5 im_0 = fillPoly(im_0) 6 im_90 = fillPoly(im_90) 7 im_90 = im_90.rotate(-90, expand=True) 8 9 i=0 10 for x in range(im.size[0]): 11 for y in range(im.size[1]): 12 if(im_0.getpixel((x,y))==(0,255,0) and 13 im_90.getpixel((x,y))==(0,255,0)): 14 im.putpixel((x,y),(0,255,0)) 15 i+=1 16 return i/(im.size[0]*im.size[1])
做兩遍的效果圖
可以看到效果非常不錯,但是依舊有個別圖像有問題,比如十字分佈的,
但現在的話誤差已經降低非常多了,這些極其個別的十字現象可以手動把原圖切割一下,或者乾脆不處理了
所有代碼,畫出綠圖片為了方便直觀的查看,函數中可以把圖片順便保存一下,總體看一下效果
1 from PIL import Image 2 3 def pixel_wanted(pix): 4 return pix==(237,28, 36) 5 6 def fillPoly(im): 7 coordinate_List = [] 8 9 top_Pixel = (0,0,0) 10 for x in range(im.size[0]): 11 flag = False #初始化每一列翻轉位為False 12 for y in range(im.size[1]): 13 current_pixel = im.getpixel((x,y)) 14 last_pixel = im.getpixel((x,y-1)) if y>0 else top_Pixel 15 #翻轉判定 16 if pixel_wanted(current_pixel) and \ 17 not pixel_wanted(last_pixel): 18 flag = True 19 if flag and not pixel_wanted(current_pixel): 20 flag = False 21 if(flag): 22 coordinate_List.append((x,y)) 23 coordinate_Min_Max_List = [] 24 #找最小最大 25 for i in range(im.size[0]): 26 min=-1 27 max=-1 28 for coordinate in coordinate_List: 29 if coordinate[0] == i: 30 min = coordinate[1] 31 max = coordinate[1] 32 break 33 for coordinate in coordinate_List: 34 if coordinate[0] == i: 35 if coordinate[1]>max: 36 max = coordinate[1] 37 coordinate_Min_Max_List.append(min) 38 coordinate_Min_Max_List.append(max) 39 #上色 40 for x in range(im.size[0]): 41 for y in range(im.size[1]): 42 min = coordinate_Min_Max_List[x*2] 43 max = coordinate_Min_Max_List[x*2+1] 44 if min<y<max: 45 im.putpixel((x,y),(0,255,0)) 46 else: 47 #可以把非紅圈的上掩膜遮住 48 pass 49 return im 50 51 def Cal_S(im): 52 im_0 = im.rotate(0) 53 im_90 = im.rotate(90, expand=True) 54 55 im_0 = fillPoly(im_0) 56 im_90 = fillPoly(im_90) 57 im_90 = im_90.rotate(-90, expand=True) 58 59 i=0 60 for x in range(im.size[0]): 61 for y in range(im.size[1]): 62 if(im_0.getpixel((x,y))==(0,255,0) and 63 im_90.getpixel((x,y))==(0,255,0)): 64 im.putpixel((x,y),(0,255,0)) 65 i+=1 66 return i/(im.size[0]*im.size[1])