1 | initial version |
Really you could use HitAndMiss morphology to find a particular pattern like 3 point junctions. Talking about the "geometric solution", is not so easy to get from contours needed points to write the equation of the line. I'll come back with some code for this
2 | No.2 Revision |
Really To apply the "geometric solution", you need to know the points to write the equation of the line, this is not so easy.
You could use HitAndMiss the hit-and-miss morphology to find a particular pattern...in this case the 3-point junctions.
Below is my implementation to achieve the following result (Src, Junctions and Final):
See http://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm for more details about hit-and-miss
In short, you have to define the right kernel for each pattern like 3 point junctions. Talking about the "geometric solution", you want to detect. In OpenCV hit-and-miss (is available thanks to @LorenaGdL) you have to use:
1
=> white is -1
=> black is required here0
=> doesn't matterThan, catch all directions/orientation rotating the kernel
Here is the code:
// This is your sample image (objects are black)
cv::Mat src = (cv::Mat_<UINT8>(7, 8) <<
0, 1, 1, 1, 1, 1, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 0, 1, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1,
1, 0, 0, 1, 1, 1, 0, 1,
0, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1);
void GetJunctions(const Mat &src, Mat &dst)
{
// the hit-and-miss kernels to locate 3-points junctions
// to be used in each directions
// NOTE: float type is needed points to write the equation of the line. I'll come back due to limitation/bug in warpAffine with some code signed char
cv::Mat k1 = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
0, 1, 0,
1, 0, 1);
cv::Mat k2 = (cv::Mat_<float>(3, 3) <<
1, 0, 0,
0, 1, 0,
1, 0, 1);
cv::Mat k3 = (cv::Mat_<float>(3, 3) <<
0, -1, 1,
1, 1, -1,
0, 1, 0);
// Some useful declarations
Mat tmp, rotMat;
Size ksize = k1.size();
Point center = Point(ksize.width / 2, ksize.height / 2);
// 90 degrees rotation matrix
rotMat = getRotationMatrix2D(center, 90, 1);
// dst accumulates all matches
dst = Mat::zeros(src.size(), CV_16UC1);
// Do hit & miss for this all possible directions (0,90,180,270)
for (int i = 0; i < 3; i++)
{
morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k1), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k2), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k3), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
// Rotate the kernels (90deg)
warpAffine(k1, k1, rotMat, ksize);
warpAffine(k2, k2, rotMat, ksize);
warpAffine(k3, k3, rotMat, ksize);
}
// normalize the accumulator to 0..255
cv::normalize(dst, tmp, 0, 255, cv::NORM_MINMAX);
tmp.convertTo(dst, src.type());
}
int main()
{
src *= 255;
// Morphology logic is: white objects on black foreground
src = ~src;
// Get junctions
Mat junctionsScore, dst;
vector<Point> junctionsPoint;Point pt;
GetJunctions(src, junctionsScore);
// Draw markers where junction score is non zero
cvtColor(src, dst, CV_GRAY2BGR);
findNonZero(junctionsScore, junctionsPoint);
for (size_t i = 0; i < junctionsPoint.size(); i++)
{
pt = junctionsPoint[i];
char score = junctionsScore.at<uchar>(pt);
dst.at<Vec3b>(pt) = Vec3b(0, 0, score);
}
// show the result
string winDst="Dst",winSrc = "Src", winJunc = "Junctions";
namedWindow(winSrc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
namedWindow(winJunc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
namedWindow(winDst, WINDOW_NORMAL | WINDOW_KEEPRATIO);
int scale = 24;
resizeWindow(winSrc, scale*src.cols, scale*src.rows);
resizeWindow(winJunc, scale*src.cols, scale*src.rows);
resizeWindow(winDst, scale*src.cols, scale*src.rows);
imshow(winSrc, src);
imshow(winJunc, junctionsScore);
imshow(winDst, dst);
waitKey(0);
return 0;
}
3 | No.3 Revision |
To apply the "geometric solution", you need to know the points to write the equation of the line, this is not so easy.
You could use the hit-and-miss morphology to find a particular pattern...in this case the 3-point junctions.
Below is my implementation to achieve the following result (Src, Junctions and Final):
See http://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm for more details about hit-and-miss
In short, you have to define the right kernel for each pattern you want to detect. In OpenCV hit-and-miss (is available thanks to @LorenaGdL) @LorenaGdL ) you have to use:
1
=> white is required here-1
=> black is required here0
=> doesn't matterThan, catch all directions/orientation rotating the kernel
Here is the code:
// This is your sample image (objects are black)
cv::Mat src = (cv::Mat_<UINT8>(7, 8) <<
0, 1, 1, 1, 1, 1, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 0, 1, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1,
1, 0, 0, 1, 1, 1, 0, 1,
0, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1);
void GetJunctions(const Mat &src, Mat &dst)
{
// the hit-and-miss kernels to locate 3-points junctions
// to be used in each directions
// NOTE: float type is needed due to limitation/bug in warpAffine with signed char
cv::Mat k1 = (cv::Mat_<float>(3, 3) <<
0, 1, 0,
0, 1, 0,
1, 0, 1);
cv::Mat k2 = (cv::Mat_<float>(3, 3) <<
1, 0, 0,
0, 1, 0,
1, 0, 1);
cv::Mat k3 = (cv::Mat_<float>(3, 3) <<
0, -1, 1,
1, 1, -1,
0, 1, 0);
// Some useful declarations
Mat tmp, rotMat;
Size ksize = k1.size();
Point center = Point(ksize.width / 2, ksize.height / 2);
// 90 degrees rotation matrix
rotMat = getRotationMatrix2D(center, 90, 1);
// dst accumulates all matches
dst = Mat::zeros(src.size(), CV_16UC1);
// Do hit & miss for all possible directions (0,90,180,270)
int op = MORPH_HITMISS;
for (int i = 0; i < 3; i++)
{
morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k1), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k2), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k3), Point(-1, -1), 1, BORDER_CONSTANT, 0);
add(dst, tmp, dst, noArray(), dst.type());
// Rotate the kernels (90deg)
warpAffine(k1, k1, rotMat, ksize);
warpAffine(k2, k2, rotMat, ksize);
warpAffine(k3, k3, rotMat, ksize);
}
// normalize the accumulator to 0..255
cv::normalize(dst, tmp, 0, 255, cv::NORM_MINMAX);
tmp.convertTo(dst, src.type());
}
int main()
{
src *= 255;
// Morphology logic is: white objects on black foreground
src = ~src;
// Get junctions
Mat junctionsScore, dst;
vector<Point> junctionsPoint;Point pt;
GetJunctions(src, junctionsScore);
// Draw markers where junction score is non zero
cvtColor(src, dst, CV_GRAY2BGR);
findNonZero(junctionsScore, junctionsPoint);
for (size_t i = 0; i < junctionsPoint.size(); i++)
{
pt = junctionsPoint[i];
char score = junctionsScore.at<uchar>(pt);
dst.at<Vec3b>(pt) = Vec3b(0, 0, score);
}
// show the result
string winDst="Dst",winSrc = "Src", winJunc = "Junctions";
namedWindow(winSrc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
namedWindow(winJunc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
namedWindow(winDst, WINDOW_NORMAL | WINDOW_KEEPRATIO);
int scale = 24;
resizeWindow(winSrc, scale*src.cols, scale*src.rows);
resizeWindow(winJunc, scale*src.cols, scale*src.rows);
resizeWindow(winDst, scale*src.cols, scale*src.rows);
imshow(winSrc, src);
imshow(winJunc, junctionsScore);
imshow(winDst, dst);
waitKey(0);
return 0;
}