用分水岭算法实现图像分割 - sumpig/OpenCV GitHub Wiki
如果把图像看作一个拓扑地貌,那么同类区域就相当于陡峭边缘内相对平坦的盆地。分水岭算法通过逐步增高水位,把地貌分割成多个部分。
因为算法很简单,它的原始版本会过度分割图像,产生很多小的区域。因此OpenCV 提出了该算法的改进版本,使用一系列预定义标记来引导图像分割的定义方式。
使用分水岭分割法需要调用 cv::watershed 函数。该函数的输入对象是一个标记图像,图像的像素值为32 位有符号整数,每个非零像素代表一个标签。
不同应用程序获得标记的方式各不相同。主要思想是标记处图像中的部分前景和背景。
// 消除噪声和细小物体
cv::Mat fg;
cv::erode(binary, fg, cv::Mat(), cv::Point(-1,-1), 4);
// 标识不含物体的图像像素
cv::Mat bg;
cv::dilate(binary, bg, cv::Mat(), cv::Point(-1,-1), 4);
cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);
// 创建标记图像
cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));
markers = fg + bg;
// 创建分水岭分割类的对象
WatershedSegmenter segmenter;
// 设置标记图像,然后执行分割过程
segmenter.setMarkers(markers);
segmenter.process(image);
class WatershedSegmenter {
private:
cv::Mat markers;
public:
void setMarkers(const cv::Mat& markerImage) {
// 转换成整数型图像
markerImage.convertTo(markers, CV_32S);
}
cv::Mat process(const cv::Mat &image) {
// 应用分水岭
cv::watershed(image, markers);
return markers;
}
标签图像:
// 以图像的形式返回结果
cv::Mat getSegmentation() {
cv::Mat tmp;
// 所有标签值大于255 的区段都赋值为255
markers.convertTo(tmp,CV_8U);
return tmp;
}
边缘图像:
// 以图像的形式返回分水岭
cv::Mat getWatersheds() {
cv::Mat tmp;
// 在变换前,把每个像素p 转换为255p+255
markers.convertTo(tmp,CV_8U,255,255);
return tmp;
}
扩展
用户可以交互式地在场景中的物体和背景上绘制区域,以标注物体。
当需要标识的物体位于图像中间时,可以简单地在输入图像的中心位置标记特定标签,在图像的边缘位置(假设背景在边缘位置)标记上另一个标签。在创建标记图像时,可以在标记图像上绘制加粗的矩形:
// 标识背景像素
cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0));
cv::rectangle(imageMask,cv::Point(5,5),
cv::Point(image.cols-5, image.rows-5),
cv::Scalar(255), 3);
// 标识前景像素
// (在图像的中心)
cv::rectangle(imageMask,
cv::Point(image.cols/2-10, image.rows/2-10),
cv::Point(image.cols/2+10, image.rows/2+10),
cv::Scalar(1), 10);