In this image, the square g1 cannot be detected due to overlapping of g and the board edge. Here is a canny edge image to show what I mean:
Here is the Minimum Reproducible Code after taking a screenshot and detecting the board and resizing the 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;
}
static inline void expand_rect(cv::Rect2d *rect, double percentage) { // expands by percentage
percentage = percentage / 100;
(*rect).x -= (*rect).width * percentage;
(*rect).y -= (*rect).height * percentage;
percentage = percentage * 2;
(*rect).width += (*rect).width * percentage;
(*rect).height += (*rect).height * percentage;
}
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;
}
Here is the output image of the contour drawing:
std output:
63
I tried many things, but nothing worked so far. I am failing to detect a single square contour in a chess board which is g1. What can I do to solve this issue?