【问题标题】:Basic Shape recognition (openCV C++)基本形状识别(openCV C++)
【发布时间】:2019-01-07 18:39:38
【问题描述】:

我有一个需要为课程制作的项目,我选择的任务有点超出我的技能。 目标是计算掷骰子的结果。 现在,我正在尝试让它在示例图片上工作:

sample pic of dices

我当前的代码添加在下面:

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "iostream"

using namespace cv;
using namespace std;

Mat KostkaFunkcja(Mat image, Mat in, Scalar low, Scalar high);
int getMaxAreaContourId(vector <vector<cv::Point>> contours);
vector<Point> contoursConvexHull(vector<vector<Point> > contours, int index);
Mat ZnakiFunkcja(Mat image, Mat in, Scalar low, Scalar high);


int main(int argc, char** argv)
{
    Mat image;
    image = imread("kostki.jpg", CV_LOAD_IMAGE_COLOR);

    if (!image.data)
    {
        cout << "Could not open or find the image" << std::endl;
        return -1;
    }

    Mat imgHSV;
    Mat workimage = image;

    cvtColor(workimage, imgHSV, COLOR_BGR2HSV); //Convert the captured frame from BGR to HSV
    //red dice
    workimage = KostkaFunkcja(workimage, imgHSV, Scalar(146, 0, 31), Scalar(179, 255, 255));
    //green dice
    workimage = KostkaFunkcja(workimage, imgHSV, Scalar(25, 147, 0), Scalar(98, 255, 154));
    //yellow dice
    workimage = KostkaFunkcja(workimage, imgHSV, Scalar(22, 45, 161), Scalar(91, 255, 255));
    //black dice
    workimage = KostkaFunkcja(workimage, imgHSV, Scalar(98, 0, 0), Scalar(179, 232, 107));
    //white symbols
    workimage = ZnakiFunkcja(workimage, imgHSV, Scalar(58, 0, 183), Scalar(179, 145, 255));
    namedWindow("Kostki_kontur", CV_WINDOW_AUTOSIZE);
    imshow("Kostki_kontur", workimage);
    waitKey(0);

    return 0;

}

Mat KostkaFunkcja(Mat image, Mat in, Scalar low, Scalar high)
{
    Mat temp;
    inRange(in, low, high, temp);
    erode(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    dilate(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    dilate(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    erode(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));

    Mat srcBlur, srcCanny;
    blur(temp, srcBlur, Size(3, 3));

    Canny(srcBlur, srcCanny, 0, 100, 3, true);
    vector<vector<Point> > contours;
    findContours(srcCanny, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    int largest_contour_index = getMaxAreaContourId(contours);

    Mat drawing = Mat::zeros(srcCanny.size(), CV_8UC3);

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

    vector<Point> ConvexHullPoints = contoursConvexHull(contours, largest_contour_index);
    polylines(image, ConvexHullPoints, true, Scalar(0, 0, 255), 2);
    return image;
}

vector<Point> contoursConvexHull(vector<vector<Point> > contours, int index)
{
    vector<Point> result;
    vector<Point> pts;
    for (size_t j = 0; j< contours[index].size(); j++)
        pts.push_back(contours[index][j]);
    convexHull(pts, result);
    return result;
}

int getMaxAreaContourId(vector <vector<cv::Point>> contours)
{
    double maxArea = 0;
    int maxAreaContourId = -1;
    for (int j = 0; j < contours.size(); j++) {
        double newArea = cv::contourArea(contours.at(j));
        if (newArea > maxArea) {
            maxArea = newArea;
            maxAreaContourId = j;
        }
        return maxAreaContourId;
    }
}
Mat ZnakiFunkcja(Mat image, Mat in, Scalar low, Scalar high)
{
    Mat temp;
    inRange(in, low, high, temp);
    erode(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    dilate(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    dilate(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
    erode(temp, temp, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));

    Mat srcBlur, srcCanny;
    blur(temp, srcBlur, Size(3, 3));

    Canny(srcBlur, srcCanny, 0, 100, 3, true);
    vector<vector<Point> > contours;
    findContours(srcCanny, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    Mat drawing = Mat::zeros(srcCanny.size(), CV_8UC3);
    for (int i = 0; i< contours.size(); i++)
    {
        Scalar color = Scalar(255, 255, 255);
        drawContours(drawing, contours, i, color, 2);
        polylines(image, contours, true, Scalar(0, 0, 255), 2);
        return image;
    }

}

但我不知道如何计算不同的形状(心形、闪电、盾牌、数字)。

如果有人能给我关于如何完成这项工作的提示或解决方案,我将非常高兴。

1) 抱歉英语不好 2) 我们在课堂上没有 openCV [只有基本的 c++] 3)试图在互联网上找到任何有用的东西,但即使我找到任何东西,我也无法理解代码中发生了什么

【问题讨论】:

  • 最简单的方法是存储您要计数的符号的“灰度”版本/图像。可以使用您现在使用的算法找到骰子,然后您可以运行template matching 来查找图像中是否存在任何存储的符号。当图像质量/视角/纵横比发生变化时,这将很困难。
  • 给函数起英文名称会很有帮助,这样更容易理解它们的用途。
  • @RickM - 轮换呢?当我使用您链接的内容时,“强制”将模板与图像匹配。所以我猜如果图片上的符号被旋转,它将不匹配。我对么?我还可以使用变换透视和旋转来导出骰子并在匹配之前将其“水平”定位吗?如果是的话,有什么简单的方法吗?
  • @OskarŁukianowicz 是的,对于旋转和大小更改,它将不起作用。在那种情况下,你可以按照你的建议去做,但我认为这项任务并不容易。您可以使用HOG features,也可以使用Feature detectors and descriptors

标签: c++ opencv visual-c++


【解决方案1】:

您的项目可以分为三个步骤:

  1. 找到骰子。
  2. 从可见面中提取形状 骰子。
  3. 数数面孔。

对于所有可能的方法中的第一步,我认为显着图方法可以提供帮助。 显着图是一系列分割算法,旨在检测图像中更容易引起视觉注意的部分。

OpenCV 有一个显着性 API,它已经实现了几种显着性算法,并且您可以为每个算法获得一个分割图。

考虑到您给出显着性的示例图像很可能会集中在骰子上。

由此,您可以从原始图像中提取骰子作为 rois。

对于第 2 步),显着性算法也可能适合...或不适合,这取决于算法使用的许多统计标准。 然而,之前提取的 rois 应该只包含骰子的面,其中包含您要在步骤 3) 中计算的形状,因此基于轮廓检测的方法可能会产生相当好的结果。

一旦您在计算每个形状的方法中获得了形状,您就可以使用 templateMatching(OpenCV 中也已经实现)、基于形状敏感度量(Hausdorff、Dice、...)的聚类方法,或者许多其他的。

这里有一段代码可以帮助你处理这两个第一步。

#ifndef _DEBUG
#define _DEBUG
#endif

#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/saliency.hpp>
#include <opencv2/highgui.hpp>

#include <list>

CV_EXPORTS_W void get_regions_of_interest(cv::InputArray _src, cv::OutputArrayOfArrays mv, cv::OutputArrayOfArrays mv2 = cv::noArray());

int main()
{
    cv::Mat tmp = cv::imread("C:\Desktop\dices.jpg");

    if(!tmp.empty())
    {
        cv::imshow("source",tmp);
        cv::waitKey(-1);
    }

    std::vector<cv::Mat> rois;

    get_regions_of_interest(tmp,rois);

    std::cout << "Hello World!" << std::endl;
    return 0;
}

void get_regions_of_interest(cv::InputArray _src, cv::OutputArrayOfArrays _rois, cv::OutputArrayOfArrays _contours)
{

    // Check that the first argument is an image and the second a vector of images.
    CV_Assert(_src.isMat() && !_src.depth() && (_src.channels() == 1 || _src.channels() == 3) && _rois.isMatVector() && (!_contours.needed() || (_contours.needed() && _contours.isMatVector()) ) );

    static cv::Ptr<cv::saliency::StaticSaliencySpectralResidual> saliency;

    if(!saliency)
        saliency = cv::saliency::StaticSaliencySpectralResidual::create();

    cv::Mat src = _src.getMat();
    cv::Mat gray;

    if(src.depth() == src.type())
        gray = src;
    else
        cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    bool is_ctr_needed = _contours.needed();
    std::list<cv::Mat> final_ctrs;

    // Step 1) Process the saliency in order to segment the dices.

    cv::Mat saliency_map;
    cv::Mat binary_map;

    saliency->computeSaliency(src,saliency_map);
    saliency->computeBinaryMap(saliency_map,binary_map);


    saliency_map.release();

    // Step 2) From the binary map get the regions of interest.

    cv::Mat1i stats;
    std::vector<cv::Mat> rois;

    cv::Mat labels;
    cv::Mat centroids;

    cv::connectedComponentsWithStats(binary_map, labels, stats, centroids);


    labels.release();
    centroids.release();

    // prepare the memory
    rois.reserve(stats.rows-1);

// Sort the stats in order to remove the background.

    stats = stats.colRange(0,stats.cols-1);

    // Extract the rois.

    for(int i=0;i<stats.rows;i++)
    {
        cv::Rect roi = *reinterpret_cast<cv::Rect*>(stats.ptr<int>(i));

        if(static_cast<std::size_t>(roi.area()) == gray.total())
            continue;

        rois.push_back(gray(roi));
#ifdef _DEBUG
        cv::imshow("roi_"+std::to_string(i),gray(roi));
#endif
    }



    // Step 3) Refine.

    // Because the final number of shape cannot be determine in advance it is better to use a linked list than a vector.
    // In practice except if there is a huge number of elements to work with the performance will be almost the same.
    std::list<cv::Mat> shapes;

    int cnt=0;
    for(const cv::Mat& roi : rois)
    {

        cv::Mat tmp = roi.clone();

        // Slightly sharpen the regions contours
        cv::morphologyEx(tmp,tmp, cv::MORPH_CLOSE, cv::noArray());
        // Reduce the influence of local unhomogeneous illumination.
        cv::GaussianBlur(tmp,tmp,cv::Size(31,31), 5);

        cv::Mat thresh;
        // Binarize the image.
        cv::threshold(roi,thresh,0.,255.,cv::THRESH_BINARY | cv::THRESH_OTSU);
#ifdef _DEBUG
        cv::imshow("thresh"+std::to_string(cnt++),thresh);
#endif
        // Find the contours of each sub region on interest
        std::vector<cv::Mat> contours;

        cv::findContours(thresh, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

        cv::Mat dc;

        cv::merge(std::vector<cv::Mat>(3,thresh),dc);

//        cv::drawContours(dc, contours,-1,cv::Scalar(0.,0.,255),2);
//        cv::imshow("ctrs"+std::to_string(cnt),dc);

        // Extract the sub-regions

        if(is_ctr_needed)
        {
            for(const cv::Mat& ctrs: contours)
            {

                cv::Rect croi = cv::boundingRect(ctrs);

                // If the sub region is to big or to small it is depreate
                if(static_cast<std::size_t>(croi.area()) == roi.total() || croi.area()<50)
                    continue;

                final_ctrs.push_back(ctrs);

                shapes.push_back(roi(croi));

#ifdef _DEBUG
                cv::rectangle(dc,croi,cv::Scalar(0.,0.,255.));

                cv::imshow("sub_roi_"+std::to_string(cnt++),roi(croi));
#endif
            }
        }
        else
        {
            for(const cv::Mat& ctrs: contours)
            {

                cv::Rect croi = cv::boundingRect(ctrs);

                // If the sub region is to big or to small it is depreate
                if(static_cast<std::size_t>(croi.area()) == roi.total() || croi.area()<50)
                    continue;

                shapes.push_back(roi(croi));

#ifdef _DEBUG
                cv::rectangle(dc,croi,cv::Scalar(0.,0.,255.));

                cv::imshow("sub_roi_"+std::to_string(cnt++),roi(croi));
#endif

            }
        }

    }
#ifdef _DEBUG
    cv::waitKey(-1);
#endif

    // Final Step: set the output

    _rois.create(shapes.size(),1,CV_8U);
    _rois.assign(std::vector<cv::Mat>(shapes.begin(),shapes.end()));

    if(is_ctr_needed)
    {
        _contours.create(final_ctrs.size(),1,CV_32SC2);
        _contours.assign(std::vector<cv::Mat>(final_ctrs.begin(), final_ctrs.end()));
    }

}

【讨论】:

  • TY for anwser!哇...信息量很大...我想我明天会研究一下,但乍一看,我大部分都看不懂。
  • 我能理解。我检查了我给你的代码是否有效,这样你就可以让它工作并查看不同的 imshow 输出,这比阅读代码更容易。
  • 第一个问题是,当我尝试运行您的代码时,出现错误:1>c:\users\user\desktop\kostki\kostki\kostki\main.cpp(9) : 致命错误 C1083: Nie można otworzyć pliku dołącz: 'opencv2/saliency.hpp': 没有这样的文件或目录我正在使用他们网站上的最新 OpenCV,我需要做些什么特别的事情才能使用 #include ?
  • 你编译过 opencv_contrib 模块吗?
  • 如你所见:github.com/opencv OpenCV 主要由 4 个 git hub 组成。 OpenCV 包含通用版本的库,OpenCV_contrib 包含一些额外的模块,OpenCV_extra 只是一些对某些应用程序有帮助的图像数据库,并且是某些 contrib 模块所需要的。 CVAT 是一种图像和视频注释工具。
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 1970-01-01
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 2014-04-10
  • 1970-01-01
  • 2012-12-13
相关资源
最近更新 更多