【问题标题】:Detect rectangles drawn on an background image using OpenCV使用 OpenCV 检测在背景图像上绘制的矩形
【发布时间】:2016-05-21 22:04:17
【问题描述】:

我正在尝试检测在图像上绘制的一些矩形(白色)。 (比如使用绘画或其他图像编辑工具)。 由于我是图像处理的初学者,我搜索了网络和 OpenCV 示例程序来完成这项工作,但无法让它完美地工作。我正在使用 OpenCV C++ 库。

我试过的算法

cv::Mat src = cv::imread(argv[1]);
cv::Mat gray;
cv::cvtColor(src, gray, CV_BGR2GRAY);
meanStdDev(gray, mu, sigma);
cv::Mat bw;
cv::Canny(gray, bw, mu.val[0] - sigma.val[0], mu.val[0] + sigma.val[0]);
std::vector<std::vector<cv::Point> > contours;
cv::findContours(bw.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

std::vector<cv::Point> approx;
for (int i = 0; i < contours.size(); i++){
cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);
if (approx.size() >= 4 && approx.size() <= 6)
Rect boundRect = boundingRect( Mat(approx) );
rectangle( dst, boundRect.tl(), boundRect.br(), Scalar(255,255,255), 1, 8, 0 );}

只检测到一个矩形。能否请您指导我或提供相同的链接。

输入图片:

输出图像:

【问题讨论】:

  • 什么是i(例如contours[i])?
  • 我正在遍历 contours.size(),我已经编辑了我之前的帖子。很抱歉造成混乱。
  • 你能std::cout &lt;&lt; contours.size() &lt;&lt; std::endl;吗?是否找到多个轮廓?你确定approx.size() &lt;= 6 没有过滤掉很多好的轮廓吗?你玩过那个参数吗?请发布示例输入图像。
  • contours.size() 347. 我在帖子中添加了示例输入图像
  • 我猜图像中的干扰太多了,所以大多数白色矩形在canny图像中都会有额外的结构。

标签: c++ opencv image-processing


【解决方案1】:

我无法编译您的代码示例,因为在 if 块中声明了 boundRect,但矩形绘图(试图访问 boundRect)在 if 块之外,所以我调整了您的代码:

int main(int argc, char* argv[])
{
    cv::Mat src = cv::imread("C:/StackOverflow/Input/rectangles.png");
    cv::Mat dst = src.clone();

    cv::Mat gray;
    cv::cvtColor(src, gray, CV_BGR2GRAY);

    // ADDED: missing declaration of mu and sigma
    cv::Scalar mu, sigma;
    meanStdDev(gray, mu, sigma);
    cv::Mat bw;
    cv::Canny(gray, bw, mu.val[0] - sigma.val[0], mu.val[0] + sigma.val[0]);

    // ADDED: displaying the canny output
    cv::imshow("canny", bw);


    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(bw.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    std::vector<cv::Point> approx;
    for (int i = 0; i < contours.size(); i++){
        cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);
        if (approx.size() >= 4 && approx.size() <= 6)
        {
            // ADDED: brackets around both lines belonging to the if-block
            cv::Rect boundRect = cv::boundingRect(cv::Mat(approx));
            cv::rectangle(dst, boundRect.tl(), boundRect.br(), cv::Scalar(255, 255, 255), 3, 8, 0);
        }

    }

    // ADDED: displaying input and results
    cv::imshow("input", src);
    cv::imshow("dst", dst);
    cv::imwrite("C:/StackOverflow/Output/rectangles.png", dst);
    cv::waitKey(0);
    return 0;
}

使用您的输入图像,我确实得到了以下输出:

这可能不是您所期望的。查看精巧的输出图像(查看中间结果以进行可视化调试总是好的!),图像中的结构太多,轮廓将涵盖所有这些,因此有些将近似为多项式有 4 到 6 个元素。

相反,您必须变得更聪明一些。您可以尝试使用 cv::HoughLinesP 提取直线并连接这些线。或者您可以尝试先通过查找白色区域来分割图像(如果您的矩形始终是白色的)。

int main(int argc, char* argv[])
{
    cv::Mat src = cv::imread("C:/StackOverflow/Input/rectangles.png");
    cv::Mat dst = src.clone();

    cv::Mat gray;
    cv::cvtColor(src, gray, CV_BGR2GRAY);

    cv::Mat mask;
    // find "white" pixel
    cv::inRange(src, cv::Scalar(230, 230, 230), cv::Scalar(255, 255, 255), mask);
    cv::imshow("mask", mask);

    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    std::vector<cv::Point> approx;
    for (int i = 0; i < contours.size(); i++){
        cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);
        if (approx.size() >= 4 && approx.size() <= 6)
        {
            cv::Rect boundRect = cv::boundingRect(cv::Mat(approx));
            cv::rectangle(dst, boundRect.tl(), boundRect.br(), cv::Scalar(255, 255, 255), 1, 8, 0);
        }
    }


    cv::imshow("input", src);
    cv::imshow("dst", dst);
    cv::imwrite("C:/StackOverflow/Output/rectangles2.png", dst);
    cv::waitKey(0);
    return 0;
}

给出这个结果:

如您所见,白色附近还有其他明亮区域。多项式逼近也没有多大帮助。

【讨论】:

  • 酷@Micka,这确实更聪明。我需要扩大范围以获得期望结果 cv::inRange(src, cv::Scalar(190, 190, 190), cv::Scalar(255, 255, 255), mask);但正如你所说,这个解决方案的稳健性是一个问题。
【解决方案2】:

一般来说,在 HSV 空间中分割颜色(甚至是白色)更容易。使用适当的阈值:

inRange(hsv, Scalar(0, 0, 220), Scalar(180, 30, 255), mask);

我们不关心色调,只保留低饱和度和高值,我得到:

然后您可以轻松找到连接的组件,并丢弃小于阈值th_blob_size 的 blob。生成的矩形是(绿色):

您最终可以应用其他过滤阶段来解决更困难的情况,但对于此图像,去除小斑点就足够了。如果您一般需要更强大的东西,请发布其他图片。

代码:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc, char** argv)
{
    Mat3b img = imread("path_to_image");

    int th_blob_size = 100;

    Mat3b hsv;
    cvtColor(img, hsv, COLOR_BGR2HSV);

    Mat1b mask;
    inRange(hsv, Scalar(0, 0, 220), Scalar(180, 30, 255), mask);

    vector<vector<Point>> contours;
    findContours(mask.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

    Mat3b res = img.clone();
    for (int i = 0; i < contours.size(); ++i)
    {
        // Remove small blobs
        if (contours[i].size() < th_blob_size)
        {
            continue;
        }

        Rect box = boundingRect(contours[i]);
        rectangle(res, box, Scalar(0,255,0), 1);
    }

    imshow("Result", res);
    waitKey();

    return 0;
}

【讨论】:

  • 感谢您的回复。当背景颜色和轮廓颜色相同时,在图片中分割颜色的方法是失败的。 :(
  • 好吧,1) 请提供该案例的图片。有很多改进的空间。 2) 你为什么不把你的矩形变成亮绿色的 0,255,0,这样在图像中出现的机会几乎是 0?此外,在没有抗锯齿或 jpeg 压缩的情况下绘制的矩形图像会好得多
  • 添加图片示例
【解决方案3】:

您确定只找到一个轮廓还是只绘制一个轮廓?看起来您并没有在绘图例程中循环,因此您只会绘制找到的第一个。

我有一个博客,早就死了,它可能会为你提供一些好的方向:http://workingwithcomputervision.blogspot.co.uk/2012/09/game-player-step-2-finding-game-board.html

如果链接失效,我相信这是文章中与绘制轮廓最相关的部分:

//Draw contours
for (int i = 0; i < contours.size(); i++) {
Scalar color = Scalar(0, 255, 0);
drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
}

我注意到您正在使用边界矩形进行绘图。这是另一个绘图例程,同样来自上面的链接,它执行此操作:

Rect bounds;
Mat drawing = Mat::zeros(purpleOnly.size(), CV_8UC3);
int j = 0;
for (int i = 0; i < contours.size(); i++) {
   if (arcLength(contours[i], true) > 500){
       Rect temp = boundingRect(contours[i]);
       rectangle(drawing, temp, Scalar(255, 0, 0), 2, 8);
       if (j == 0) {
           bounds = temp;
       } else {
           bounds = bounds | temp;
       }
       j++;
   }
}

请注意,我还对轮廓的大小进行了一些检查以滤除噪声。

【讨论】:

  • 感谢您的回复,我粘贴的代码是我使用的算法,但在实际运行的代码中,我循环了所有的 contours.size()。
  • 抱歉帮助不大 :( 似乎它只绘制边界矩形。我想检测矩形是问题而不是把它画回来。
  • 哦,好吧,自从我查看 OpenCV 以来已经有一段时间了,但是当我浏览时这个问题落在了我的膝上,我认为这会涵盖它。还建议像@Micka 那样查看中期结果,这样做你会收获很多,但没有时间。如果它涵盖了您的问题并很好地解决了您的问题,请不要忘记投票/接受他的回答。
  • 好吧,Micka 的 sol 给了我一个结果,这在特定情况下确实很好,但是一旦在相对白色的背景上添加了更多矩形,它就无法检测到。 :(。所以绝对是赞成票,但恐怕不能接受。
  • 不用担心,何时或是否接受答案取决于您,只是轻轻提醒以防万一。 :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
  • 2011-12-20
相关资源
最近更新 更多