边界提取
要在二值图像中提取物体的边界,容易想到的一个方法是将所有物体内部的点删除(置为背景色)。具体地说,可以逐行扫描图像,如果发现一个黑点的8个邻域都是黑点,则该点为内部点,在目标图像中将它删除。实际上这相当于采用一个3*3的结构元素对原图进行腐蚀,使得只有那些8个邻域都有黑点的内部点被保留,再用原图像减去腐蚀后的图像,恰好删除了这些内部点,留下了边界像素。这过程如下图所示。
示例演示
利用OpenCV实现上面边界提取的功能。
#include<opencv2/opencv.hpp>
using namespace cv;
int main(int argc, char *argv[])
{
Mat originimage = imread("E:/TestData/head_portrait.bmp");
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
imshow("OriginImage", originimage);
Mat erodeimage;
cv::erode(originimage, erodeimage, element);
imshow("ErodeImage", erodeimage);
Mat output = originimage - erodeimage;
imshow("Output", output);
waitKey(0);
return 0;
}
边界跟踪
为了依次记录边界上的每个像素,边界跟踪首先按照某种扫描规则找到目标物体边界上的一个像素,然后就以该像素为起始点,根据某种顺序(如顺时针或逆时针)依次找出物体边界上的其余像素,直到又回到起始点,完成整条边界的跟踪。
例如,我们可以按照从左到右、从上到下的顺序扫描图像,这样首先会找到目标物体最左上方的边界点P0.显然,这个点的左侧及上侧都不可能存在边界点(否则左侧或上侧的边界点就会称为第一个被扫描到的边界点),因此不妨从左下方逆时针开始探查,如左下方的点是黑点(轮廓点),直接跟踪至此边界点,否则探查方向逆时针旋转45度,直至找到第一个黑点为止,跟踪至此边界点。找到边界点后,在当前探查方向的基础上顺时针回转90度,继续用上述方法搜索下一个边界点,直到探查又回到初始的边界点P0,则完成整条边界的跟踪。
示例演示
在一幅图像中,实现跟踪多个边界的功能。对于带孔洞的物体也可以跟踪至其孔洞的轮廓。
#include<opencv2/opencv.hpp>
using namespace cv;
//only process binary image
//black is boundary
std::vector<std::vector<cv::Point>> TraceBoundary(Mat &image)
{
std::vector<std::vector<Point>> boundaryset;
Point start, current, next; // start point and current point
//search dirction array
int direction[8][2] ={{-1, 1}, //left-down
{0, 1}, // down
{1, 1}, //right-down
{1, 0}, //right
{1, -1}, //right-up
{0, -1}, //up
{-1, -1}, //left-up
{-1, 0} // left
};
int begindirection = 0, currentdirection = 0;
bool atstart = false, findboundary = false;
for(int i = 0; i < image.rows; i++)
{
for(int j = 0; j < image.cols; j++)
{
if(image.at<uchar>(i, j) == 0) //find start point
{
start.x = j;
start.y = i;
current = start;
atstart = true;
findboundary = true;
std::vector<Point> points;
points.push_back(current);
std::cout << "Start: " << j << " " << i << std::endl;
while((current.x != start.x) || (current.y != start.y) || atstart)
{
atstart = false;
//search next point
next.x = current.x + direction[currentdirection][0];
next.y = current.y + direction[currentdirection][1];
int searchtimes = 1;
while(next.x < 0 || next.x >= image.cols || next.y < 0 || next.y >= image.rows || image.at<uchar>(next) == 255)
{
currentdirection++; //rotate 45 degrees counterclockwise
currentdirection %= 8;
next.x = current.x + direction[currentdirection][0];
next.y = current.y + direction[currentdirection][1];
//there are no boundary points in 8 domains, which means they are isolated points
if(++searchtimes >= 8)
break;
}
if(image.at<uchar>(next) == 0) // find next point
{
std::cout << "Next: " << next.x << " " << next.y << std::endl;
points.push_back(next);
current = next;
currentdirection -= 2;
if(currentdirection < 0)
currentdirection += 8;
}
else // not find next point
{
findboundary = false;
break;
}
}
if(findboundary)
{
boundaryset.push_back(points);
for(auto &p : points)
{
image.at<uchar>(p) = 255;
}
}
} // find boundary one time
} // for j
} // for i
return boundaryset;
}
int main(int argc, char *argv[])
{
Mat originimage = imread("E:/TestData/head_boundary.bmp");
imshow("OriginImage", originimage);
Mat image;
cvtColor(originimage, image, CV_BGR2GRAY);
std::vector<std::vector<Point>> boundaryset = TraceBoundary(image);
//show result
Mat result;
originimage.copyTo(result);
for(auto &points : boundaryset)
{
for(auto &p : points)
{
result.at<Vec3b>(p)[0]= 0;
result.at<Vec3b>(p)[0]= 0;
result.at<Vec3b>(p)[1]= 255;
}
}
imshow("Output", result);
waitKey(0);
return 0;
}