c++ - 如何忽略/删除接触图像边界的轮廓

标签 c++ opencv

我有以下代码使用 cvThresholdcvFindContours 检测图像中的轮廓:

CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contours = 0;

cvThreshold( processedImage, processedImage, thresh1, 255, CV_THRESH_BINARY );
nContours = cvFindContours(processedImage, storage, &contours, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, cvPoint(0,0) );

我想以某种方式扩展此代码以过滤/忽略/删除任何接触图像边界的轮廓。但是我不确定如何去做。我应该过滤阈值图像还是可以过滤轮廓?希望有人知道一个优雅的解决方案,因为令人惊讶的是我无法通过谷歌搜索找到解决方案。

最佳答案

更新 2021-11-25

  • 更新代码示例
  • 修复了图像边框的错误
  • 添加更 multimap 片
  • 添加带有 CMake 支持的 Github 存储库以构建示例应用

可在此处找到完整的开箱即用示例: C++ application with CMake

一般信息

  • 我正在使用 OpenCV 3.0.0
  • 使用 cv::findContours 实际上会改变输入图像,因此请确保您专门针对此功能处理单独的拷贝,或者根本不进一步使用该图像

2019-03-07 更新:“因为 opencv 3.2 源图像未被此函数修改。”(参见 corresponding OpenCV documentation)

通用解决方案

关于轮廓,您只需要知道它的任何点是否触及图像边界即可。可以通过以下两个过程之一轻松提取此信息:

  • 检查轮廓的每个点的位置。如果它位于图像边界(x = 0 或 x = width - 1 或 y = 0 或 y = height - 1),则忽略它。
  • 围绕轮廓创建边界框。如果边界框位于图像边界上,那么您知道轮廓也是如此。

第二种方案(CMake)的代码:

cmake_minimum_required(VERSION 2.8)

project(SolutionName)

find_package(OpenCV REQUIRED)

set(TARGETNAME "ProjectName")

add_executable(${TARGETNAME} ./src/main.cpp)

include_directories(${CMAKE_CURRENT_BINARY_DIR} ${OpenCV_INCLUDE_DIRS} ${OpenCV2_INCLUDE_DIR})
target_link_libraries(${TARGETNAME} ${OpenCV_LIBS})

第二种方案的代码(C++):

bool contourTouchesImageBorder(const std::vector<cv::Point>& contour, const cv::Size& imageSize)
{
    cv::Rect bb = cv::boundingRect(contour);

    bool retval = false;

    int xMin, xMax, yMin, yMax;

    xMin = 0;
    yMin = 0;
    xMax = imageSize.width - 1;
    yMax = imageSize.height - 1;

    // Use less/greater comparisons to potentially support contours outside of 
    // image coordinates, possible future workarounds with cv::copyMakeBorder where
    // contour coordinates may be shifted and just to be safe.
    // However note that bounding boxes of size 1 will have their start point
    // included (of course) but also their and with/height values set to 1 
    // but should not contain 2 pixels.
    // Which is why we have to -1 the "search grid"
    int bbxEnd = bb.x + bb.width - 1;
    int bbyEnd = bb.y + bb.height - 1;
    if (bb.x <= xMin ||
        bb.y <= yMin ||
        bbxEnd >= xMax ||
        bbyEnd >= yMax)
    {
        retval = true;
    }

    return retval;
}

通过以下方式调用它:

...
cv::Size imageSize = processedImage.size();
for (auto c: contours)
{
    if(contourTouchesImageBorder(c, imageSize))
    {
        // Do your thing...
        int asdf = 0;
    }
}
...

完整的 C++ 示例:

void testContourBorderCheck()
{
    std::vector<std::string> filenames =
    {
        "0_single_pixel_top_left.png",
        "1_left_no_touch.png",
        "1_left_touch.png",
        "2_right_no_touch.png",
        "2_right_touch.png",
        "3_top_no_touch.png",
        "3_top_touch.png",
        "4_bot_no_touch.png",
        "4_bot_touch.png"
    };

    // Load example image
    //std::string path = "C:/Temp/!Testdata/ContourBorderDetection/test_1/";
    std::string path = "../Testdata/ContourBorderDetection/test_1/";

    for (int i = 0; i < filenames.size(); ++i)
    {
        //std::string filename = "circle3BorderDistance0.png";
        std::string filename = filenames.at(i);
        std::string fqn = path + filename;
        cv::Mat img = cv::imread(fqn, cv::IMREAD_GRAYSCALE);

        cv::Mat processedImage;
        img.copyTo(processedImage);

        // Create copy for contour extraction since cv::findContours alters the input image
        cv::Mat workingCopyForContourExtraction;
        processedImage.copyTo(workingCopyForContourExtraction);

        std::vector<std::vector<cv::Point>> contours;
        // Extract contours 
        cv::findContours(workingCopyForContourExtraction, contours, cv::RetrievalModes::RETR_EXTERNAL, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE);

        // Prepare image for contour drawing
        cv::Mat drawing;
        processedImage.copyTo(drawing);
        cv::cvtColor(drawing, drawing, cv::COLOR_GRAY2BGR);

        // Draw contours
        cv::drawContours(drawing, contours, -1, cv::Scalar(255, 255, 0), 1);

        //cv::imwrite(path + "processedImage.png", processedImage);
        //cv::imwrite(path + "workingCopyForContourExtraction.png", workingCopyForContourExtraction);
        //cv::imwrite(path + "drawing.png", drawing);

        const auto imageSize = img.size();
        bool liesOnBorder = contourTouchesImageBorder(contours.at(0), imageSize);
        // std::cout << "lies on border: " << std::to_string(liesOnBorder);
        std::cout << filename << " lies on border: "
            << liesOnBorder;
        std::cout << std::endl;
        std::cout << std::endl;

        cv::imshow("processedImage", processedImage);
        cv::imshow("workingCopyForContourExtraction", workingCopyForContourExtraction);
        cv::imshow("drawing", drawing);
        cv::waitKey();

        //cv::Size imageSize = workingCopyForContourExtraction.size();
        for (auto c : contours)
        {
            if (contourTouchesImageBorder(c, imageSize))
            {
                // Do your thing...
                int asdf = 0;
            }
        }
        for (auto c : contours)
        {
            if (contourTouchesImageBorder(c, imageSize))
            {
                // Do your thing...
                int asdf = 0;
            }
        }
    }
}

int main(int argc, char** argv)
{
    testContourBorderCheck();
    return 0;
}

图像边界附近的轮廓检测问题

OpenCV 似乎无法正确找到图像边界附近的轮廓。

对于这两个对象,检测到的轮廓是相同的(见图片)。然而,在图像 2 中,检测到的轮廓不正确,因为对象的一部分位于 x = 0 处,但轮廓位于 x = 1 处。

这对我来说似乎是一个错误。 这里有一个关于此的未决问题:https://github.com/opencv/opencv/pull/7516

似乎也有 cv::copyMakeBorder ( https://github.com/opencv/opencv/issues/4374 ) 的解决方法,但它似乎有点复杂。

如果您能有点耐心,我建议您等待 OpenCV 3.2 的发布,它应该会在接下来的 1-2 个月内发布。

新示例图片: 单像素左上角,物体左,右,上,下,每个接触和不接触(1px距离)

image

image

image

image

image

image

image

image


示例图片

  • 物体接触图像边框
  • 物体不接触图像边框
  • 物体接触图像边框的轮廓
  • 不接触图像边界的对象轮廓

Object touching image border

Object not touching image border

Contour for object touching image border

Contour for object not touching image border

关于c++ - 如何忽略/删除接触图像边界的轮廓,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40615515/

相关文章:

c++ - 从字符串c++的开头和结尾删除标点符号

c++ - 多重继承 : using a member function of a private base

c++ - 模板特化不明确

opencv - 如何在 OpenCV 中的 CV_32FC1 和 CV_32FC3 图像之间进行像素倍增

numpy - 将有符号 float 转换为 uint8 图像 opencv

c++ - 如何像初始化一样给结构赋值

c++ - 递归返回最小索引

python - 如何在opencv-python中填充canny边缘图像

python - VideoCapture() opencv python pyinstaller 打不开

java - BackgroundSubtractor.apply() 不应该返回二进制掩码吗?