一、算法原理
区域生长算法的基本思想是将有相似性质的像素点合并到一起。对每一个区域要先指定一个种子点作为生长的起点,然后将种子点周围领域的像素点和种子点进行对比,将具有相似性质的点合并起来继续向外生长,直到没有满足条件的像素被包括进来为止。这样一个区域的生长就完成了。
因此区域生长算法一般分为三个步骤实现:
(1)确定生长种子点;
(2)规定生长准则;
(3)确定生长停止条件。
关键点在于:
①给定种子点(种子点如何选取)
种子点的选取很多时候都采用人工交互的方法实现,
也有用其他方式的,比如寻找物体并提取物体内部点作为种子点。
②确定在生长过程中能将相邻像素包括进来的准则(相似性质准则)
灰度图像的差值;
彩色图像的颜色等等。
都是关于像素与像素间的关系描述。
③生长的停止条件:
二、算法实现
opencv代码实现:
该代码采用八连通区域。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include
#include "math.h"
using namespace cv;
using namespace std;
Point recent_Point,recent_Point1;
Mat RegionGrow(Mat src, Point2i pt, int th)
{
Point2i ptGrowing; //待生长点位置
int nGrowLable = 0; //标记是否生长过
int nSrcValue = 0; //生长起点灰度值
int nCurValue = 0; //当前生长点灰度值
Mat matDst = Mat::zeros(src.size(), CV_8UC1); //创建一个空白区域,填充为黑色
//生长方向顺序数据
int DIR[8][2] = { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };
vector vcGrowPt; //生长点栈
vcGrowPt.push_back(pt); //将生长点压入栈中
matDst.at(pt.y, pt.x) = 255; //标记生长点
nSrcValue = src.at(pt.y, pt.x); //记录生长点的灰度值
while (!vcGrowPt.empty()) //生长栈不为空则生长
{
pt = vcGrowPt.back(); //取出一个生长点
vcGrowPt.pop_back();
//分别对八个方向上的点进行生长
for (int i = 0; i<9; ++i)
{
ptGrowing.x = pt.x + DIR[i][0];
ptGrowing.y = pt.y + DIR[i][1];
//检查是否是边缘点
if (ptGrowing.x < 0 || ptGrowing.y < 0 || ptGrowing.x >(src.cols - 1) || (ptGrowing.y > src.rows - 1))
continue;
nGrowLable = matDst.at(ptGrowing.y, ptGrowing.x); //当前待生长点的灰度值
if (nGrowLable == 0) //如果标记点还没有被生长
{
nCurValue = src.at(ptGrowing.y, ptGrowing.x);
if (abs(nSrcValue - nCurValue) < th) //在阈值范围内则生长
{
matDst.at(ptGrowing.y, ptGrowing.x) = 255; //标记为白色
vcGrowPt.push_back(ptGrowing); //将下一个生长点压入栈中
}
}
}
}
return matDst.clone();
}
void On_mouse(int event, int x, int y, int flags, void*)//每次点击左键,在相应位置画红点
{
if (event == EVENT_LBUTTONDOWN)
{
recent_Point = Point(x, y);
cout << "img_x" << " " << recent_Point.x << " " << "img_y" << " " << recent_Point.y << endl;
//circle(srcimg, recent_Point, 2, Scalar(0, 0, 255), -1);
//imshow("srcImg", srcimg);
}
}
int main() //区域生长
{
Mat binaryimg,greyimg;
Mat regiongrow,regiongrow1,regiongrow2;
Mat dst;
int th = 10;
Mat src = imread("/Users/superlinc/工作/OpenCVlearn/regiongrow/753401277.jpg");
cvtColor(src, greyimg, COLOR_BGR2GRAY); //转化为灰度图
Mat temp_regiongrow = Mat::zeros(src.size(), CV_8UC1); //创建一个空白区域,填充为黑色
//转化为二值图
threshold(greyimg, binaryimg, 200, 255, THRESH_BINARY);
namedWindow("srcImg", 0);
imshow("srcImg", src);
namedWindow("binaryImg", 0);
imshow("binaryImg", binaryimg);
cout << "select one point in binaryImg" << endl;
setMouseCallback("binaryImg", On_mouse);
for(int i = 0;i < 2;i++){
char c = (char)waitKey(0);
cout << "select one point in binaryImg" << endl;
setMouseCallback("binaryImg", On_mouse);
if(c == 'b'){
regiongrow1 = RegionGrow(binaryimg, recent_Point, th);
bitwise_or(regiongrow1,temp_regiongrow,regiongrow); //和前一个分割的图做或运算
temp_regiongrow = regiongrow1.clone(); //保存前一个分割图
}
bitwise_and(greyimg,regiongrow,dst); //与原图做与运算
namedWindow("dstimg", 0);
imshow("dstimg", dst);
}
waitKey(0);
return 0;
}
结果:
以下为原图,先转化为灰度图,再转换为二值图。
鼠标先在二值图左肺选取一个种子点,按‘b‘进行分割;然后在二值图右肺选取一个种子点,按‘b‘进行分割,之后做或运算,将两个分割结果合并;最后与灰度图做与运算得到最终分割结果。
区域生长算法有几个缺点:
1.需要人工选取种子点,不能实现全自动化;
2.二值化时需要设置阈值,阈值参数影响较大;
3.后期还得做平滑运算;
4.需要设置领域像素与种子点灰度值差的阈值。
Matlab代码实现:
该代码采用四连通区域,比较简单可用于学习。
%基于区域生长算法的图像分割
clear;
[fileName,pathName] = uigetfile('*.*','Please select an image');
if(fileName)
fileName = strcat(pathName,fileName); %拼接
fileName = lower(fileName); %小写
else
J = 0; %记录区域生长所分割得到的区域
msgbox('Please select an image');
return;
end
I = imread(fileName); %原始图像
figure;imshow(I);
if(~(size(I,3) - 3))
I = rgb2gray(I);
end
I = im2double(I); %图像灰度值归一化到[0,1]之间
%种子点的交互式选择
[y,x] = getpts; %鼠标取点,回车确定
x = round(x);
y = round(y);
J = zeros(size(I)); %主函数的返回值,记录区域生长所得到的区域
Isizes = size(I);
reg_mean = I(x,y); %表示分割好的区域内的平均值,初始化为种子点的灰度值
reg_size = 1; %分割得到的区域,初始化只有种子点一个
neg_free = 10000; %动态分配内存的时候每次申请的连续空间大小
neg_list = zeros(neg_free,3); %第一列行坐标,第二列列坐标,第三列存储灰度值
%定义领域列表,并且预先分配用于储存待分析的像素点的坐标值和灰度值的空间,加速
neg_pos = 0; %用于记录neg_list中的待分析的像素点点个数
pixdist = 0;
%记录最新像素点增加到分割区域后的距离测度
%下一次待分析的四个领域像素点和当前种子点的距离
%如果当前坐标为(x,y)那么通过neigb我们可以得到其四个邻域像素的位置
neigb = [-1 0;
1 0 ;
0 -1;
0 1];
%开始进行区域生长,当所有待分析的邻域像素点和已经分割好的区域像素点的灰度值距离
%大于reg_maxdis,区域生长结束
while(pixdist < 0.06 && reg_size < numel(I))
%增加新的领域像素到neg_list中
for j = 1:4
xn = x + neigb(j,1); %领域像素坐标
yn = y + neigb(j,2);
%检查领域像素是否超过了图像的边界
ins = (xn >= 1)&&(yn >= 1)&&(xn <= Isizes(1))&&(yn <= Isizes(2));
%如果领域像素在图像内部,并且尚未分割好;那么将它添加到领域列表中
if(ins && J(xn,yn) == 0) %J(xn,yn)==0表示图像中该点还没有被分割
neg_pos = neg_pos + 1;
neg_list(neg_pos,:) = [xn,yn,I(xn,yn)]; %存储对应点灰度值
J(xn,yn) = 1; %标注该邻域像素点已经被访问过并不意味着他在分割区域内
end
end
%如果分配的内存空问不够,申请新的内存空间
if(neg_pos + 10 > neg_free)
neg_free = neg_free + 100000;
neg_list((neg_pos + 1):neg_free,:) = 0;
end
%从所有待分析的像素点中选择一个像素点,该点的灰度值和已经分割好区域灰度均值的
%差的绝对值是待分析像素中最小的
dist = abs(neg_list(1:neg_pos,3)-reg_mean);
[pixdist,index] = min(dist);
%计算区域新的均值
reg_mean = (reg_mean * reg_size + neg_list(index,3))/(reg_size + 1);
reg_size = reg_size + 1;
%将旧的种子点标记为已经分割好的区域像素点
J(x,y) = 2; %标志该像素点已经是分割好的像素点
x = neg_list(index,1); %新的种子点
y = neg_list(index,2);
%将新的种子点从待分析的邻域像素列表中移除
neg_list(index,:) = neg_list(neg_pos,:);
neg_pos = neg_pos - 1;
end
J = (J==2); %我们之前将分割好的像素点标记为2
hold off;
figure;
subplot(2,2,2),imshow(J);
J = bwmorph(J,'dilate'); %补充空间,膨胀
subplot(2,2,3),imshow(J);
subplot(2,2,4),imshow(I+J);