根据灰度、颜色、纹理和形状等特征,把图像分成若干个特定的、具有独特性质的区域,这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性,并提出感兴趣目标的技术和过程。 它是由图像处理到图像分析的关键步骤。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像索赋予相同的编号。
目的是将图像中像素根据一定的规则分为若干(N)个聚(cluster)集合,每个集合包含一类像素。将对象在背景提取出来。
图像分割方法:基于阈值的分割方法、
基于边缘的分割方法、
基于区域的分割方法、
基于特定理论的分割方法
图像分割方法概述如下:
阈值法的基本思想是基于图像的灰度特征来计算一个或多个灰度阈值,并将图像中每个像素的灰度值与阈值相比较,最后将像素根据比较结果分到合适的类别中。因此,该类方法最为关键的一步就是按照某个准则函数来求解最佳灰度阈值。
所谓边缘是指图像中两个不同区域的边界线上连续的像素点的集合,是图像局部特征不连续性的反映,体现了灰度、颜色、纹理等图像特性的突变。通常情况下,基于边缘的分割方法指的是基于灰度值的边缘检测,它是建立在边缘灰度值会呈现出阶跃型或屋顶型变化这一观测基础上的方法。
阶跃型边缘两边像素点的灰度值存在着明显的差异,而屋顶型边缘则位于灰度值上升或下降的转折处。对于阶跃状边缘常用微分算子进行边缘检测,其位置对应一阶导数的极值点,对应二阶导数的过零点(零交叉点)。常用的一阶微分算子有Roberts算子、Prewitt算子和Sobel算子,二阶微分算子有Laplace算子和Kirsh算子等。在实际中各种微分算子常用小区域模板来表示,微分运算是利用模板和图像卷积来实现。这些算子对噪声敏感,只适合于噪声较小不太复杂的图像。
此类方法是将图像按照相似性准则分成不同的区域,主要包括种子区域生长法、区域分裂合并法和分水岭法等几种类型。
种子区域生长法是从一组代表不同生长区域的种子像素开始,接下来将种子像素邻域里符合条件的像素合并到种子像素所代表的生长区域中,并将新添加的像素作为新的种子像素继续合并过程,直到找不到符合条件的新像素为止。该方法的关键是选择合适的初始种子像素以及合理的生长准则。
区域分裂合并法(Gonzalez,2002)的基本思想是首先将图像任意分成若干互不相交的区域,然后再按照相关准则对这些区域进行分裂或者合并从而完成分割任务,该方法既适用于灰度图像分割也适用于纹理图像分割。
分水岭法(Meyer,1990)是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。该算法的实现可以模拟成洪水淹没的过程,图像的最低点首先被淹没,然后水逐渐淹没整个山谷。当水位到达一定高度的时候将会溢出,这时在水溢出的地方修建堤坝,重复这个过程直到整个图像上的点全部被淹没,这时所建立的一系列堤坝就成为分开各个盆地的分水岭。分水岭算法对微弱的边缘有着良好的响应,但图像中的噪声会使分水岭算法产生过分割的现象。
此类方法把图像分割问题与图的最小割(min cut)问题相关联。首先将图像映射为带权无向图G=
该类方法主要指的是活动轮廓模型(active contour model)以及在其基础上发展出来的算法,其基本思想是使用连续曲线来表达目标边缘,并定义一个能量泛函使得其自变量包括边缘曲线,因此分割过程就转变为求解能量泛函的最小值的过程,一般可通过求解函数对应的欧拉(Euler.Lagrange)方程来实现,能量达到最小时的曲线位置就是目标的轮廓所在。按照模型中曲线表达形式的不同,活动轮廓模型可以分为两大类:参数活动轮廓模型(parametric active contour model)和几何活动轮廓模型(geometric active contour model)。
代码示例:
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("tahiti.jpg");
imshow("原图", img);
// 将白色背景转换为黑色背景
for (int i = 0; i < img.rows; i++) {
for (int j = 0; j < img.cols; j++) {
if (img.at(i, j) == Vec3b(255, 255, 255)) {
img.at(i, j)[0] = 0;
img.at(i, j)[1] = 0;
img.at(i, j)[2] = 0;
}
}
}
// filter2d和拉普拉斯算子来得到图像的边界
Mat filter2d_img;
Mat kernel = (Mat_(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
filter2D(img, filter2d_img, CV_32F, kernel);
filter2d_img.convertTo(filter2d_img, CV_8UC3);
imshow("filter2d_img", filter2d_img);
// 将源图像减去filter_2d的图像可以得到边界更加清晰的图像
Mat result_img;
result_img = img - filter2d_img;
imshow("result_img", result_img);
// 首先将图像转化为灰度图像,然后对图像使用自适应阈值变为二值化图像
Mat gray;
cvtColor(result_img, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
// 使用自适应阈值,将图片变为黑白图像
Mat binary_img;
threshold(gray, binary_img, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary_img);
// 对图片进行膨胀,膨胀会消除黑色的噪音。膨胀后仍为黑色的区域,即为背景区域。
Mat sure_bg;
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(9, 9), Point(-1, -1));
dilate(binary_img, sure_bg, kernel1, Point(-1, -1), 3);
sure_bg.convertTo(sure_bg, CV_8UC1);
imwrite("sure_bg.jpg", sure_bg);
// 首先对二值图像进行距离变换,距离大于0.4的均认为是前景图像
Mat sure_fg;
Mat dist_img;
// 距离变换:计算源像素的每个像素到最近的零像素的距离
distanceTransform(binary_img, dist_img, DIST_L1, 3, 5);
// 将距离变换的图像进行归一化
normalize(dist_img, dist_img, 0, 1, NORM_MINMAX);
// 距离大于0.4的皆为前景
threshold(dist_img, sure_fg, 0.4, 1, THRESH_BINARY);
normalize(sure_fg, sure_fg, 0, 255, NORM_MINMAX);
sure_fg.convertTo(sure_fg, CV_8UC1);
imwrite("sure_fg.jpg", sure_fg);
// 背景区域减去前景区域,即我们不确定的区域,该区域中包含了图像分割的边界点
Mat unknown;
unknown = sure_bg - sure_fg;
imwrite("unknown.jpg", unknown);
// 对这些区域进行标记,其中不确定区域标记为0,背景区域标记为1
// 前景区域借助于connectedComponents函数进行标记,该函数会将前景区域从1开始进行标记
Mat marker1;
connectedComponents(sure_fg, marker1);
// 将标记的marker1每个值都加上1
// 相当于前景区域从2开始标记,除了前景区域的其他区域的值均为1
Mat markers = Mat::ones(marker1.size(), marker1.type());
markers = marker1 + markers;
markers.convertTo(markers, CV_8UC1);
// 将图像中的未知区域标记为0
for (int row = 0; row < unknown.rows; row++) {
for (int col = 0; col < unknown.cols; col++) {
uchar c = unknown.at(row, col);
if (c == 255)
markers.at(row, col) = 0;
}
}
normalize(markers, markers, 0, 255, NORM_MINMAX);
imwrite("markers.jpg", markers);
//实施分水岭算法,标签图像将会被修改便捷区域的标记为-1
markers.convertTo(markers, CV_32SC1);
watershed(img, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
imshow("water_img", mark);
// 根据mark,对图片进行着色
vector pixes;
vector colors;
RNG rng;
Mat final_rst(mark.size(), CV_8UC3);
for (int i = 0; i < markers.rows; i++) {
for (int j = 0; j < markers.cols; j++) {
uchar c = mark.at(i, j);
bool target = false;
for (int k = 0; k < pixes.size(); k++) {
if (c == pixes[k]) {
final_rst.at(i, j) = colors[k];
target = true;
break;
}
}
if (target == false) {
pixes.push_back(c);
int r = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int b = rng.uniform(0, 255);
Vec3b color = Vec3b((uchar)b, (uchar)g, (uchar)r);
final_rst.at(i, j) = color;
colors.push_back(color);
}
}
}
imshow("final", final_rst);
waitKey(0);
return 0;
}