1.图像分割概述
(1)What(什么是图像分割)
将图像划分为不同的子区域,使得同一子区域具有较高的相似性,不同的子区域具有明显的差异性
(2)Why(对图像进行分割有什么作用)
- 医学领域:将不同组织分割成不同区域帮助分析病情
- 军事领域:通过对图像的分割,为自动目标识别提供参数,为飞行器或武器的精准导航提供依据
- 遥感领域:通过遥感图像分析城市地貌、作物生长情况。此外,云系分析和天气预报都离不开图像分割
- 交通领域:车辆跟踪和车牌识别
- 工业领域:零部件分类、质量评估等
(3)Which(有哪些图像分割的方法)
- 基于阈值的分割方法:利用灰度直方图得到分割的阈值,利用这些阈值将图像分为几个部分,核心思想是认为同一部分的像素是同一个物体。
- 基于边缘的分割方法:检测图像的边界以实现对图像的分割
- 基于区域的分割方法:核心思想是将有相似特性的像素集合起来构成区域,将差异性较大的区域进行分裂
- 基于神经网络的分割方法:这里不多赘言,现在很火…
- 基于聚类的分割方法:依据像素相似度,使用聚类算法将像素划分为不同类别
2.基于阈值的分割
(1)固定阈值分割
将图像分为两个部分:黑和白两个区域
/*@author @还下着雨ZG
* @brief 固定阈值图像分割
* @param[in] imSrc, 待分割的图像
* @param[out] imSegment, 分割后的图像
* @param[in] threVal, 输入的阈值
* @return, 返回正整数表示图像分割成功,负整数表示失败
**/
int ImgSegmentByGlbThreVal(const cv::Mat& imSrc, cv::Mat& imSegment, int threVal)
{
if(imSrc.empty()) return -1;
if(threVal<0 || threVal>255) return -2;
// 图像预处理
cv::Mat imGray;
if(imSrc.channels()==1) imGray = imSrc.clone();
else if(imSrc.channels() == 3)
{
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
}
else
{
return -3;
}
cv::GaussianBlur(imGray, imGray, cv::Size(3,3), 0);
//全局阈值法
cv::threshold(imGray, imSegment, threVal);
return 1;
}
阈值分割函数threshold的介绍:
double cv::threshold(
cv::Mat &imSrc, //输入图像,应该为单通道
cv::Mat &imDst, //分割后的图像,大小和类型和imSrc相同
double thresh, //表示阈值
double maxval, //最大灰度值,一般设为255
int type //阈值化类型,详细介绍如下所示
};
参数type的介绍:type是一个枚举类型的数据
THRESH_BINARY = 0, // ( x > thresh ) ? 255 : 0
THRESH_BINARY_INV = 1, // ( x > thresh ) ? 0 : 255
THRESH_TRUNC = 2, // ( x > thresh ) ? thresh : x
THRESH_TOZERO = 3, // ( x < thresh ) ? 0 : x
THRESH_TOZERO = 4, // ( x < thresh ) ? x : 0
THRESH_MASK = 7,
THRESH_OTSU = 8, // 自动处理,图像自适应二值化,常用区间【0-255】
(2)自适应阈值分割
根据图像不同区域的亮度分布计算局部阈值,对于图像的不同区域能够自适应计算不同阈值
void cv::adaptiveThreshold(
cv::Mat &imSrc, //输入的源图像
cv::Mat &imDst, //输出图像
double maxval, //预设满足条件的最大值
int adaptMethod, //指定自适应阈值算法类型(ADAPTIVE_MEAN_C或ADAPTIVE_THRESH_GAUSSIAN_C两种)
int threshType, //阈值类型(THRESH_BINARY或THRESH_BINARY_INV)
int blockSize, //领域块的大小,用于计算区域阈值(3,5,7 ...)
double C, //与算法有关的参数,是一个从均值或加权均值提取的常数,可为负
);
使用adaptiveThresh:
/*@author @还下着雨ZG
* @brief 自适应阈值图像分割
* @param[in] imSrc, 待分割的图像
* @param[out] imSegment, 分割后的图像
* @return, 返回正整数表示图像分割成功,负整数表示失败
**/
int ImgSegmentByAdpThre(const cv::Mat& imSrc, cv::Mat& imSegment)
{
if (imSrc.empty()) return -1;
cv::Mat imGray;
if (imSrc.channels() == 1)
{
cv::copyTo(imSrc, imGray, cv::Mat());
}
else if (imSrc.channels() == 3)
{
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
}
else
{
return -2;
}
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
int blockSize = 3;
int constValue = 0;
cv::adaptiveThreshold(imGray, imSegment, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, blockSize,
constValue);
return 1;
}
在实际使用时,大部分是先通过算子寻找边缘,然后和区域生长融合来分割图像
3.基于区域的分割
有相似特性的像素集合起来构成区域,将差异性较大的区域进行分裂
(1)分水岭算法
A.What(分水岭算法的概念)
将图像看作是测地学上的拓扑地貌,每一个像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域被称为盆地,对应得边界形成分水岭。
每一个局部极小值表面,刺穿一个小孔,然后从小孔浸水,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,两个盆地间形成分水岭。
opencv提供的分水岭算法原型如下:
void cv::watershed(
cv::Mat &imBGR, //三通道8bit的彩色图像
);
B.How(如何使用分水岭算法对图像进行分割)
- step01: 图像预处理(灰度化、滤波去噪等)
- step02: Canny边缘检测
- step03: 查找轮廓(findContours函数查找轮廓),并把轮廓信息按照不同编号绘制到watershed的第二次参数markers上,相当于标记注水点
- step04: watershed分水岭算法调用
- step05: 绘制分割出来的区域
/*@author @还下着雨ZG
* @param[in] imSrc, 输入的源图像
* @param[in] imMarks, 输出图像,分割之后的结果
* @return, 正整数表示成功,负整数表示失败
*/
int ImgDvdWatershed(const cv::Mat& imSrc, cv::Mat& imMarks)
{
//step01 图像预处理:灰度+滤波
cv::Mat imGray;
cv::Mat imGray;
if (imSrc.channels() == 1)
{
cv::copyTo(imSrc, imGray, cv::Mat());
}
else if (imSrc.channels() == 3)
{
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
}
else {
return -1;
}
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 2); //高斯滤波
//Step02 Canny边缘检测
cv::Mat imEdg(imGray.size(), CV_8UC1);
cv::Canny(imGray, imEdg, 40, 110);
//Step03 查找轮廓并绘制轮廓
std::vector<std::vector<cv::Point>> vCnts;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(imEdg, vCnts, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
cv::Mat imContours;
if (!imMarks.empty())
{
imMarks.release();
}
imMarks = cv::Mat(imGray.size(), CV_32S, cv::Scalar::all(0));
int iIdx = 0;
int compCount = 0;
for (; iIdx >= 0; iIdx = hierarchy[iIdx][0], compCount++)
{
cv::drawContours(imMarks, vCnts, iIdx, cv::Scalar::all(compCount + 1), 1, 8, hierarchy);
cv::drawContours(imContours, vCnts, iIdx, cv::Scalar(255), 1, 8, hierarchy);
}
cv::Mat imRGB;
if (imSrc.channels() == 1)
{
cv::cvtColor(imSrc, imRGB, cv::COLOR_GRAY2RGB);
}
else if (imSrc.channels() == 3)
{
imRGB = imSrc.clone();
}
//Step04 调用分水岭算法
cv::watershed(imRGB, imMarks);//marks既是输入参数又是输出参数
imRGB.release();
return 1;
}
说明:该函数输出参数imMarks图像,同一区域用相同的数值标识,分水岭用-1标识
(2)洪水填充法分割图像
该算法通常对边缘图像进行操作,可用于分割出比较完整的外轮廓
int cv::floodFill(
cv::Mat &imSrc, //输入图像
cv::Point seedPt, //填充的起始点
cv::Scalar newVal, //填充的像素值
cv::Rect *rect=0, //将要重绘区域的最小边界矩形区域
cv::Scalar loDiff = cv::Scalar(), //像素值负差的最大值
cv::Scalar upDiff = cv::Scalar(), //像素值正差的最大值
int flags = 4 //操作标识符
);