如何使用 python opencv 可靠地仅选择 maksed 液滴图像的外轮廓?

How can I reliably choose only the outer contour of a maksed droplet image with python opencv?

我正在编写一个程序,它需要检测液滴的外轮廓并将椭圆拟合到该形状。

我有一个运行良好的设置:

  1. canny 边缘检测
  2. 找到等高线
  3. 选择最长的轮廓
  4. 拟合椭圆

但现在该过程需要包括用于分配液滴的注射器的面罩,它将外轮廓分成两半。 我知道如何屏蔽从 canny 返回的数组中检测到的边缘,但我不知道如何从那里继续。

我需要使用两个外部轮廓来拟合椭圆,但我不知道如何可靠地提取它们。

最小工作代码:

from typing import Tuple
import cv2
import numpy as np

def evaluate_droplet(img, y_base, mask: Tuple[int,int,int,int] = None):
    # crop img from baseline down (contains no useful information)
    crop_img = img[:y_base,:]
    shape = img.shape
    height = shape[0]
    width = shape[1]
    # calculate thrresholds
    thresh_high, thresh_im = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    thresh_low = 0.5*thresh_high
    bw_edges = cv2.Canny(crop_img, thresh_low, thresh_high)
    
    # block detection of syringe
    if (not mask is None):
        x,y,w,h = mask
        bw_edges[y:y+h, x:x+w] = 0
        cv2.imshow('bw',bw_edges)
        cv2.waitKey(0)


# for testing purposes:
if __name__ == "__main__":
    im = cv2.imread('untitled1.png', cv2.IMREAD_GRAYSCALE)
    im = np.reshape(im, im.shape + (1,) )
    (h,w,d) = np.shape(im)
    try:
        drp = evaluate_droplet(im, 250, (int(w/2-40), 0, 80, h))
    except Exception as ex:
        print(ex)
    cv2.imshow('Test',im)
    cv2.waitKey(0)

使用的图像:

我暂时这样解决了:

计算每个轮廓的边界矩形的面积并选择最大的两个

def evaluate_droplet(img, y_base, mask: Tuple[int,int,int,int] = None):
    # crop img from baseline down (contains no useful information)
    crop_img = img[:y_base,:]
    shape = img.shape
    height = shape[0]
    width = shape[1]
    # calculate thrresholds
    thresh_high, thresh_im = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    thresh_low = 0.5*thresh_high
    bw_edges = cv2.Canny(crop_img, thresh_low, thresh_high)
    
    # block detection of syringe
    if (not mask is None):
        x,y,w,h = mask
        bw_edges[y:y+h, x:x+w] = 0

    contours, hierarchy = cv2.findContours(bw_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    if len(contours) == 0:
        print("no contour found!")

    cont_area_list = []
    # match contours with their bounding rect area
    for cont in contours:
        x,y,w,h = cv2.boundingRect(cont)
        cont_area_list.append((cont, w*h))
    
    # sort combined list by area
    cont_areas_sorted = sorted(cont_area_list, key=lambda item: item[1])

    # largest 2 contours, assumes mask splits largest contour in the middle
    if not mask is None:
        largest_conts = [elem[0] for elem in cont_areas_sorted[-2:]]
        # merge largest 2 contorus into one for handling purposes
        contour = np.concatenate((largest_conts[0], largest_conts[1]))
    # if no mask is used use only single largest contour
    else:
        contour = cont_areas_sorted[-1][0]
    
    # display bounding rect of final contour
    x,y,w,h = cv2.boundingRect(contour)
    bw_edges = cv2.rectangle(bw_edges, (x,y), (x+w,y+h), (255,255,255))
    cv2.imshow('bw',bw_edges)
    cv2.waitKey(0)