Since you are looking for a very specific object, you can use the following algorithm:
- Invert the image, so that the foreground becomes white, and the background black.
- Find contours of connected components
For each contours
a. Compute the minimum area rectangle box
b. Compute the area of box
: barea
c. Compute the area of the contour: carea
d. Apply some constraint to be sure your contour is the square you're looking for
The constraints of step 3d are:
The ratio barea / carea
should be high (let's say higher then 0.9), meaning that the contour belongs to an almost rectangular blob.
The aspect ratio of box
should be almost 1, meaning that the box
is basically a square
The size of the square should be almost 30
, to reject other smaller or bigger squares in the image.
The result I get running this is:
Here is the code. Sorry, it's C++, but since it's all OpenCV function calls you should be able to port it easily to C#. At least, you can use it as a reference:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// Load image
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Create the output image
Mat3b out;
cvtColor(img, out, COLOR_GRAY2BGR);
// Create debug image
Mat3b dbg = out.clone();
// Binarize (to remove jpeg arifacts)
img = img > 200;
// Invert image
img = ~img;
// Find connected components
vector<vector<Point>> contours;
findContours(img.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<RotatedRect> squares;
// For each contour
for (int i = 0; i < contours.size(); ++i)
{
// Find rotated bounding box
RotatedRect box = minAreaRect(contours[i]);
// Compute the area of the contour
double carea = contourArea(contours[i]);
// Compute the area of the box
double barea = box.size.area();
// Constraint #1
if ((carea / barea) > 0.9)
{
drawContours(dbg, contours, i, Scalar(0, 0, 255), 7);
// Constraint #2
if (min(box.size.height, box.size.width) / max(box.size.height, box.size.width) > 0.95)
{
drawContours(dbg, contours, i, Scalar(255, 0, 0), 5);
// Constraint #3
if (box.size.width > 25 && box.size.width < 35)
{
drawContours(dbg, contours, i, Scalar(0, 255, 0), 3);
// Found the square!
squares.push_back(box);
}
}
}
// Draw output
for (int i = 0; i < squares.size(); ++i)
{
Point2f pts[4];
squares[i].points(pts);
for (int j = 0; j < 4; ++j)
{
line(out, pts[j], pts[(j + 1) % 4], Scalar(0,255,0), 5);
}
}
}
// Resize for better visualization
resize(out, out, Size(), 0.25, 0.25);
resize(dbg, dbg, Size(), 0.25, 0.25);
// Show images
imshow("Steps", dbg);
imshow("Result", out);
waitKey();
return 0;
}