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

Posted

技术标签:

【中文标题】如何在存在重叠和噪声的情况下从图像中分割主要形状?【英文标题】:How to segment primary shapes from an image in the presence of overlap and noise? 【发布时间】:2021-11-14 16:41:04 【问题描述】:

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

【问题讨论】:

您的问题没有正确描述问题。初始形状有一个很大的表面(实际上是最大的)。请改写。 【参考方案1】:

使用点对的梯度体面(最小化距离),您可以获得数字最小切割列表。将这些最小切割应用于原始对象可以将其分割成单独的对象。您需要设置的唯一参数是 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);


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

【讨论】:

非常感谢您为这个问题提供了可接受的解决方案。 如果可能的话,你能画出一个由梯度下降产生的最小切割的例子吗?我喜欢你的最终输出,但我不太明白如何根据你提供的蓝色图像进行最小剪切操作。 我不确定我是否理解您的问题。上面的代码使用了该策略,附加图片显示了一些子步骤。 你能画出并展示剪切是如何产生的吗?使用任何绘图工具,如 MS Paint。 当然,我今天下午会尝试添加一些解释【参考方案2】:

这是一个想法:

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

这是过程的可视化

对于椭圆

由于您没有指定语言,所以这里是用 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()

【讨论】:

您是如何确定内核大小的?

以上是关于如何在存在重叠和噪声的情况下从图像中分割主要形状?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV - 从图像中分割树

图像处理之霍夫变换(直线检测算法)

图像处理之霍夫变换(直线检测算法)

如何在不启动容器的情况下从 dockerfile 创建图像 [重复]

如何在不重新保存的情况下从 iCloud 下载图像?

机器为什么可以学习---错误和噪声