【问题标题】:How to go about doing blob detection on non-circular objects? (No thresholding/binarising)如何对非圆形物体进行斑点检测? (无阈值/二值化)
【发布时间】:2025-12-22 19:35:12
【问题描述】:

我正在处理星系图像,我想使用斑点检测来识别这些图像中心的星系,然后掩盖斑点之外的像素。我使用了 cv2 和 skimage 的斑点检测模块,但它们只返回圆形斑点。只要星系完全包含在圆圈内,这实际上就可以了,但问题是这些斑点往往小于非圆形斑点的实际大小(例如椭圆形的斑点)。

blob 太小的示例:

斑点检测结果与实际星系:

我尝试过高斯模糊。

是否有任何实现/方法让我能够获得也与银河系形状相匹配的斑点?如果可能,我想避免使用阈值方法。

我现在的代码(只是包的基本使用):

fig, axes = plt.subplots(4, 5, figsize=(25, 20), sharex=True, sharey=True)
ax = axes.ravel()
i = 0

for galaxy, original_img_data in nsa_galaxy_images.items():
    
    blurred_img = skimage.filters.gaussian(original_img_data, sigma=3)
    blobs_dog = blob_dog(blurred_img, max_sigma=100, threshold=.1, exclude_border=False, overlap=1)
    blobs_dog[:, 2] = blobs_dog[:, 2] * sqrt(2)


    ax[i].set_title(galaxy)
    ax[i].imshow(original_img_data)

    for blob in blobs_dog:
            
        y, x, r = blob
        c = plt.Circle((x, y), r, color='green', linewidth=2, fill=False)

        ax[i].add_patch(c)
        ax[i].set_axis_off()

    i += 1

编辑 1:

正如@Cris Luengo 所建议的,这里的代码允许您下载类似的星系图像以在 python 中使用:

from astroquery.skyview import SkyView

FAMOUS_GALAXIES_LIST = ['NGC 450', 'NGC 5792', 'NGC 4437', 
                        'NGC 1032', 'NGC 4753',
                        'NGC 60', 'NGC 5496', 'NGC 936',
                        ]

galaxy_images = {}

for galaxy in FAMOUS_GALAXIES_LIST:
    img = SkyView.get_images(galaxy,survey=['SDSSi'], pixels=250,)
    original_img_data = img[0][0].data.copy()
    galaxy_images[galaxy] = original_img_data

【问题讨论】:

  • 你试过没有循环过滤的 OpenCV SimpleBlobDetector 吗?但是,他们仍然只会返回循环答案。也许您可以在仅使用检测器发现斑点的存在后更好地分割斑点,而不使用圆形作为形状?
  • 您是否有理由不想对图像进行阈值处理?您是否考虑过改用contours?如果您要寻找更适合您的图像的边界区域,则可以将ellipses 拟合到轮廓。
  • 你能发布一张或几张没有注释的星系图像吗?它将允许人们在提议之前尝试一种方法。
  • 如果你能描述一下你在手动确定一个星系是什么/在哪里以及被其包围时应用的逻辑或想法,也许会有所帮助。这样可能更容易思考如何实现“手动”逻辑。另外,在你看来,星系是不规则的形状,还是可以用椭圆合理地近似?答案将在该方法中发挥重要作用。
  • IMO 认为这些星系完全有形状是一种错误的观点,因为它们正在平滑地融合到背景中。所以你需要声明一个规则来定义大纲。据我所知,您能做的最好的事情就是设置……密度阈值。

标签: python opencv image-processing scikit-image


【解决方案1】:

这还不是一个解决方案,但也许对您来说是一个很好的起点。

想法:

  1. 从星系中心(假定的图像中心):追踪强度直到找到特定背景
  2. 对不同的角度重复此操作

我用过这张图片

int main()
{
    //cv::Mat img = cv::imread("C:/data/*/skyView/SAMPLE.png", cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
    //cv::Mat img = cv::imread("C:/data/*/skyView/SAMPLE.png", cv::IMREAD_GRAYSCALE);
    cv::Mat imgColor = cv::imread("C:/data/*/skyView/SAMPLE.png");
    std::vector<cv::Mat> colorChannels;
    cv::split(imgColor, colorChannels);
    cv::Mat img = colorChannels[2]; // R channel

    cv::Scalar meanAvg = cv::mean(img);

    

    for (double degrees = 0; degrees <= 180; degrees += 22.5)
    {
        cv::Point2f zero = cv::Point(1, 0);
        double rad = degrees / 180.0 * CV_PI;
        cv::Point2f current = cv::Point2f(cos(rad) * zero.x + -sin(rad) * zero.y, sin(rad * zero.x) + cos(rad) * zero.y);
        //cv::LineIterator lit = cv::LineIterator(img, cv::Point(0, img.rows - 1), cv::Point(img.cols - 1, 0));
        cv::LineIterator lit = cv::LineIterator(img, cv::Point(img.cols/2.0 , img.rows/2.0) - cv::Point(sqrt(2)*img.cols*current), cv::Point(img.cols / 2.0, img.rows / 2.0) + cv::Point(sqrt(2)*img.cols * current));
        int scale = 6;
        cv::Mat plotLine = cv::Mat::zeros(256, lit.count * scale, CV_8UC1);
        cv::Mat plot = cv::Mat::zeros(256, lit.count * scale, CV_8UC1);
        cv::Point prev;
        for (int i = 0; i < lit.count; ++lit, ++i)
        {
            uchar val = *(*lit);
            uchar val_inv = 255 - val;
            cv::Point pos = cv::Point(i * scale + scale / 2, val_inv);
            if (prev == cv::Point()) prev = pos;
            cv::line(plot, prev, pos, cv::Scalar::all(255), 1);
            prev = pos;
        }

        cv::Mat plotFlipped;
        cv::flip(plot, plotFlipped, 1);

        std::vector<cv::Mat> channels;
        channels.push_back(plot);
        channels.push_back(plotFlipped);
        cv::line(plotLine, cv::Point(0, 255 - meanAvg[0]), cv::Point(plot.cols, 255 - meanAvg[0]), cv::Scalar::all(255), 1);
        channels.push_back(plotLine);

        cv::Mat merged;
        cv::merge(channels, merged);

        cv::imshow("img", img);
        cv::imshow("plot", plot);
        cv::imshow("merged", merged);
        cv::imwrite("C:/data/*/skyView/SAMPLE_plot_" + std::to_string(degrees)+ ".png", merged);
        cv::waitKey(0);
    }
}

并得到这个输出:

红线是整个图像的平均平均值(中位数会更好) 其他地块是彼此的镜像版本

45°(假定最小尺寸

90°(假定最大尺寸)

全部,从 0° 到 180°,步长为 22.5°:

尖锐的零峰是呈现给图像的绿色圆圈,它不会出现在您的原始图像中

【讨论】: