Анализ видео / изображений для определения расстояния между контурами - PullRequest
0 голосов
/ 14 июня 2019

Новое изображение: тестовое изображение

Я пытаюсь количественно определить расстояние между двумя контурами в видео с микрососудом (см. Снимок)

Структура анализа изображений

Image analysis structure

Прямо сейчас я могу выбрать только для одного контура (который выделен), и я получаю измерения из этого контура, но я хотел бы выбрать для этого верхний и нижний контур структуры и меры расстояние (помечено оранжевой линией и A на снимке).

Есть предложения, как это сделать? Мой код для этого видео анализа следующий. Заранее спасибо за помощь!:

import cv2
import pandas as pd
import numpy as np
import imutils
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours

videocapture = cv2.VideoCapture('RTMLV.mp4')

def safe_div(x,y):
    if y==0: return 0
    return x/y

def nothing(x):
    pass

def rescale_frame(frame, percent=100): #make the video windows a bit smaller
    width = int(frame.shape[1]*percent/100)
    height = int(frame.shape[0]*percent/100)
    dim = (width, height)
    return cv2.resize(frame, dim, interpolation=cv2.INTER_AREA)

if not videocapture.isOpened():
    print("Unable to open video")
    exit()

windowName="Vessel Tracking"

cv2.namedWindow(windowName)

# Sliders to adjust image

cv2.createTrackbar("Threshold", windowName, 75, 255, nothing)
cv2.createTrackbar("Kernel", windowName, 5, 30, nothing)
cv2.createTrackbar("Iterations", windowName, 1, 10, nothing)

showLive=True
while(showLive):

    ret, frame=videocapture.read()
    frame_resize=rescale_frame(frame)
    if not ret:
        print("Cannot capture the frame")
        exit()

    thresh = cv2.getTrackbarPos("Threshold", windowName)
    ret,thresh1 = cv2.threshold(frame_resize, thresh, 255, cv2.THRESH_BINARY)

    kern = cv2.getTrackbarPos("Kernel", windowName)
    kernel = np.ones((kern, kern), np.uint8) # square image kernel used for erosion

    itera=cv2.getTrackbarPos("Iterations", windowName)
    dilation = cv2.dilate(thresh1, kernel, iterations=itera)
    erosion = cv2.erode(dilation, kernel, iterations=itera) #refines all edges in the binary image

    opening = cv2.morphologyEx(erosion, cv2.MORPH_OPEN, kernel)
    closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
    closing = cv2.cvtColor(closing, cv2.COLOR_BGR2GRAY)

    contours,hierarchy = cv2.findContours(closing,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) # find contours with simple approximation cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE

    closing = cv2.cvtColor(closing,cv2.COLOR_GRAY2RGB)
    cv2.drawContours(closing, contours, -1, (128,255,0), 1)

    # focus on only the largest outline by area
    areas = [] #list to hold all areas

    for contour in contours:
      ar = cv2.contourArea(contour)
      areas.append(ar)

    max_area = max(areas)
    max_area_index = areas.index(max_area)  # index of the list element with largest area

    cnt = contours[max_area_index - 1] # largest area contour is usually the viewing window itself, why?

    cv2.drawContours(closing, [cnt], 0, (0,0,255), 1)

    def midpoint(ptA, ptB): 
      return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)

    # compute the rotated bounding box of the contour
    orig = frame_resize.copy()
    box = cv2.minAreaRect(cnt)
    box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
    box = np.array(box, dtype="int")

    # order the points in the contour such that they appear
    # in top-left, top-right, bottom-right, and bottom-left
    # order, then draw the outline of the rotated bounding
    # box
    box = perspective.order_points(box)
    cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 1)

    # loop over the original points and draw them
    for (x, y) in box:
      cv2.circle(orig, (int(x), int(y)), 5, (0, 0, 255), -1)

    # unpack the ordered bounding box, then compute the midpoint
    # between the top-left and top-right coordinates, followed by
    # the midpoint between bottom-left and bottom-right coordinates
    (tl, tr, br, bl) = box
    (tltrX, tltrY) = midpoint(tl, tr)
    (blbrX, blbrY) = midpoint(bl, br)

    # compute the midpoint between the top-left and top-right points,
    # followed by the midpoint between the top-right and bottom-right
    (tlblX, tlblY) = midpoint(tl, bl)
    (trbrX, trbrY) = midpoint(tr, br)

    # draw the midpoints on the image
    cv2.circle(orig, (int(tltrX), int(tltrY)), 5, (255, 0, 0), -1)
    cv2.circle(orig, (int(blbrX), int(blbrY)), 5, (255, 0, 0), -1)
    cv2.circle(orig, (int(tlblX), int(tlblY)), 5, (255, 0, 0), -1)
    cv2.circle(orig, (int(trbrX), int(trbrY)), 5, (255, 0, 0), -1)

    # draw lines between the midpoints
    cv2.line(orig, (int(tltrX), int(tltrY)), (int(blbrX), int(blbrY)),(255, 0, 255), 1)
    cv2.line(orig, (int(tlblX), int(tlblY)), (int(trbrX), int(trbrY)),(255, 0, 255), 1)
    cv2.drawContours(orig, [cnt], 0, (0,0,255), 1)

    # compute the Euclidean distance between the midpoints
    dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
    dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))

    # compute the size of the object
    P2M4x = 1.2
    P2M10x = 3.2
    P2M20x = 6
    pixelsPerMetric = P2M10x # Pixel to micron conversion
    dimA = dA / pixelsPerMetric
    dimB = dB / pixelsPerMetric

    dimensions = [dimA, dimB]

    # draw the object sizes on the image
    cv2.putText(orig, "{:.1f}um".format(dimA), (int(tltrX - 15), int(tltrY - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 2)
    cv2.putText(orig, "{:.1f}um".format(dimB), (int(trbrX + 10), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 2)

    # compute the center of the contour
    M = cv2.moments(cnt)
    cX = int(safe_div(M["m10"],M["m00"]))
    cY = int(safe_div(M["m01"],M["m00"]))

    # draw the contour and center of the shape on the image
    cv2.circle(orig, (cX, cY), 5, (255, 255, 255), -1)
    cv2.putText(orig, "center", (cX - 20, cY - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)

    cv2.imshow(windowName, orig)
    cv2.imshow('', closing)
    if cv2.waitKey(30)>=0:
        showLive=False

videocapture.release()
cv2.destroyAllWindows()

1 Ответ

0 голосов
/ 14 июня 2019

Изменения были внесены в этот ответ в ответ на новое тестовое изображение, которое было добавлено к сообщению.

Мне не удалось сегментировать кровеносный сосуд на тестовом изображении с помощью загруженного вами кода. Я сегментировал изображение, используя ручную аннотацию и алгоритм GrabCut.

manual segmentation

Это код, который я использовал для ручной сегментации:

import cv2, os, numpy as np
import time
# Plot with Matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img_path = '/home/stephen/Desktop/0lszR.jpg'
img = cv2.imread(img_path)
img = img[420:1200, :]
h,w,_ = img.shape
mask = np.zeros((h,w), np.uint8)
mask[:] = 2
src = img.copy()
h,w,_ = img.shape
drawing = src.copy()

# Mouse callback function
global k, px, py

k = 0
px, py = 0,0

def callback(event, x, y, flags, param):
    global k, px, py
    print(x,y, k, px, py)
    if k == 115: # 's' for sure background
        if px+py!=0:            
            cv2.line(img, (x,y), (px, py), (255,255,0), 8)
            cv2.line(mask, (x,y), (px, py), 0, 8)
    if k == 116: # 't' for sure foreground
        if px+py!=0:            
            cv2.line(img, (x,y), (px, py), (0,255,255), 8)
            cv2.line(mask, (x,y), (px, py), 1, 8)  
        else: print(px, py)
    px, py = x,y
    #if k != 115 or 116: px, py =  0,0
cv2.namedWindow('img')
cv2.setMouseCallback('img', callback)
while k != 27:
    cv2.imshow('img', img)
    k_temp = cv2.waitKey(1)
    if k_temp!=-1: k = k_temp
cv2.destroyAllWindows()

После того как я нашел сегментированное изображение, я использовал функцию np.nonzero(), чтобы найти вершины и впадины столбцов:

finding width

Это код, который я использовал, чтобы найти ширину:

# Initialize parameters for the GrabCut algorithm
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

# Apply GrabCut
out_mask = mask.copy()
out_mask, _, _ = cv2.grabCut(src,out_mask,None,bgdModel,fgdModel,1,cv2.GC_INIT_WITH_MASK)
out_mask = np.where((out_mask==2)|(out_mask==0),0,1).astype('uint8')
# Open the mask to fill in the holes
out_img = src*out_mask[:,:,np.newaxis]
flip_mask = cv2.flip(out_mask, 0)

# Find the distances
distances = []
for col_num in range(src.shape[1]-1):
    col = out_mask[:, col_num:col_num+1]
    flip_col = flip_mask[:, col_num:col_num+1]
    top = np.nonzero(col)[0][0]
    bottom = h-np.nonzero(flip_col)[0][0]
    if col_num % 12 == 0:
        cv2.line(drawing, (col_num, top), (col_num, bottom), (234,345,34), 4)
    distances.append(bottom-top)

f, axarr = plt.subplots(2,3, sharex=True)
axarr[0,0].imshow(src)
axarr[0,1].imshow(out_mask)
axarr[0,2].imshow(drawing)
axarr[1,0].imshow(img)
axarr[1,1].imshow(out_img)
axarr[1,2].plot(distances)

axarr[0,0].set_title("Source")
axarr[0,1].set_title('Mask from GrabCut')
axarr[0,2].set_title('Widths')
axarr[1,0].set_title('Manual Annotation')
axarr[1,1].set_title('GrabCut Mask')
axarr[1,2].set_title('Graph of Width')

axarr[0,0].axis('off')
axarr[0,1].axis('off')
axarr[1,0].axis('off')
axarr[1,1].axis('off')
axarr[1,2].axis('off')
axarr[0,2].axis('off')
plt.show()
...