簡體   English   中英

裁剪 X 射線圖像以去除背景

[英]cropping x-ray image to remove background

我有許多 X 射線掃描,需要從背景噪聲中裁剪掃描的 object。

這些文件是.png 格式,我打算使用 OpenCV Python 來完成這個任務。 我已經看到了 FindContours() 的一些作品,但不確定閾值是否適用於這種情況。

圖片前:

圖像之前

后/裁剪圖像:

后/裁剪圖像

任何建議的解決方案/代碼表示贊賞。

這是在 Python/OpenCV 中執行此操作的一種方法。 它假設您在所有圖像中都有相同的多余邊框,以便可以按區域對輪廓進行排序並跳過最大的輪廓以獲得第二大的輪廓。

輸入:

在此處輸入圖像描述

import cv2
import numpy as np

# load image
img = cv2.imread("table_xray.jpg")
hh, ww = img.shape[:2]

# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# median filter
filt = cv2.medianBlur(gray, 15)

# threshold the filtered image and invert
thresh = cv2.threshold(filt, 64, 255, cv2.THRESH_BINARY)[1]
thresh = 255 - thresh

# find  contours and store index with area in list
cntrs_info = []
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
index=0
for cntr in contours:
    area = cv2.contourArea(cntr)
    print(index, area)
    cntrs_info.append((index,area))
    index = index + 1

# sort contours by area
def takeSecond(elem):
    return elem[1]
cntrs_info.sort(key=takeSecond, reverse=True)

# get bounding box of second largest contour skipping large border
index_second = cntrs_info[1][0]
x,y,w,h = cv2.boundingRect(contours[index_second])
print(index_second,x,y,w,h)

# crop input image
results = img[y:y+h,x:x+w]

# write result to disk
cv2.imwrite("table_xray_thresholded.png", thresh)
cv2.imwrite("table_xray_extracted.png", results)

cv2.imshow("THRESH", thresh)
cv2.imshow("RESULTS", results)
cv2.waitKey(0)
cv2.destroyAllWindows()

過濾和閾值圖像:

在此處輸入圖像描述

裁剪結果:

在此處輸入圖像描述

這是另一種可能的解決方案。 它使用輸入圖像的K 通道,一旦轉換為CMYK顏色空間。 K (或Key )通道具有黑色的大部分信息,因此它對於分割輸入圖像應該很有用。 之后,您可以應用重形態鏈來生成 object 的良好掩碼。 之后,裁剪 object 非常簡單。 讓我們看看代碼:

# Imports
import cv2
import numpy as np

# Read image
imagePath = "D://opencvImages//"
inputImage = cv2.imread(imagePath+"jU6QA.jpg")

# Convert to float and divide by 255:
imgFloat = inputImage.astype(np.float) / 255.

# Calculate channel K:
kChannel = 1 - np.max(imgFloat, axis=2)

# Convert back to uint 8:
kChannel = (255*kChannel).astype(np.uint8)

程序的第一位將您的圖像轉換為CMYK顏色空間並提取K通道。 OpenCV 沒有直接轉換到此顏色空間,因此需要手動轉換。 我們需要小心數據類型,因為涉及到float操作。 生成的圖像是這樣的:

具有黑色信息的像素被分配接近255的強度。 現在,讓我們對該圖像進行閾值化以獲得二進制掩碼。 閾值水平是固定的:

# Threshold the image with a fixed thresh level
thresholdLevel = 200
_, binaryImage = cv2.threshold(kChannel, thresholdLevel, 255, cv2.THRESH_BINARY)

這會產生以下二進制圖像:

好吧。 我們需要隔離 object,但是我們有背景線和圖像周圍的“框架”。 讓我們先擺脫線條。 我們將應用形態Erosion 然后,我們將在圖像的左上角右下角兩個位置用黑色去除框架Flood-Filling 之后,我們將應用Dilation來恢復對象的原始大小。 我將這些 OpenCV 函數包裝在自定義函數中,從而節省了我輸入幾行代碼的時間 - 這些輔助函數在文章末尾提供。 這是方法:

# Perform Small Erosion:
binaryImage = morphoOperation(binaryImage, 3, 5, "Erode")

# Flood-Fill at two locations: Top  left corner and  bottom right:
(imageHeight, imageWidth) = binaryImage.shape[:2]
floodPositions = [(0, 0),(imageWidth-1, imageHeight-1)]
binaryImage = floodFill(binaryImage, floodPositions, 0)

# Perform Small Dilate:
binaryImage = morphoOperation(binaryImage, 3, 5, "Dilate")

這是結果:

好的。 我們可以通過應用第二個形態鏈來改進掩碼,這一次需要更多的iterations 讓我們應用 Dilation 來嘗試加入 object 的“孔”,然后再進行 Erosion 以再次恢復對象的原始大小:

# Perform Big Dilate:
binaryImage = morphoOperation(binaryImage, 3, 10, "Dilate")

# Perform Big Erode:
binaryImage = morphoOperation(binaryImage, 3, 10, "Erode")

這會產生以下結果:

object 內部的空隙已被填補。 現在,讓我們檢索此蒙版上的contours以找到對象的輪廓。 我還包括了一個area filter 此時面罩非常干凈,所以也許這個過濾器不是太需要。 找到輪廓后,我們可以從原始圖像中裁剪 object:

# Find the contours on the binary image:
contours, hierarchy = cv2.findContours(binaryImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# BGR image for drawing results:
binaryBGR = cv2.cvtColor(binaryImage, cv2.COLOR_GRAY2BGR)

# Look for the outer bounding boxes (no children):
for _, c in enumerate(contours):

    # Get blob area:
    currentArea = cv2.contourArea(c)

    # Set a min area value:
    minArea = 10000

    if minArea < currentArea:

        # Get the contour's bounding rectangle:
        boundRect = cv2.boundingRect(c)

        # Get the dimensions of the bounding rect:
        rectX = boundRect[0]
        rectY = boundRect[1]
        rectWidth = boundRect[2]
        rectHeight = boundRect[3]

        # Set bounding rect:
        color = (0, 255, 0)
        cv2.rectangle( binaryBGR, (int(rectX), int(rectY)),
                       (int(rectX + rectWidth), int(rectY + rectHeight)), color, 5 )

        cv2.imshow("Rects", binaryBGR)

        # Crop original input:
        currentCrop = inputImage[rectY:rectY + rectHeight, rectX:rectX + rectWidth]
        
        cv2.imshow("Cropped", currentCrop)
        cv2.waitKey(0)

最后一步生成以下兩個圖像。 第一個是被矩形包圍的object,第二個是實際裁剪的:

我還用你的第二張圖片測試了算法,這些是最終結果:

哇。 有人帶槍去機場? 那不行。 這些是之前使用的輔助函數。 這第一個 function 執行morphological operations

def morphoOperation(binaryImage, kernelSize, opIterations, opString):
    # Get the structuring element:
    morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
    # Perform Operation:
    if opString == "Dilate":
        op = cv2.MORPH_DILATE
    else:
        if opString == "Erode":
            op = cv2.MORPH_ERODE

    outImage = cv2.morphologyEx(binaryImage, op, morphKernel, None, None, opIterations,
                                   cv2.BORDER_REFLECT101)
    return outImage

第二個 function 在給定種子點列表的情況下執行Flood-Filling

def floodFill(binaryImage, positions, color):
    # Loop thru the positions list of tuples:
    for p in range(len(positions)):
        currentSeed = positions[p]
        x = int(currentSeed[0])
        y = int(currentSeed[1])
        # Apply flood-fill:
        cv2.floodFill(binaryImage, mask=None, seedPoint=(x, y), newVal=(color))

    return binaryImage

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM