采用区域生长法作为图像分割方法,它的基本原理是将相同特征的像素点归为一类。并且这些特征在针对具体应用的实现中可以是灰度值、像素梯度等(同时作为比较的对象,即可以选择最初的种子,也可以动态选择邻域的中心)。
作为区域增长的起点(种子)的选择同样重要,根据图像的复杂情况,可以选择多个点作为初始值,当有多个种子,在区域增长时,需要考虑相同特征像素点的合并问题。因此最终分割对象的数量要小于等于种子数量。
优缺点分析:
- 优点:思想相对简单,可以分割出封闭的区域,在复杂环境下有较好的分割效果;
- 缺点:对噪声敏感,会造成空洞(一般需要进行平滑操作),时间和空间复杂度较大
区域分割算法实现的一般步骤:
- 选择合适方法和数量的种子;
- 确定邻域内(8邻域或4邻域)不同像素点特征的计算和比较方法;
- 确定生长停止的条件
本文主要为理解区域分割法的核心思想,采用人工选择的种子。以灰度值作为像素点的特征;特征比较方式是与最开始的种子进行比较(+/-5);将邻域内且边界内相同特征的像素点push到STL的stack中(也可以用queue吧,没有比较过);生长的停止条件为遍历完容器内所有符合要求的点。
使用OpenCV实现如下:
RegionGrowup.h
#include<opencv2/opencv.hpp>
#include<vector>
class RegionGrowup
{
public:
RegionGrowup(cv::Mat src); //构造函数
~RegionGrowup() {} //析构函数
void SetInitPoints(std::vector<cv::Point2i> points);
cv::Mat computI();
private:
int _height, _width;
int _channels;
cv::Mat _src;
std::vector<cv::Point2i> PointShift2D;
std::vector<cv::Point2i> InitPoints2D; //使用单个种子点进行测试
cv::RNG rng;
};
RegionGrowup.cpp
#include "Func.h"
#include <stack>
#include <ctime>
RegionGrowup::RegionGrowup(cv::Mat src) : _src(src)
{
this->_height = src.rows;
this->_width = src.cols;
this->_channels = src.channels();
PointShift2D.push_back(cv::Point2i(1, 0));
PointShift2D.push_back(cv::Point2i(-1, 0));
PointShift2D.push_back(cv::Point2i(0, -1));
PointShift2D.push_back(cv::Point2i(0, 1));
PointShift2D.push_back(cv::Point2i(-1, -1));
PointShift2D.push_back(cv::Point2i(-1, 1));
PointShift2D.push_back(cv::Point2i(1, -1));
PointShift2D.push_back(cv::Point2i(1, 1));
}
void RegionGrowup::SetInitPoints(std::vector<cv::Point2i> points)
{
this->InitPoints2D = points;
}
cv::Mat RegionGrowup::computI()
{
cv::Mat dst = cv::Mat(_height, _width, CV_8UC3, cv::Scalar(0, 0, 0));
//此处再对_src进行判断
if (!_src.data)
return dst;
//将原图转换为灰度图
if (_channels != 1)
{
cv::cvtColor(_src, _src, cv::COLOR_RGB2GRAY);
}
//首先处理背景
cv::Point2i backPoint(5, 5);
uchar backValue = _src.at<uchar>(backPoint);
//获得单个种子点的灰度值
int j = 0;
for (auto index : InitPoints2D)
{
uchar SeedValue = _src.at<uchar>(index);
if (SeedValue >= backValue - 20 && SeedValue <= backValue + 20)
continue;
//构建栈用于保存和种子点处于同一区域的点
std::stack<cv::Point2i> pointStack;
pointStack.push(index);
int temp = j * 30;
int r = rng.uniform(temp, 255 + temp) - temp;
int g = rng.uniform(temp, 255 + temp) - temp;
int b = rng.uniform(temp, 255 + temp) - temp;
//判断种子点的8邻域(4邻域)范围的点
while (!pointStack.empty())
{
cv::Point2i top = pointStack.top();
pointStack.pop();
for (int i = 0; i < 8; ++i)
{
//获得一个方向上的点(相对于当前点)
cv::Point2i p = top + PointShift2D[i];
//需要对边界外边的灰度值进行判断
if ((p.x < 0 || p.x > _width) || (p.y < 0 || p.y > _height))
continue;
//获得该点的灰度值
uchar pvalue = _src.at<uchar>(p);
uchar topvalue = _src.at<uchar>(top);
//std::cout << (int)pvalue << std::endl;
//当该点的灰度值在SeedValue+-5范围内时,可以将该点
//和InitPoint归类为同一类
if ((pvalue > topvalue - 2 && pvalue < topvalue + 2) && (dst.at<cv::Vec3b>(p) == cv::Vec3b(0, 0, 0)))
{
//同时将这个点push到stack中
cv::Vec3b color(r, g, b); //根据j的值计算颜色
dst.at<cv::Vec3b>(p) = color;
pointStack.push(p);
}
}
}
++j;
}
return dst;
}
测试函数main.cpp
#include "Func.h"
#include<random>
void PrintImage(cv::Mat src,std::vector<cv::Point2i>pots);
std::vector<cv::Point2i>GetRandomPoint(int nrows,int ncols,int n);
int main(int argc, char **argv)
{
cv::Mat src = cv::imread("../0004ResizeBlur.jpg", 0);
if (!src.data)
{
std::cout << "fail to load image" << std::endl;
return -1;
}
cv::imshow("1", src);
RegionGrowup rg(src);
//获取随机点
std::vector<cv::Point2i>pots=GetRandomPoint(src.rows,src.cols,50);
//PrintImage(src,pots);
rg.SetInitPoints(pots); //这边以一个点作为测试
cv::Mat dst = rg.computI();
if (!dst.data)
{
std::cout << "compute error" << std::endl;
return -1;
}
cv::imshow("2", dst);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
void PrintImage(cv::Mat src,std::vector<cv::Point2i>pots)
{
cv::cvtColor(src,src,cv::COLOR_GRAY2RGB);
for(auto index:pots)
{
cv::circle(src,index,2,cv::Scalar(0,0,255),-1);
}
cv::imshow("srcPoint",src);
}
std::vector<cv::Point2i>GetRandomPoint(int nrows,int ncols,int n)
{
std::vector<cv::Point2i>res;
std::default_random_engine generator;
std::normal_distribution<double>distuibutionx(ncols/2,65);
std::normal_distribution<double>distuibutiony(nrows/2,65);
for(int i=0;i<n;++i)
{
cv::Point2i temp;
temp.y = static_cast<int>(distuibutiony(generator));
temp.x = static_cast<int>(distuibutionx(generator));
if(temp.x>=0&&temp.x<=ncols&&temp.y>=0&&temp.y<=nrows) res.push_back(temp);
}
return res;
}
结果显示: