1.概念及原理
(1)腐蚀和膨胀是形态学中最基本的运算,而结构元素又是数学形态学中最基本的工具。结构元素可以简单理解为像素的结构以及一个原点。使用形态学滤波就是对像素的每个元素应用这个结构,当结构元素的原点和像素对齐时,它与图像的相交部分定义了一组进行形态学运算的像素。结构元素可以是任何形状,我们一般使用简单的方形、圆形、或菱形,原点即位于中心位置。
(2)腐蚀替换当前像素位像素集合中找到的最小像素值,膨胀则相反。为了想象出两个运算的效果,可以考虑背景(黑色)和前景(白色)。腐蚀情况下,给定像素的结构元素接触到背景,该像素被置为背景。膨胀则是给定像素位置接触到前景物体,该像素被置为白色。因此,腐蚀后的图像尺寸变小,膨胀即相反。
2.实验
由于形态学滤波通常使用二值图像,所以我们先把图像转换为二值图像然后对图像进行腐蚀和膨胀。
以下实验都是对该二值图像进行处理。
源码示例
#include
#include
#include
#include
using namespace std;
using namespace cv;
Mat g_pGrayImage;
Mat g_pBinaryImage;
int main(){
// 从文件中加载原图
Mat pSrcImage = cvLoadImage("horse.jpg", CV_LOAD_IMAGE_UNCHANGED);
// 转为灰度图
g_pGrayImage.create(pSrcImage.rows, pSrcImage.cols, CV_8U);
cvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
// 创建二值图
g_pBinaryImage.create(g_pGrayImage.rows, g_pGrayImage.cols, CV_8U);
// 转为二值图
threshold(g_pGrayImage, g_pBinaryImage, 100, 255, CV_THRESH_BINARY);
//显示二值图像
cvNamedWindow("BinaryImage");
imshow("BinaryImage", g_pBinaryImage);
//腐蚀图像
Mat eroded; //目标图像
erode(g_pBinaryImage, eroded, Mat());
//显示腐蚀后的图像
cvNamedWindow("Eroded Image");
imshow("Eroded Image", eroded);
//膨胀图像
Mat dilated; //目标图像
dilate(g_pBinaryImage, dilated, Mat());
//显示膨胀后的图像
cvNamedWindow("Dilated Image");
imshow("Dilated Image", dilated);
waitKey(0);
system("pause");
return 0;
}
运行效果
1.概念及原理
(1)闭运算定义为对图像进行先膨胀再腐蚀,开运算定义为对图像进行先腐蚀再膨胀
(2)闭滤波器可以填充白色前景物中的小洞,开滤波器可以移除场景中比较小的物体。这些滤波器通常在物体检测中使用,闭滤波器可以将误分割成碎片的物体重新连接,而开滤波器可以处理图像噪点引起的小像素块。
2.实验
通过合适的参数调用cv::morphologyEx 使用更高级的形态学滤波。
源码示例
#include
#include
#include
#include
using namespace std;
using namespace cv;
Mat g_pGrayImage;
Mat g_pBinaryImage;
int main(){
// 从文件中加载原图
Mat pSrcImage = cvLoadImage("horse.jpg", CV_LOAD_IMAGE_UNCHANGED);
// 转为灰度图
g_pGrayImage.create(pSrcImage.rows, pSrcImage.cols, CV_8U);
cvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
// 创建二值图
g_pBinaryImage.create(g_pGrayImage.rows, g_pGrayImage.cols, CV_8U);
// 转为二值图
threshold(g_pGrayImage, g_pBinaryImage, 100, 255, CV_THRESH_BINARY);
//显示二值图像
cvNamedWindow("BinaryImage");
imshow("BinaryImage", g_pBinaryImage);
//闭运算图像,使用5*5的结构元素使得滤波效果更明显
Mat element5(5, 5, CV_8U, Scalar(1));
Mat closed;
morphologyEx(g_pBinaryImage, closed, MORPH_CLOSE, element5);
//显示闭运算后的图像
cvNamedWindow("闭运算图像");
imshow("闭运算图像", closed);
//开运算图像
Mat opened; //目标图像
morphologyEx(g_pBinaryImage, opened, MORPH_OPEN, element5);
//显示开运算后的图像
cvNamedWindow("开运算图像");
imshow("开运算图像", opened);
waitKey(0);
system("pause");
return 0;
}
程序运行后的效果
实验一
检测灰度图中的直线。
源码示例
#include
#include
#include
#include
using namespace std;
using namespace cv;
class MorphoFeatures{
private:
//用于生成二值图像的阈值
int ithreshold;
//角点检测中用到的结构元素
Mat cross;
Mat diamond;
Mat square;
Mat x;
void applyThreshold(Mat &result);
public:
Mat getEdges(const Mat &image);
void setThreshold(float t);
};
//获取二值的边缘图像
void MorphoFeatures::applyThreshold(Mat &result){
//使用阈值化
if (ithreshold > 0)
{
threshold(result, result, ithreshold, 255, THRESH_BINARY);
}
}
//morphologyEx + 合适的滤波器实现直线的检测
Mat MorphoFeatures::getEdges(const Mat &image){
//得到梯度图
Mat result;
morphologyEx(image, result, MORPH_GRADIENT, Mat());
//阈值化以得到二值图像
applyThreshold(result);
return result;
}
//设置直方图的阈值[0,1]
void MorphoFeatures::setThreshold(float t){
ithreshold = t;
}
int main(){
Mat image = cvLoadImage("floor.jpg",0);
MorphoFeatures morpho;
morpho.setThreshold(80);
//获取边缘
Mat edges;
edges = morpho.getEdges(image);
namedWindow("floor");
imshow("floor",image);
namedWindow("edges");
imshow("edges", edges);
waitKey(0);
system("pause");
return 0;
}
检测后的效果
实验二
检测灰度图中的角点
角点检测使用四种不同的结构元素检测图像角点,分别为十字型、菱型、x型和方形元素,尺寸规定为5*5。与边缘检测不同,角点的检测复杂。运算过程主要分三步:
第一步,先用十字形的结构元素膨胀原图像,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,只有拐角处才会被“收缩”,而直线边缘不发生变化。
第二步,用X型的结构元素膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。
第三步,将一二步的两幅输出图像相减,结果只保留了各个拐角处的细节。
源码示例#include
#include
#include
#include
using namespace std;
using namespace cv;
class MorphoFeatures{
private:
//用于生成二值图像的阈值
int ithreshold;
//角点检测中用到的结构元素
Mat cross;
Mat diamond;
Mat square;
Mat x;
void applyThreshold(Mat &result);
public:
MorphoFeatures():ithreshold(50), cross(5, 5, CV_8U, Scalar(0)), diamond(5, 5, CV_8U, Scalar(1)), square(5, 5, CV_8U, Scalar(1)),x(5,5,CV_8U,Scalar(0)){
//创建十字形元素
for (int i = 0; i < 5; i++)
{
cross.at(2, i) = 1;
cross.at(i, 2) = 1;
}
//创建菱形元素
diamond.at(0, 0) = 0;
diamond.at(0, 1) = 0;
diamond.at(1, 0) = 0;
diamond.at(4, 4) = 0;
diamond.at(3, 4) = 0;
diamond.at(4, 3) = 0;
diamond.at(4, 0) = 0;
diamond.at(4, 1) = 0;
diamond.at(3, 0) = 0;
diamond.at(0, 4) = 0;
diamond.at(0, 3) = 0;
diamond.at(1, 4) = 0;
//创建x形元素
for (int i = 0; i < 5; i++)
{
x.at(i, i) = 1;
x.at(4-i, i) = 1;
}
}
Mat getEdges(const Mat &image);
void setThreshold(float t);
Mat getCorners(const Mat&image);
void drawOnImage(const Mat &binary, Mat &image);
};
//获取二值的边缘图像
void MorphoFeatures::applyThreshold(Mat &result){
//使用阈值化
if (ithreshold > 0)
{
threshold(result, result, ithreshold, 255, THRESH_BINARY);
}
}
//morphologyEx + 合适的滤波器实现直线的检测
Mat MorphoFeatures::getEdges(const Mat &image){
//得到梯度图
Mat result;
morphologyEx(image, result, MORPH_GRADIENT, Mat());
//阈值化以得到二值图像
applyThreshold(result);
return result;
}
//设置直方图的阈值[0,1]
void MorphoFeatures::setThreshold(float t){
ithreshold = t;
}
//连接使用这些结构以得到最终的角点映射图
Mat MorphoFeatures::getCorners(const Mat&image){
Mat result;
//十字形膨胀
dilate(image, result, cross);
//菱形腐蚀
erode(result, result, diamond);
Mat result2;
//X形膨胀
dilate(image, result2, x);
//方形腐蚀
erode(result2, result2, square);
//通过对两张图像做差值得到角点图像
absdiff(result2, result, result);
//阈值化以得到二值图像
applyThreshold(result);
return result;
}
//在二值图像中的每个检测点上绘制一个圆 更好的可视化结果
void MorphoFeatures::drawOnImage(const Mat &binary, Mat &image){
Mat_::const_iterator it = binary.begin();
Mat_::const_iterator itend = binary.end();
//遍历每个像素
for (int i = 0; it != itend; ++it, ++i)
{
if (*it)
circle(image, Point(i%image.step, i/image.step), 5, Scalar(255, 0, 0));
}
}
int main(){
Mat image = cvLoadImage("floor.jpg");
Mat grayImage;
cvtColor(image, grayImage,CV_BGR2GRAY);
//显示原图
namedWindow("Image");
imshow("Image", grayImage);
MorphoFeatures morpho;
//得到角点
Mat corners;
corners = morpho.getCorners(grayImage);
//在图像中显示角点
morpho.drawOnImage(corners, grayImage);
namedWindow("Corners on Image");
imshow("Corners on Image", grayImage);
waitKey(0);
return 0;
}
角点检测效果图
这里需要注意由于要把输入图像转化为二值图像,因此阈值的选择会影响角点检测效果,在此可以输入不同的阈值查看角点检测效果。
1.概念及原理
(1)分水岭分割方法是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
实验
实验主要分为
1.对二值图像进行反转,因为我们一般用255白色表示前景 0黑色表示背景
2.有二值图腐蚀移除噪点和微小物体 获得前景
3.膨胀二值图获取背景
4.前景与背景相加得到markers标记图像
5.设置markers标记进行分水岭分割
源码示例
#include
#include
#include
#include
using namespace std;
using namespace cv;
class WatershedSegmenter{
private:
//用来表示标记(图)
Mat markers;
public:
//设置标记图
void setMarkers(const Mat&makerImage);
Mat process(const Mat&image);
Mat getSegmentation();
Mat getWatersheds();
};
void WatershedSegmenter::setMarkers(const Mat&makerImage){
//转换为整数图像 watershed()的输入参数必须为一个32位有符号的标记,所以要先进行转换
makerImage.convertTo(markers, CV_32S);
}
Mat WatershedSegmenter::process(const Mat&image){
//使用算法
watershed(image, markers);
return markers;
}
//以图像形式返回结果
Mat WatershedSegmenter::getSegmentation(){
Mat temp;
//从32S到8U(0-255)会进行饱和运算,所以像素高于255的一律复制为255
markers.convertTo(temp, CV_8U);
return temp;
}
//以图像的形式返回分水岭 分割线
Mat WatershedSegmenter::getWatersheds(){
Mat temp;
//在设置标记图像,即执行setMarkers()后,边缘的像素会被赋值为-1,其他的用正整数表示
//下面的这个转换可以让边缘像素变为-1*255+255=0,即黑色,其余的溢出,赋值为255,即白色
markers.convertTo(temp, CV_8U, 255, 255);
return temp;
}
int main(){
Mat image = cvLoadImage("group.jpg");
Mat grayImage;
cvtColor(image, grayImage, CV_BGR2GRAY);
//转换为二值图
Mat binaryImage;
threshold(grayImage, binaryImage, 80,255, CV_THRESH_BINARY);
// 1.二值图 这里进行了像素反转,因为一般我们用255白色表示前景(物体),用0黑色表示背景
Mat reverseBinaryImage;
bitwise_not(binaryImage,reverseBinaryImage);
namedWindow("reverseBinaryImage");
imshow("reverseBinaryImage", reverseBinaryImage);
//2.由二值图像获得前景 腐蚀 移除噪点与微小物体
Mat fg;
erode(reverseBinaryImage, fg, Mat(), Point(-1, -1), 6);
//3.识别不包含物体的背景 膨胀二值图来获取背景(只有草地,没有树林)
Mat bg;
dilate(reverseBinaryImage, bg, Mat(), Point(-1, -1), 6);
threshold(bg, bg, 1, 128, THRESH_BINARY_INV);
//4.组合这些图形成标记图形
Mat markers(binaryImage.size(), CV_8U, Scalar(0));
markers = fg + bg; //使用重载操作符+
//5.创建分水岭分割对象
WatershedSegmenter segmenter;
//设置标记并进行处理
segmenter.setMarkers(markers);
segmenter.process(image);
namedWindow("Segmentation");
imshow("Segmentation", segmenter.getSegmentation());
namedWindow("Watersheds");
imshow("Watersheds", segmenter.getWatersheds());
waitKey(0);
return 0;
}
实验过程中的一些图片
原图和像素反转二值图
fg前景图和bg背景图
标记图像和边界图像
源码示例
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat image=imread("group.jpg");
namedWindow("Image");
imshow("Image",image);
Rect rectangle(10,100,380,180);
Mat result;
Mat bgModel,fgModel;
grabCut(image,result,rectangle,bgModel,fgModel,5,GC_INIT_WITH_RECT);
compare(result,GC_PR_FGD,result,CMP_EQ);
Mat foreground(image.size(),CV_8UC3,Scalar(255,255,255));
image.copyTo(foreground,result);
namedWindow("Foreground");
imshow("Foreground",foreground);
waitKey();
return 0;
}
grabCut函数的API说明如下:
void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect,
InputOutputArray _bgdModel, InputOutputArray _fgdModel,
int iterCount, int mode )
/*
****参数说明:
img——待分割的源图像,必须是8位3通道(CV_8UC3)图像,在处理的过程中不会被修改;
mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
GCD_BGD(=0),背景;
GCD_FGD(=1),前景;
GCD_PR_BGD(=2),可能的背景;
GCD_PR_FGD(=3),可能的前景。
如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;
rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
bgdModel——背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
fgdModel——前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
iterCount——迭代次数,必须大于0;
mode——用于指示grabCut函数进行什么操作,可选的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
GC_EVAL(=2),执行分割。
*/
【OpenCV学习笔记 008】基于形态学运算的图像变换 配套的源码下载