<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于 OpenCV 的圖像分割

          共 10653字,需瀏覽 22分鐘

           ·

          2020-09-20 10:51


          點擊上方小白學視覺”,選擇加"星標"或“置頂

          重磅干貨,第一時間送達

          本期我們將一起來實現(xiàn)一個有趣的問題 -圖像分割的算法。

          本文的示例代碼可以在以下鏈接中找到:

          https://github.com/kiteco/kite-python-blog-post-code/tree/master/image-segmentation

          作為我們的例子,我們將對KESM顯微鏡獲取的圖像進行分割以獲取其中的血管組織。

          數(shù)據(jù)科學家和醫(yī)學研究人員可以將這種方法作為模板,用于更加復雜的圖像的數(shù)據(jù)集(如天文數(shù)據(jù)),甚至一些非圖像數(shù)據(jù)集中。由于圖像在計算機中表示為矩陣,我們有一個專門的排序數(shù)據(jù)集作為基礎。在整個處理過程中,我們將使用 Python 包,以及OpenCV、scikit 圖像等幾種工具。除此之外,我們還將使用 numpy ,以確保內(nèi)存中的值一致存儲。

          主要內(nèi)容

          去噪

          為了消除噪聲,我們使用簡單的中位數(shù)濾波器來移除異常值,但也可以使用一些不同的噪聲去除方法或偽影去除方法。這項工件由采集系統(tǒng)決定(顯微鏡技術),可能需要復雜的算法來恢復丟失的數(shù)據(jù)。工件通常分為兩類:

          1. 模糊或焦點外區(qū)域

          2. 不平衡的前景和背景(使用直方圖修改正確)

          分割

          對于本文,我們使用Otsu 的方法分割,使用中位數(shù)濾波器平滑圖像后,然后驗證結果。只要分段結果是二進制的,就可以對任何分段算法使用相同的驗證方法。這些算法包括但不限于考慮不同顏色空間的各種循環(huán)閾值方法。

          一些示例包括:

          1. 李閾值

          2. 依賴于局部強度的自適應閾值方法

          3. 在生物醫(yī)學圖像分割中常用的Unet等深度學習算法

          4. 在語義上對圖像進行分段的深度學習方法

          驗證

          我們從已手動分割的基礎數(shù)據(jù)集開始。為了量化分段算法的性能,我們將真實數(shù)據(jù)與預測數(shù)據(jù)的二進制分段進行比較,同時顯示準確性和更有效的指標。盡管真陽性 (TP) 或假陰性 (FN) 數(shù)量較低,但精度可能異常高。在這種情況下,F(xiàn)1 分數(shù)和?MCC是二進制分類的更好量化指標。稍后我們將詳細介紹這些指標的優(yōu)缺點。

          為了定性驗證,我們疊加混淆矩陣結果,即真正的正極、真負數(shù)、假陽性、假負數(shù)像素正好在灰度圖像上。此驗證也可以應用于二進制圖像分割結果上的顏色圖像,盡管本文中使用的數(shù)據(jù)是灰度圖像。最后,我們將介紹整個實現(xiàn)過程?,F(xiàn)在,讓我們看看數(shù)據(jù)和用于處理這些數(shù)據(jù)的工具。

          Loading and visualizing data

          我們將使用以下模塊加載、可視化和轉(zhuǎn)換數(shù)據(jù)。這些對于圖像處理和計算機視覺算法非常有用,具有簡單而復雜的數(shù)組數(shù)學。如果單獨安裝,括號中的模塊名稱會有所幫助。

          如果在運行示例代碼中,遇到 matplotlib 后端的問題,請通過刪除 plt.ion() 調(diào)用來禁用交互式模式,或是通過取消注釋示例代碼中的建議調(diào)用來在每個部分的末尾調(diào)用 plt.show()。"Agg"或"TkAgg"將作為圖像顯示的后端。繪圖將顯示在文章中。

          代碼導入

          import cv2 import matplotlib.pyplot as plt import numpy as np import scipy.misc import scipy.ndimage import skimage.filters import sklearn.metrics # Turn on interactive mode. Turn off with plt.ioff() plt.ion()

          在本節(jié)中,我們將加載可視化數(shù)據(jù)。數(shù)據(jù)是小老鼠腦組織與印度墨水染色的圖像,由顯微鏡(KESM)生成。此 512 x 512 圖像是一個子集,稱為圖塊。完整的數(shù)據(jù)集為 17480 x 8026 像素,深度為 799,大小為 10gb。因此,我們將編寫算法來處理大小為 512 x 512 的圖塊,該圖塊只有 150 KB。

          各個圖塊可以映射為在多處理/多線程(即分布式基礎結構)上運行,然后再縫合在一起即獲得完整的分段圖像。我們不介紹具體的縫合方法。簡而言之,拼接涉及對整個矩陣的索引并根據(jù)該索引將圖塊重新組合??梢允褂胢ap-reduce進行,Map-Reduce的指標例如所有圖塊的所有F1分數(shù)之和等。我們只需將結果添加到列表中,然后執(zhí)行統(tǒng)計摘要即可。

          左側(cè)的黑色橢圓形結構是血管,其余的是組織。因此,此數(shù)據(jù)集中的兩個類是:

          ? 前景(船只)—標記為255

          ? 背景(組織)—標記為0

          右下方的最后一個圖像是真實圖像。通過繪制輪廓并填充輪廓以手動方式對其進行追蹤,通過病理學家獲得真實情況。我們可以使用專家提供的類似示例來訓練深度學習網(wǎng)絡,并進行大規(guī)模驗證。我們還可以通過將這些示例提供給其他平臺并讓他們以更大的比例手動跟蹤一組不同的圖像以進行驗證和培訓來擴充數(shù)據(jù)。

          grayscale = scipy.misc.imread('grayscale.png')grayscale = 255 - grayscalegroundtruth = scipy.misc.imread('groundtruth.png')plt.subplot(1, 3, 1)plt.imshow(255 - grayscale, cmap='gray')plt.title('grayscale')plt.axis('off')plt.subplot(1, 3, 2)plt.imshow(grayscale, cmap='gray')plt.title('inverted grayscale')plt.axis('off')plt.subplot(1, 3, 3)plt.imshow(groundtruth, cmap='gray')plt.title('groundtruth binary')plt.axis('off')


          前處理

          在分割數(shù)據(jù)之前,我們應該檢查一下數(shù)據(jù)集,以確定是否存在由于成像系統(tǒng)而造成了偽影。在此示例中,我們僅討論一個圖像。通過查看圖像,我們可以看到?jīng)]有任何明顯的偽影會干擾分割。但是,小伙伴們可以使用中值濾鏡消除離群值噪聲并平滑圖像。中值過濾器用中值(在給定大小的內(nèi)核內(nèi))替換離群值。

          內(nèi)核大小3的中值過濾器

          median_filtered = scipy.ndimage.median_filter(grayscale, size=3) plt.imshow(median_filtered, cmap='gray') plt.axis('off') plt.title("median filtered image")


          要確定哪種閾值技術最適合分割,我們可以先通過閾值確定是否存在將這兩個類別分開的獨特像素強度。在這種情況下,可以使用通過目視檢查獲得的強度對圖像進行二值化處理。我們使用的圖像許多像素的強度小于50,這些像素與反轉(zhuǎn)灰度圖像中的背景類別相對應。

          盡管類別的分布不是雙峰的,但仍然在前景和背景之間有所區(qū)別,在該區(qū)域中,較低強度的像素達到峰值,然后到達谷底。可以通過各種閾值技術獲得該精確值。分割部分將詳細研究一種這樣的方法。

          可視化像素強度的直方圖

          counts, vals = np.histogram(grayscale, bins=range(2 ** 8)) plt.plot(range(0, (2 ** 8) - 1), counts) plt.title("Grayscale image histogram") plt.xlabel("Pixel intensity") plt.ylabel("Count")



          分割

          去除噪聲后,我們可以用skimage濾波器模塊對所有閾值的結果進行比較,來確定所需要使用的像素。有時,在圖像中,其像素強度的直方圖不是雙峰的。因此,可能會有另一種閾值方法可以比基于閾值形狀在內(nèi)核形狀中進行閾值化的自適應閾值方法更好。Skimage中的函數(shù)可以方便看到不同閾值的處理結果。

          嘗試所有閾值

          result = skimage.filters.thresholding.try_all_threshold(median_filtered)


          最簡單的閾值處理方法是為圖像使用手動設置的閾值。但是在圖像上使用自動閾值方法可以比人眼更好地計算其數(shù)值,并且可以輕松復制。對于本例中的圖像,似乎Otsu,Yen和Triangle方法的效果很好。

          在本文中,我們將使用Otsu閾值技術將圖像分割成二進制圖像。Otsu通過計算一個最大化類別間方差(前景與背景之間的方差)并最小化類別內(nèi)方差(前景內(nèi)部的方差或背景內(nèi)部的方差)的值來計算閾值。如果存在雙峰直方圖(具有兩個不同的峰)或閾值可以更好地分隔類別,則效果很好。

          Otsu閾值化和可視化

          threshold = skimage.filters.threshold_otsu(median_filtered) print("Threshold value is {}".format(threshold)) predicted = np.uint8(median_filtered > threshold) * 255 plt.imshow(predicted, cmap='gray') plt.axis('off') plt.title("otsu predicted binary image")


          如果上述簡單技術不能用于圖像的二進制分割,則可以使用UNet,帶有FCN的ResNet或其他各種受監(jiān)督的深度學習技術來分割圖像。要去除由于前景噪聲分段而產(chǎn)生的小物體,也可以考慮嘗試skimage.morphology.remove_objects()

          驗證方式

          一般情況下,我們都需要由具有圖像類型專長的人員手動生成基本事實,來驗證準確性和其他指標,并查看圖像的分割程度。

          confusion矩陣

          我們sklearn.metrics.confusion_matrix()用來獲取該矩陣元素,如下所示。假設輸入是帶有二進制元素的元素列表,則Scikit-learn混淆矩陣函數(shù)將返回混淆矩陣的4個元素。對于一切都是一個二進制值(0)或其他(1)的極端情況,sklearn僅返回一個元素。我們包裝了sklearn混淆矩陣函數(shù),并編寫了我們自己的這些邊緣情況,如下所示:

          get_confusion_matrix_elements()

          def get_confusion_matrix_elements(groundtruth_list, predicted_list):    """returns confusion matrix elements i.e TN, FP, FN, TP as floats  See example code for helper function definitions    """    _assert_valid_lists(groundtruth_list, predicted_list)
          if _all_class_1_predicted_as_class_1(groundtruth_list, predicted_list) is True: tn, fp, fn, tp = 0, 0, 0, np.float64(len(groundtruth_list))
          elif _all_class_0_predicted_as_class_0(groundtruth_list, predicted_list) is True: tn, fp, fn, tp = np.float64(len(groundtruth_list)), 0, 0, 0
          else: tn, fp, fn, tp = sklearn.metrics.confusion_matrix(groundtruth_list, predicted_list).ravel() tn, fp, fn, tp = np.float64(tn), np.float64(fp), np.float64(fn), np.float64(tp)
          return tn, fp, fn, tp

          準確性

          在二進制分類的情況下,準確性是一種常見的驗證指標。計算為
          其中TP =真正,TN =真負,F(xiàn)P =假正,F(xiàn)N =假負

          get_accuracy()

          def get_accuracy(groundtruth_list, predicted_list):
          tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, predicted_list) total = tp + fp + fn + tn accuracy = (tp + tn) / total return accuracy

          它在0到1之間變化,0是最差的,1是最好的。如果算法將所有東西都檢測為整個背景或前景,那么仍然會有很高的準確性。因此,我們需要一個考慮班級人數(shù)不平衡的指標。特別是由于當前圖像比背景0具有更多的前景像素(類1)。

          F1分數(shù)從0到1不等,計算公式為:

          0是最差的預測,而1是最好的預測?,F(xiàn)在,考慮邊緣情況,處理F1分數(shù)計算。

          get_f1_score()

          def get_f1_score(groundtruth_list, predicted_list):    """Return f1 score covering edge cases"""
          tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, predicted_list) if _all_class_0_predicted_as_class_0(groundtruth_list, predicted_list) is True: f1_score = 1 elif _all_class_1_predicted_as_class_1(groundtruth_list, predicted_list) is True: f1_score = 1 else: f1_score = (2 * tp) / ((2 * tp) + fp + fn)
          return f1_score

          高于0.8的F1分數(shù)被認為是良好的F1分數(shù),表明預測表現(xiàn)良好。

          客戶中心

          MCC代表馬修斯相關系數(shù),其計算公式為:

          它位于-1和+1之間。-1是實際情況與預測之間絕對相反的相關性,0是隨機結果,其中某些預測匹配,而+1是實際情況與預測之間絕對匹配,保持正相關。因此,我們需要更好的驗證指標,例如MCC。

          在MCC計算中,分子僅由四個內(nèi)部單元(元素的叉積)組成,而分母由混淆矩陣的四個外部單元(點的積)組成。在分母為0的情況下,MCC將能夠注意到我們的分類器方向錯誤,并且會通過將其設置為未定義的值(即numpy.nan)進行警告。但是,為了獲得有效值,并能夠在必要時對不同圖像平均MCC,我們將MCC設置為-1(該范圍內(nèi)最差的值)。其他邊緣情況包括將MCC和F1分數(shù)設置為1的所有正確檢測為前景和背景的元素。否則,將MCC設置為-1且F1分數(shù)為0。

          想要了解有關MCC和邊緣案例,以及MCC為什么比準確性或F1分數(shù)更好,可以閱讀下面這篇文章:

          https://lettier.github.io/posts/2016-08-05-matthews-correlation-coefficient.html

          https://en.wikipedia.org/wiki/Matthews_correlation_coefficient#Advantages_of_MCC_over_accuracy_and_F1_score

          get_mcc()

          def get_mcc(groundtruth_list, predicted_list):    """Return mcc covering edge cases"""   
          tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, predicted_list) if _all_class_0_predicted_as_class_0(groundtruth_list, predicted_list) is True: mcc = 1 elif _all_class_1_predicted_as_class_1(groundtruth_list, predicted_list) is True: mcc = 1 elif _all_class_1_predicted_as_class_0(groundtruth_list, predicted_list) is True: mcc = -1 elif _all_class_0_predicted_as_class_1(groundtruth_list, predicted_list) is True : mcc = -1
          elif _mcc_denominator_zero(tn, fp, fn, tp) is True: mcc = -1
          # Finally calculate MCC else: mcc = ((tp * tn) - (fp * fn)) / ( np.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn))) return mcc

          最后,我們可以按結果并排比較驗證指標。

          >>> validation_metrics = get_validation_metrics(groundtruth, predicted) {'mcc': 0.8533910225863214, 'f1_score': 0.8493358633776091, 'tp': 5595.0, 'fn': 1863.0, 'fp': 122.0, 'accuracy': 0.9924278259277344, 'tn': 254564.0}

          精度接近1,因為示例圖像中有很多背景像素可被正確檢測為背景(即,真實的底片自然更高)。這說明了為什么精度不是二進制分類的好方法。

          F1分數(shù)是0.84。因此,在這種情況下,我們可能不需要用于二進制分割的更復雜的閾值算法。如果堆棧中的所有圖像都具有相似的直方圖分布和噪聲,則可以使用Otsu并獲得相當不錯的預測結果。

          所述MCC?0.85高時,也表示地面實況和預測圖像具有高的相關性,從在上一節(jié)的預測圖像圖片清楚地看到。

          現(xiàn)在,讓我們可視化并查看混淆矩陣元素TP,F(xiàn)P,F(xiàn)N,TN在圖像周圍的分布位置。它向我們顯示了在不存在閾值(FP)的情況下閾值正在拾取前景(容器),在未檢測到真實血管的位置(FN),反之亦然。

          驗證可視化

          為了可視化混淆矩陣元素,我們精確地找出混淆矩陣元素在圖像中的位置。例如,我們發(fā)現(xiàn)TP陣列(即正確檢測為前景的像素)是通過找到真實情況和預測陣列的邏輯“與”。同樣,我們使用邏輯布爾運算通常稱為FP,F(xiàn)N,TN數(shù)組。

          get_confusion_matrix_intersection_mats()

          def get_confusion_matrix_intersection_mats(groundtruth, predicted):    """ Returns dict of 4 boolean numpy arrays with True at TP, FP, FN, TN    """
          confusion_matrix_arrs = {}
          groundtruth_inverse = np.logical_not(groundtruth) predicted_inverse = np.logical_not(predicted)
          confusion_matrix_arrs['tp'] = np.logical_and(groundtruth, predicted) confusion_matrix_arrs['tn'] = np.logical_and(groundtruth_inverse, predicted_inverse) confusion_matrix_arrs['fp'] = np.logical_and(groundtruth_inverse, predicted) confusion_matrix_arrs['fn'] = np.logical_and(groundtruth, predicted_inverse)
          return confusion_matrix_arrs

          然后,我們可以將每個數(shù)組中的像素映射為不同的顏色。對于下圖,我們將TP,F(xiàn)P,F(xiàn)N,TN映射到CMYK(青色,品紅色,黃色,黑色)空間。同樣可以將它們映射到(綠色,紅色,紅色,綠色)顏色。然后,我們將獲得一張圖像,其中所有紅色均表示錯誤的預測。CMYK空間使我們能夠區(qū)分TP,TN。

          get_confusion_matrix_overlaid_mask()

          def get_confusion_matrix_overlaid_mask(image, groundtruth, predicted, alpha, colors):    """    Returns overlay the 'image' with a color mask where TP, FP, FN, TN are    each a color given by the 'colors' dictionary    """    image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)    masks = get_confusion_matrix_intersection_mats(groundtruth, predicted)    color_mask = np.zeros_like(image)    for label, mask in masks.items():        color = colors[label]        mask_rgb = np.zeros_like(image)        mask_rgb[mask != 0] = color        color_mask += mask_rgb    return cv2.addWeighted(image, alpha, color_mask, 1 - alpha, 0)
          alpha = 0.5confusion_matrix_colors = { 'tp': (0, 255, 255), #cyan 'fp': (255, 0, 255), #magenta 'fn': (255, 255, 0), #yellow 'tn': (0, 0, 0) #black }validation_mask = get_confusion_matrix_overlaid_mask(255 - grayscale, groundtruth, predicted, alpha, confusion_matrix_colors)print('Cyan - TP')print('Magenta - FP')print('Yellow - FN')print('Black - TN')plt.imshow(validation_mask)plt.axis('off')plt.title('confusion matrix overlay mask')

          我們在此處使用OpenCV將此顏色蒙版作為透明層覆蓋到原始(非反轉(zhuǎn))灰度圖像上。這稱為Alpha合成:

          總結

          存儲庫中的最后兩個示例通過調(diào)用測試函數(shù)來測試邊緣情況和在小的數(shù)組(少于10個元素)上的隨機預測場景。如果我們測試該算法的簡單邏輯,則測試邊緣情況和潛在問題很重要。

          Travis CI對于測試我們的代碼是否可以在需求中描述的模塊版本上工作以及在新更改合并到主版本中時所有測試通過均非常有用。最佳做法是保持代碼整潔,文檔完善,并對所有語句進行單元測試和覆蓋。這些習慣限制了在復雜的算法建立在可能已經(jīng)進行了單元測試的簡單功能塊之上時,消除錯誤的需求。通常,文檔和單元測試可幫助其他人隨時了解功能意圖。整理有助于提高代碼的可讀性,而flake8是實現(xiàn)此目的的良好Python包。

          以下是本文的重要內(nèi)容:

          1. 適用于內(nèi)存中不適合的數(shù)據(jù)的拼接和拼接方法

          2. 嘗試不同的閾值技術

          3. 驗證指標的精妙之處

          4. 驗證可視化

          5. 最佳實踐

          交流群


          歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器自動駕駛、計算攝影、檢測、分割、識別、醫(yī)學影像、GAN、算法競賽等微信群(以后會逐漸細分),請掃描下面微信號加群,備注:”昵稱+學校/公司+研究方向“,例如:”張三?+?上海交大?+?視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進入相關微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~


          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日产毛片不 | 久久久精品 | A A A 免费毛片 | 男女AA免费 | 丝袜自拍偷拍 |