查找重叠椭圆的直径和面积(OpenCV,Python) [英] Finding the diameter and area of overlapping ellipses (OpenCV, Python)

查看:37
本文介绍了查找重叠椭圆的直径和面积(OpenCV,Python)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我关于 Stackoverflow 的第一个问题.我有点激动,如果我错了,请原谅我.我们混合了从油漆中随机绘制的重叠和不重叠的椭圆.我正在分享我正在处理的图像和我的代码.我不是 opencv 模块的专业人士,我是在受来源启发的研究中编写代码的.

This is my first question on Stackoverflow. I'm a little excited, forgive me if I'm wrong. We have mixed ellipses with and without overlapping drawn randomly from paint. I'm sharing the image I'm working on and my code. I am not a professional in opencv module, I wrote my code as a result of research inspired by sources.

我的代码的目的是,

使用 cv2.fitEllipse 方法检测带有和不带有重叠椭圆的随机绘制.接下来,找出检测到的椭圆的长轴、短轴和面积.

Detection of randomly drawn with and without overlapping ellipses using the cv2.fitEllipse method. Next, find the major axis, minor axis and areas of the detected ellipses.

我的代码的问题实际上是这样的,

The problem with my code is actually this,

在重叠的椭圆中,正常情况下拟合椭圆时,应该拟合2个椭圆,但是拟合了大约6-7个椭圆,无法达到我想要计算的值.

In overlapping ellipses, while fitting the ellipse under normal conditions, 2 ellipses should be fit, but about 6-7 ellipses are fit and I cannot reach the values I want to be calculated.

我愿意接受您的帮助,在此先感谢您.

I'm open to your help, thank you in advance.

示例图片:

import cv2
import numpy as np
import random as rng
import math

img = cv2.imread('overlapping_ellipses.png', 1)
imge= cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(imge, cv2.COLOR_BGR2GRAY)
blur = cv2.blur(gray, (2,2), 3)
edged = cv2.Canny(blur, 50, 100)
kernel= np.ones((2,2))
edged1 = cv2.dilate(edged, kernel, iterations=2)
edged2 = cv2.erode(edged1, kernel, iterations=2)

def thresh_callback(val):
 threshold = val

 canny_output = cv2.Canny(edged2, threshold, threshold * 4)
 contours, _ = cv2.findContours(canny_output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 minRect = [None]*len(contours)
 minEllipse = [None]*len(contours)
 for i, c in enumerate(contours):
    minRect[i] = cv2.minAreaRect(c)
    if c.shape[0] > 5:
        minEllipse[i] = cv2.fitEllipse(c)
        (x1,y1),(d1,d2),angle = minEllipse[i]
        print('\nX1: ', round(x1,4), '\nY1: ', round(y1,4), '\nD1:',round(d1,4), '\nD2',round(d2,4), '\nAngle:', round(angle,4))
        long= x1-d2
        small= y1-d1
        major= long/2
        minor= small/2
        pixel= 37.795275591
        major1= major/pixel
        minor1= minor/pixel
        print('--------------------------------')
        print('Major axis is: ', abs(round(major1,4)), 'cm')
        print('Minor axis is: ', abs(round(minor1,4)), 'cm')
        print('--------------------------------')
drawing = np.zeros((canny_output.shape[1], canny_output.shape[1], 3), dtype=np.uint8)

for i, c in enumerate(contours):
    color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
    cv2.drawContours(drawing, contours, i, color)
    if c.shape[0] > 5:
        cv2.ellipse(drawing, minEllipse[i], color, 1)
        
cv2.imshow('Fitting Ellips', drawing)

source_window = 'Source'
cv2.namedWindow(source_window)
cv2.imshow(source_window, img)
max_thresh = 255
thresh = 100
cv2.createTrackbar('Canny Thresh:', source_window, thresh, max_thresh, thresh_callback)
thresh_callback(thresh)
cv2.waitKey()

推荐答案

步骤 1:识别并分离输入图像中的斑点.

Step 1: Identify and separate the blobs in the input image.

由于我们这里不关心颜色信息,我们可以直接将图片加载为灰度.

Since we don't care about colour information here, we can directly load the image as grayscale.

image = cv2.imread('input.png', cv2.IMREAD_GRAYSCALE)

输入图像包含白色背景上的黑色椭圆.我们只需要斑点的外部轮廓,cv2.findContours 期望黑色背景上的白色斑点.因此我们需要反转图像.同时我们需要一个二值图像.我们可以使用 cv2.threshold 来完成这两个任务.

The input image contains black ellipses on white background. We only need the external contours of the blobs, and cv2.findContours expects white blobs on black background. Therefore we need to invert the image. At the same time we need a binary image. We can use cv2.threshold to accomplish both tasks.

一旦我们检测到 blob 轮廓,我们就可以将每个 blob 的一些有用信息收集到一个简单的基于地图的数据结构中.

Once we detect the blob contours, we can collect some useful information for each blob into a simple map-based data structure.

def detect_blobs(image):
    _,img_binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)
    contours, _ = cv2.findContours(img_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    blobs = []
    for i, contour in enumerate(contours):
        orig_x, orig_y, width, height = cv2.boundingRect(contour)
        roi_image = image[orig_y:orig_y+height,orig_x:orig_x+width]
        blobs.append({
            "i" : i
            , "contour" : contour
            , "origin" : (orig_x, orig_y)
            , "size" : (width, height)
            , "roi_image" : roi_image
        })
    return blobs


第 2 步:处理每个 blob

首先,我们需要确定 blob 是单个椭圆,还是相交椭圆上的一对.一种方法是寻找凸面缺陷.

First we need to determine whether the blob is a single ellipse, or whether it is a pair over intersecting ellipses. One way to do this is by looking for convexity defects.

由于我们轮廓的坐标是用整数表示的,即使是单椭圆场景也会出现一些凸度缺陷.但是,它们的大小(轮廓上最远点与封闭凸包段之间的距离)将非常小,通常低于 1 个像素.另一方面,一对相交椭圆的轮廓会存在较大的凸度缺陷,曲线相交的四个点各有一个.

Since the coordinates of our contour are represented by integers, even the single-ellipse scenario will exhibit some convexity defects. However, their magnitude (the distance between the furthest point on the contour from the enclosing convex hull segment) will be very small, generally below 1 pixel. On the other hand, the contour of a pair of intersecting ellipses will have large convexity defects, one for each of the four points where the curves intersect.

可以在以下两张图片中看到这种区别(轮廓为蓝色,凸包为红色,识别出的大凸面缺陷的交点/位置为橙色圆圈):

This distinction can be seen on the following two images (contour is blue, convex hull red, identified intersection points/locations of large convexity defects are orange circles):

<头>
单椭圆两个相交的椭圆

因此,我们过滤掉任何小的凸面缺陷,并注意大的位置.现在我们剩下 3 种可能的情况.

We therefore filter out any small convexity defects, and note the locations of the large ones. Now we're left with 3 possible scenarios.

场景 A:未检测到交叉点

仅识别出小的凸面缺陷,这意味着这很可能是单个椭圆.我们只需将椭圆拟合到轮廓上并继续.

Only small convexity defects were identified, which means this is very likely a single ellipse. We simply fit an ellipse to the contour and move on.

场景 B:正好检测到 4 个交点

Scenario B: Exactly 4 intersection points detected

在这种情况下,我们有 2 个相交的椭圆.我们使用交点将轮廓分成 4 个部分,每个叶"一个部分.的斑点.每个线段都应包括界定它的两个交点.

In this case we have 2 intersecting ellipses. We use the intersection points to split the contour into 4 segments, one for each "lobe" of the blob. Each of the segments should include the two intersection points that delimit it.

在下图中,线段以绿色、黄色、青色和品红色显示,交点为橙色圆圈:

In the following picture, the segments are show in green, yellow, cyan and magenta, while the intersection points are orange circles:

现在,我们可以组合彼此相对的成对线段(即绿色+青色和黄色+洋红色)以获得两个点列表,每个点一个列表.同样,我们只是将一个椭圆拟合到每个点列表中.

Now, we can combine the pairs of segments that lie opposite each other (i.e. green+cyan and yellow+magenta) to get two lists of points, one for each ellipse. Again, we simply fit an ellipse to each list of points.

场景 C:检测到一些其他数量的交点

Scenario C: Some other number of intersection points detected

这被视为无效情况.

def process_blob(blob):
    MAJOR_DEFECT_THRESHOLD = 2.0
    
    contour = blob["contour"]
    blob["hull"] = cv2.convexHull(contour)
    
    hull_idx = cv2.convexHull(contour, returnPoints=False)
    defects = cv2.convexityDefects(contour, hull_idx)
    
    intersections = []
    for i,defect in enumerate(np.squeeze(defects, 1)):
        _, _, far_idx, far_dist = defect
        real_far_dist = far_dist / 256.0
        if real_far_dist >= MAJOR_DEFECT_THRESHOLD:
            intersections.append(far_idx)
    
    if len(intersections) == 0:
        print("One ellipse")
        blob["ellipses"] = [cv2.fitEllipse(contour)]
    elif len(intersections) == 4:
        print("Two ellipses")
        blob["segments"] = [
            contour[intersections[0]:intersections[1]+1]
            , contour[intersections[1]:intersections[2]+1]
            , contour[intersections[2]:intersections[3]+1]
            , np.vstack([contour[intersections[3]:],contour[:intersections[0]+1]])
        ]
        split_contours = [
            np.vstack([blob["segments"][0], blob["segments"][2]])
            , np.vstack([blob["segments"][1], blob["segments"][3]])
        ]
        blob["ellipses"] = [cv2.fitEllipse(c) for c in split_contours]
    else:
        print("Invalid scenario")
        blob["ellipses"] = []
        
    return blob["ellipses"]


此时,计算您需要的参数是微不足道的——我将把它作为练习留给读者.


At this point, it's trivial to calculate the parameters you need -- I'll leave this as an excercise to the reader.

作为奖励,这里有一些用于调试的简单可视化:

As a bonus, here's some simple visualization for debugging purposes:

def visualize_blob(blob):
    PADDING = 20
    
    orig_x, orig_y = blob["origin"]
    offset = (orig_x - PADDING, orig_y - PADDING)
    
    input_img = cv2.copyMakeBorder(blob["roi_image"]
        , PADDING, PADDING, PADDING, PADDING
        , cv2.BORDER_CONSTANT, None, 255)

    adjusted_img = cv2.add(input_img, 127) - 63
    output_img_ch = cv2.cvtColor(adjusted_img, cv2.COLOR_GRAY2BGR)
    output_img_seg = output_img_ch.copy()
    output_img_el = output_img_ch.copy()
    
    cv2.drawContours(output_img_ch, [blob["hull"] - offset], 0, (127,127,255), 4)
    cv2.drawContours(output_img_ch, [blob["contour"] - offset], 0, (255,127,127), 2)
    
    SEGMENT_COLORS = [(0,255,0),(0,255,255),(255,255,0),(255,0,255)]
    if "segments" in blob:
        for i in range(4):
            cv2.polylines(output_img_seg, [blob["segments"][i] - offset], False, SEGMENT_COLORS[i], 4)
        for i in range(4):
            center = (blob["segments"][i] - offset)[0][0]
            cv2.circle(output_img_ch, center, 4, (0,191,255), -1)
            cv2.circle(output_img_seg, center, 4, (0,191,255), -1)
        
    
    for ellipse in blob["ellipses"]:
        offset_ellipse = ((ellipse[0][0] - offset[0], ellipse[0][1] - offset[1]), ellipse[1], ellipse[2])
        cv2.ellipse(output_img_el, offset_ellipse, (0,0,255), 2)
    
    cv2.imshow('', np.hstack([output_img_ch,output_img_seg, output_img_el]))
    cv2.imwrite('output_%d_ch.png' % blob["i"], output_img_ch)
    cv2.imwrite('output_%d_seg.png' % blob["i"], output_img_seg)
    cv2.imwrite('output_%d_el.png' % blob["i"], output_img_el)
    cv2.waitKey()


综合起来:


Pulling it all together:

import cv2
import numpy as np

## INSERT THE FUNCTIONS LISTED ABOVE IN THE QUESTION ##

image = cv2.imread('input.png', cv2.IMREAD_GRAYSCALE)

blobs = detect_blobs(image)
print("Found %d blob(s)." % len(blobs))

for blob in blobs:
    process_blob(blob)
    visualize_blob(blob)

这篇关于查找重叠椭圆的直径和面积(OpenCV,Python)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆