如何在存在重叠和噪声的情况下从图像中分割主要形状?
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()
【讨论】:
您是如何确定内核大小的?以上是关于如何在存在重叠和噪声的情况下从图像中分割主要形状?的主要内容,如果未能解决你的问题,请参考以下文章