I'd like to adaptively threshold this image to find the outer border using findContours()
from OpenCV. I use an adaptive threshold for the usual reason: global thresholding, even with Otsu's method, does not adequately compensate for differences in brightness between different parts of the image.
Unfortunately, adaptive thresholding creates breakages at some of the intersections with thick gridlines. This is because, for the pixels at the intersection, the thick gridlines take up so much of the surrounding region that the local threshold is raised above the (only moderately dark) value of the pixels at the intersection. Surprisingly, this effect still holds to some degree even for large threshold windows.
Of course, this makes the adaptive threshold useless for finding contours in these types of images. However, it's still much better overall than other algorithms like Canny at coming up with connected edges.
I've been able to reconnect the edges after adaptive thresholding by manually filling in all the one- and two-pixel gaps in the image (I actually threshold on a scaled-down image to save runtime; the gaps are larger in the above full-size image). Here is the OpenCV code I used (written for the Android bindings). 0 is black and -1 is white.
private void fillGaps(Mat image) {
int size = image.rows() * image.cols();
byte[] src = new byte[size], dst = new byte[size];
image.get(0, 0, src);
int c = image.cols();
int start = 2 * c + 2;
int end = size - start;
for (int i = start; i < end; i++) {
if (src[i+1] == -1 && src[i-1] == -1 || src[i+c] == -1 && src[i-c] ==-1){
// 1-pixel gap
dst[i] = -1;
} else if (src[i+1] == 0 && src[i+2 ] == -1 && src[i-1] == -1) {
// 2-pixel horizontal gap
dst[i] = -1; dst[i+1] = -1;
} else if (src[i+c] == 0 && src[i+2*c] == -1 && src[i-c] == -1) {
// 2-pixel vertical gap
dst[i] = -1; dst[i+c] = -1;
}
}
image.put(0, 0, dst);
}
Here is the scaled-down image before and after filling in the gaps:
Although this works fairly well here, it is a crude technique, doesn't fill in all the gaps, and sometimes joins the grid with other nearby contours.
What is a reliable way to avoid disconnected contours after an adaptive threshold?
The adaptive threshold approach you are taking is alright but as a next step you should do some morphological operations : erosion, dilation, opening, closing.
For your particular case closing operation will be suitable.
There are also Open-CV inbuilt methods cvDilate and cvErode. The shape of the structuring element will not matter much but keep it small in size.
I saw your implementation of filling in the gaps. In there you are not considering diagonal elements. Its better to take a 3x3 or 5x5 window around each pixel and compare each element and then decide upon the outcome.
counter=0;
for (int k=i-radius; k<=i+radius; k++)
{
for (int l=j-radius; l<=j+radius; l++)
{
if (src[k][l] == -1)
counter++;
}
}
if (counter > 0)
dest[k][l] = -1;
else
dest[k][l] = 0;
This is the sample code I use for dilation (or filling the gaps). The radius can be 1 (3x3) or 2(5x5).