如何在存在重叠和噪声的情况下从图像中分割主要形状?

How to segment primary shapes from an image in the presence of overlap and noise?

如何根据参考图像将左图转换为右图?

这是一个想法:

  1. 加载图像,转换为灰度图像,以及 Otsu 的二值图像阈值
  2. 找到轮廓并用白色填充
  3. 现在我们有了二值图像,我们可以进行形态学操作了。根据您尝试提取的对象,我们可以创建不同的结构内核。对于矩形我们可以使用cv2.MORPH_RECT,对于椭圆我们可以删除具有较大内核大小的水平部分并使用cv2.MORPH_ELLIPSE
  4. 然后我们过滤剩余的轮廓并找到矩形和椭圆的旋转边界框

这是过程的可视化

对于椭圆

由于您没有指定语言,这里是 Python

的实现
import cv2
import numpy as np

# Load image, convert to grayscale, Otsu's threshold for binary image
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find contours and fill in contour with white
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(thresh, [c], 0, 255, -1)

# Rectangle ----------------------------------------
# Morph open to separate rectangular contour
rectangular_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (20, 20))
rect = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, rectangular_kernel, iterations=4)

# Find contours and draw rotated rectangle onto image
cnts = cv2.findContours(rect, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
isolated_rect = cv2.minAreaRect(cnts[0])
box = np.int0(cv2.boxPoints(isolated_rect))
cv2.drawContours(image, [box], 0, (36,255,12), 3)
# Rectangle ----------------------------------------

# Ellipse ----------------------------------------
# Morph open to separate elliptical contour
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,10))
ellipse = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)

# Find contours and filter for ellipse
cnts = cv2.findContours(ellipse, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Filter using contour area, could also use Aspect Ratio or contour approximation
for c in cnts:
    area = cv2.contourArea(c)
    if area > 20000:
        cv2.drawContours(image, [c], 0, (136,15,212), 3)
# Ellipse ----------------------------------------


# Display
cv2.imshow('image', image)
cv2.imshow('thresh', thresh)
cv2.imshow('rect', rect)
cv2.imshow('ellipse', ellipse)
cv2.waitKey()

使用点对的梯度下降(最小化距离),你可以得到一个数字最小切割列表。将这些最小切割应用于原始对象可以将其分割成单独的对象。您需要设置的唯一参数是 minObjectRadius,它指定您关心的形状的最小半径(它用于确定最小切割的限制和最终形状过滤)。


额外处理图像:

代码:

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <Windows.h>
#include <string>

using namespace std;
using namespace cv;

vector<tuple<int, int>> generatePointPairs(int totalIndicies, int stride = 1)
{
    vector<tuple<int, int>> pointPairs;
    for (int i = 0; i < totalIndicies; i+=stride)
    {
        for (int ii = 0; ii < totalIndicies; ii+=stride)
        {
            tuple<int, int> pair(i, ii);
            pointPairs.push_back(pair);
        }
    }
    return pointPairs;
}

double distSq(Point p1, Point p2)
{
    return pow(p1.x - p2.x, 2) + pow(p1.y - p2.y,2);
}

tuple<int, int> gradDecentPair(vector<Point> contour, tuple<int, int> pair)
{
    int index0 = get<0>(pair);
    int index1 = get<1>(pair);

    bool flip = false;

    double lastDist = distSq(contour[get<0>(pair)], contour[get<1>(pair)]);
    int flipCounter = 0;
    while (true)
    {
        bool improvementFound = false;
        int staticIndex = index1;
        if (flip) { staticIndex = index0; }
        
        double bestDist = -1;
        int bestIndex = -1;
        for (int i = -5; i <= 5; i+=1)
        {
            if (i == 0) { continue; }

            int testIndex = index0 + i;
            if (flip) { testIndex = index1 + i; }

            if (testIndex < 0)
            { testIndex += contour.size(); }
            else if (testIndex >= contour.size()) 
            { testIndex -= contour.size(); }
            

            double testDist = distSq(contour[staticIndex], contour[testIndex]);
            if (bestDist == -1 || testDist < bestDist)
            {
                bestIndex = testIndex;
                bestDist = testDist;
            }
        }

        if (bestDist < lastDist)
        {
            if (flip) { index1 = bestIndex; }
            else { index0 = bestIndex; }
            lastDist = bestDist;
            improvementFound = true;
        }


        if (index0 == index1) { break; }

        if (improvementFound) { continue; }
        else
        {
            flipCounter++;
            flip = !flip;
            if (flipCounter > 10) { break; } //pretty sure this can be better, but lazy atm
        }
    }
    return tuple<int, int>(index0, index1);
}

int main(int argc, char** argv)
{
    int minObjectRadius = 75;


    std::string  fileName = "C:/Local Software/voyDICOM/resources/images/ShapeBlob.JPG";
    Mat original = imread(fileName, cv::IMREAD_GRAYSCALE);
    imshow("Original", original);

    Mat bwImg;
    cv::threshold(original, bwImg, 0, 255, cv::THRESH_OTSU);
    bitwise_not(bwImg, bwImg);
    
    vector<vector<Point> > contours;
    findContours(bwImg, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

    Mat minCuts(original.cols, original.rows, CV_8UC3);
    fillPoly(minCuts, contours, Scalar(255,0,0));
    vector<Point> cuts;
    for (int i = 0; i < contours.size(); i++)
    {
        std::cout << contours[i].size() << std::endl;
        vector<tuple<int, int>> pointPairs = generatePointPairs(contours[i].size(), 25);
        for (int ii = 0; ii < pointPairs.size(); ii++)
        {
            tuple<int, int> minCut = gradDecentPair(contours[i], pointPairs[ii]);
            Point p1 = contours[i][get<0>(minCut)];
            Point p2 = contours[i][get<1>(minCut)];
            double tempDist = distSq(p1, p2);
            if (tempDist > 0 && tempDist <= pow(minObjectRadius, 2))
            {
                line(minCuts, contours[i][get<0>(minCut)], contours[i][get<1>(minCut)], Scalar(0, 0, 255));
                cuts.push_back(p1);
                cuts.push_back(p2);
            }
            
        }
        std::cout << i << " , " << contours.size() << std::endl;
    }
    imshow("minCuts", minCuts);

    fillPoly(bwImg, contours, 255);
    for (int i = 0; i < cuts.size(); i += 2)
    {
        line(bwImg, cuts[i],cuts[i+1], 0,2);
    }
    imshow("cutPolys", bwImg);

    Mat finalShapes = imread(fileName, cv::IMREAD_COLOR);
    int colorIndex = 0;
    vector<Scalar> colors = { Scalar(255,0,0),Scalar(0,0,255),Scalar(0,0,0) };
    vector<vector<Point> > contoursFinal;
    findContours(bwImg, contoursFinal, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contoursFinal.size(); i++)
    {
        double tempArea = contourArea(contoursFinal[i]);
        if (tempArea < pow(minObjectRadius, 2)) { continue; }

        vector<vector<Point>> singleContour;
        singleContour.push_back(contoursFinal[i]);
        fillPoly(finalShapes, singleContour, colors[colorIndex]);
        colorIndex++;
        if (colorIndex >= colors.size()) { colorIndex = 0; }
    }
    imshow("finalPolys", finalShapes);

    waitKey(0);
}

编辑:grad decent 的简要说明 生成大量随机点对(“播种”),然后继续执行上述过程。 (如果处理时间不是问题,您可以稀疏或密集地播种并包括每个可能的点对)我的突破条件很懒惰(仅在 10 个枢轴后结束)并且可以改进以检查距离是否没有显着改善(但是10 个枢轴确保不会无休止地弹跳)。此外,从技术上讲,我的实现仅在没有为当前枢轴找到更好的距离(不是每个周期)时才切换枢轴。此外,任何小于最小形状半径的最终点对都将被丢弃(许多点对种子将求解到相同的点或非常接近的点,因为这是最佳距离)。