如何使用openCV填充由于重叠而导致的边缘并仍然保持其形状?无法检测棋盘中的单个方形轮廓

问题描述 投票:0回答:1

Gray scale image

在此图像中,由于 g 与板边缘重叠,无法检测到正方形 g1。这是一个精明的边缘图像来显示我的意思: Canny Edge Image

这是截取屏幕截图并检测板并调整图像大小后的最小可重现代码:

#include <cassert>
#include <string>
#include <opencv2/opencv.hpp>
#include <cstdbool>
#include <vector>
#include <cwchar>
using std::vector;
using namespace cv;
#define ZERO 0
#define MAXVAL 255
static const cv::Size IMG_ANALYSIS_SZ = cv::Size(500, 500);
static const cv::Size STRUCT_KERN_SZ = cv::Size(2, 2);

void show_images(vector<cv::Mat> images) {
    size_t sz = images.size();
    for (size_t count = 0; count < sz; count++) {
        std::string strCnt = std::to_string(count);
        cv::imshow(strCnt, images[count]);
    }
    while (cv::getWindowProperty("0", cv::WND_PROP_VISIBLE) >= 0) {
        int key = cv::waitKey(200);
        if (key == 27) {  // If Esc key is pressed
            break;
        }
    }
    cv::destroyAllWindows();
}

static inline double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0) { // vec A . vec B = |A| |B| cos(theta) 
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

static inline bool isSquare(double area, double perimeter, vector<cv::Point> contour) {
    double ratio = (16 * area) / (perimeter * perimeter);
    vector<cv::Point> approx;
    approxPolyDP(contour, approx, perimeter*0.02, true);
    if ((ratio > 0.95 && ratio < 1.05) && approx.size() == 4 && isContourConvex(approx)) {
        double maxCos = 0;
        for(size_t i = 2; i < 5; i++) {
            double cos = fabs(angle(approx[i%4], approx[i-2], approx[i-1]));
            if(cos > maxCos) {
                maxCos = cos;
            }
        }
        if(maxCos < 0.3) {  // cos(90) -> 0
            return true;
        }
    }
    return false;
}

int main(void) {
    cv::Mat img = cv::imread("0_screenshot.png");
    cv::Mat edge_img;
    cv::Canny(img, edge_img, 0, 255);
    Mat kernel = getStructuringElement(MORPH_RECT, STRUCT_KERN_SZ);
    dilate(edge_img, edge_img, kernel);
    // I tried the following codes with different configuration and kernels etc.
    //cv::morphologyEx(edge_img, edge_img, cv::MORPH_OPEN, kernel);
    //cv::morphologyEx(edge_img, edge_img, cv::MORPH_CLOSE, kernel);
    //erode(edge_img, edge_img, kernel);
    vector<vector<cv::Point>> contours;
    cv::findContours(edge_img, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
    size_t sz = contours.size();
    vector<vector<cv::Point>> square_contours;
    for (size_t i = 0; i < sz; i++) {
        double area = cv::contourArea(contours[i]);
        double perimeter = cv::arcLength(contours[i], true);
        // why 3200? image size = 500 x 500 and 64 squares on the board
        // (500 x 500) / 64 = 3906.25, So, area of each square little bit less than 3900
        if(isSquare(area, perimeter, contours[i]) && area > 3200 && area < 3910) { 
            square_contours.push_back(contours[i]);
        }    
    }
    printf("%zu", square_contours.size());
    cv::Mat contour_image = cv::Mat::zeros(edge_img.size(), CV_8UC3);
    cv::drawContours(contour_image, square_contours, -1, cv::Scalar(0, 255, 0), 2);
    show_images({ img, contour_image, edge_img });
    return EXIT_SUCCESS;
}

这是轮廓图的输出图像: enter image description here

标准输出:

63

我尝试了很多方法,但到目前为止没有任何效果。我未能检测到棋盘中的单个方形轮廓,即 g1。我可以做什么来解决这个问题?

c++ opencv
1个回答
0
投票

我找到了一个非常hacky的方法来解决它。我对这个解决方案并不满意,因为它使用了很多魔法值。魔法值对于我的图像源来说是正确的,但可能不能解决整个问题。我只是不知道 openCV 解决这个问题的方法,因为我是 openCV 的新手。

这是我编写的一个函数,它找到 y 像素范围内的边缘,然后删除所有可能在膨胀过程中导致问题的白色,并用白色填充边缘。

如果有人可以建议 openCV 方式,请告诉我! 但现在我用它来完成棋盘,以便我可以移动棋子检测:

static inline void cure_board_edge_x(int startY, int endY, Mat& img) {
    typedef struct {
        size_t count;
        size_t first_x;
        size_t last_x;
    }RowData;
    size_t len = endY - startY;
    RowData *data = (RowData *)malloc(sizeof(RowData)*(len));
    ASSERT_EXIT(data, "Failed to allocated memory");
    size_t idx = 0;
    size_t max_idx = 0;
    for(int y = startY; y < endY; y++) {
        bool first255found = false;
        for(int x = 0; x < img.cols; x++) {
            if(img.at<uchar>(y, x) == 255) {
                (data[idx].count)++;
                if(!first255found) {
                    first255found = true;
                    data[idx].first_x = x;
                }
                data[idx].last_x = x;
            }
        }
        if(data[idx].count > data[max_idx].count) {
            max_idx = idx;
        } 
        idx++;
    }
    startY = startY + (int)max_idx; //-> location of edge
    int mY = startY - 1;
    int mmY = startY - 2;
    int mmmY = startY - 3;
    int rangeStart = startY - 50;
    int rangeEnd = startY - 30;
    int y;
    for(int x = data[max_idx].first_x + 5; x < data[max_idx].last_x - 5; x++) {
        img.at<uchar>(startY, x) = 255;
        for(int i = 0; i < 3; i++) {
            y = rangeStart + (i * (rangeEnd - rangeStart) / 3);
            if (img.at<uchar>(y, x) != 255) {
                img.at<uchar>(mY, x) = img.at<uchar>(mmY, x) = img.at<uchar>(mmmY, x) = 0;
                break;
            }
        }
    }
    free(data);
}

在主函数膨胀前添加:

int main() {
      // more code above ...............
     cure_board_edge_x(490, 495, edge_img);
     dilate(edge_img, edge_img, kernel, cv::Point(-1,-1), 1);
    // more code below ................
}
© www.soinside.com 2019 - 2024. All rights reserved.