OpenCv形态学操作
函数介绍
在常用的腐蚀和膨胀基础上,常使用**morphology()**函数.
该方法支持常用的灰度图或是彩色图像(会分单通道处理)。
" morphologyEx函数的参数声明 "
void morphologyEx(
InputArray src, //输入图像
OutputArray dst, //输出图像
int op, //表示形态学操作的标识
InputArray kernel, //自定义核
Point anchor=Point(-1,-1), //核锚点
int iterations=1, //操作迭代次数
int borderType=BORDER_CONSTANT,
//图像边界像素填充类型(默认为常数,配合borderValue参数)
const Scalar& borderValue=morphologyDefaultBorderValue()
);
op参数:
MORPH_ERODE -->"腐蚀操作"
MORPH_DILATE -->"膨胀操作"
MORPH_OPEN(MORPH_OPEN) -->"开运算"
CV_MOP_CLOSE(MORPH_CLOSE) -->"闭运算"
CV_MOP_GRADIENT(MORPH_GRADIENT) -->"形态梯度"
CV_MOP_TOPHAT(MORPH_TOPHAT) -->"礼帽"
CV_MOP_BLACKHAT(MORPH_BLACKHAT) -->"黑帽"
kernel参数:
默认参数为NULL,表示使用系统提供的3×3内核,锚点为中心位置。
常常使用自定义内核,使用函数getStructuringElement()生成我们想要的核矩阵。
Mat getStructuringElement(
int shape, //核矩阵形状
Size ksize, //尺寸
Point anchor=Point(-1,-1) //锚点位置
);
**shape**参数:
MORPH_RECT -->"矩形"
MORPH_CROSS -->"十字形"
MORPH_ELLIPSE -->"椭圆形"
**anchor**参数:
默认为Point(-1,-1)即为核中心。
对于"十字形"核中心决定核矩阵形状(十字形为单线宽)。
可以自定义一个核矩阵:
int kernel_size; //根据实际情况赋值
Mat kernel =
getStructuringElement(MORPH_RECT,
Size(kernel_size*2+1,kerner_size*2+1),
Point(kernel_size,kernel_size));
其他参数可使用默认值
所有操作支持in-place(原地输出)
形态运算
开运算
- 开运算常用与分割图片(除去小的明亮区域,剩余明亮区域被隔绝)
- 灰度图中会消除高于其邻点的孤立点。
闭运算
- 闭运算常用消除噪声(亮的区域连接在一起,大小基本不变)
- 灰度图中会消除低于其邻点的孤立点。
对于开闭运算的迭代的情况下(例如2次开运算)
是执行 腐蚀–>腐蚀–>膨胀–>膨胀
形态梯度运算
- 梯度运算应用于灰度图,凸显出灰度变化边界值
- 灰度图中边缘高亮突出。
礼帽运算
- 开运算是放大裂缝或局部低亮度区域,进行TOPHAT操作后,可以突出局部最大值周围的区域
- 突出轮廓周围更亮的区域
- 常用于分割大背景配合小图片(分割出背景)
黑帽运算
- 突出轮廓周围更暗的区域
- 可以分割出图像的轮廓
代码
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/types_c.h>
#include <iostream>
#include <opencv.hpp>
using namespace cv;
using namespace std;
void onBarChangeListener(int,void*); //trackbar回调
Mat src;
int kernel_size = 7; //核心矩阵大小
int Shape_Type=0; //核矩阵形态类型
int iterations = 1; //迭代次数
int main()
{
namedWindow("src",0);
namedWindow("dst_open",0);
namedWindow("dst_close",0);
namedWindow("dst_gradient",0);
namedWindow("dst_tophat",0);
namedWindow("dst_blackhat",0);
src = imread("H:\\road.png");
createTrackbar("Shape","src",&Shape_Type,2,onBarChangeListener);
createTrackbar("KernelSise","src",&kernel_size,10,onBarChangeListener);
createTrackbar("Iterations","src",&iterations,14,onBarChangeListener);
waitKey(0);
return 0;
}
void onBarChangeListener(int,void*)
{
Mat dst_open,dst_close,dst_gradient,dst_tophat,dst_blackhat;
Mat kernel;
int s_type;
//保证kernel矩阵最小为3x3
kernel_size = (kernel_size==0)?1:kernel_size;
//保证iterations迭代次数最小为1
iterations = (iterations==0)?1:iterations;
switch (Shape_Type)
{
case 0: s_type = MORPH_RECT; break;
case 1: s_type = MORPH_CROSS; break;
case 2: s_type = MORPH_ELLIPSE; break;
default: s_type = MORPH_RECT; break;
}
//锚点默认为中心
kernel=getStructuringElement(s_type,Size(kernel_size*2+1,kernel_size*2+1));
morphologyEx(src,dst_open,MORPH_OPEN,
kernel,Point(-1,-1),iterations);
morphologyEx(src,dst_close,MORPH_CLOSE,
kernel,Point(-1,-1),iterations);
morphologyEx(src,dst_gradient,MORPH_GRADIENT,
kernel,Point(-1,-1),iterations);
morphologyEx(src,dst_tophat,MORPH_TOPHAT,
kernel,Point(-1,-1),iterations);
morphologyEx(src,dst_blackhat,MORPH_BLACKHAT,
kernel,Point(-1,-1),iterations);
imshow("src",src);
imshow("dst_open",dst_open);
imshow("dst_close",dst_close);
imshow("dst_gradient",dst_gradient);
imshow("dst_tophat",dst_tophat);
imshow("dst_blackhat",dst_blackhat);
}
漫水填充算法
函数声明
用来标记或分离图像的一部分,类似于PS内的魔术棒功能。
在确定一个中心点情况下,利用一个下限间隔值和一个上限间隔值确定连通区域,对该区域做漫填充操作。
使用low(R1,G1,B1)和high(R2,G2,B2)来确定像素点可接受域
该函数可以彩色图片或者灰度图操作,对于彩色图如果在区域内,可以三个通道同时设置不同的间隔值,如果在接收范围内则会留下该点。
函数的操作区域一定为连续区域。
不带掩码的填充函数说明
int floodFill( InputOutputArray image, //输入图像
Point seedPoint, //中心点
CV_OUT Rect* rect=0, //设置边界区域最小矩阵
Scalar loDiff=Scalar(), //低像素间隔值
Scalar upDiff=Scalar(), //高像素间隔值
int flags=4 //控制填充区域的连通性,相关性
);
带掩码
int floodFill( InputOutputArray image, //输入图像
InputOutputArray mask, //掩码
Point seedPoint, //中心点
Scalar newVal, //填充像素值
CV_OUT Rect* rect=0, //边界最小矩阵
Scalar loDiff=Scalar(), //低
Scalar upDiff=Scalar(), //高
int flags=4 //标志
);
----------
参数说明
mask:
注意该Mat对象应该满足:
1. 单通道 CV_8UC1
2. 长宽比原图大2倍(2个像素点即可) (src.rows+2,src.cols+2)
3. floodFill函数"只操作mask内像素点为0的值"
4. mask图像的(x+1,y+1)与原图的(x,y)点对应
- 在使用过程中可以先划定并清空mask中ROI区域,再做floodFill操作。
- 也可以在mask中标定原图的边界区域,防止floodFill填充到边界。
----------
seedPoint:
floodFill操作的中心点(可以利用鼠标点击事件来获取到用户输入)
----------
newVal:
标定区域后填充的颜色值。( 彩色:Scalar(r,g,b)或灰度:Scalar(d) )
----------
rect:
默认值为0,设置floodFill函数将要填充的最小边界区域
----------
loDiff:
低下限间隔值( 彩色:Scalar(r,g,b) 灰度:Scalar(d) )
upDiff:
高上限间隔值( 彩色:Scalar(r,g,b) 灰度:Scalar(d) )
----------
flags:
int型定义前24位。参数包含三个部分。
1.对于低8位(0~7位)。控制填充算法的连通性。可以设置4或8。
a.为4 -->填充算法只考虑当前像素点的左右和垂直方向的相邻点
b.为8 -->填充算法还会考虑对角线方向的相邻点
2.对于8~15位。
指定填充掩码图像的值。如果设置为0,则mask即会用1填充。
3.对于16~23位。
a. FLOODFILL_FIXED_RANGE 只有当某个相邻点与中心点(seekPoint)像素差在范围内才填充
b. FLOODFILL_MASK_ONLY 函数不填充原始图像,只填充mask图像
flags可以通过OR操作连接起来。
例如:想用8领域填充,并填充固定像素值范围,填充mask图像,填充值为47.
则输入参数为:
flags = FLOODFILL_FIXED_RANGE
| FLOODFILL_MASK_ONLY
| (47<<8);
----------
程序实例
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/types_c.h>
#include <iostream>
#include <opencv.hpp>
using namespace cv;
using namespace std;
void onMouseChangeListener(int event,int x,int y,int,void*);//鼠标回调
void onBarChangeLisener(int,void*); //bar回调
Mat src,src_gray,temp,dst;
int lowDiff=5,highDiff=5,maxGap=30;
bool isSrc_Gray=false; //源图是否为灰度图
bool isMask=false; //floodFill是否需要掩码阵
int main()
{
src = imread("H:\\cat.jpg");
temp.create(src.rows+2,src.cols+2,CV_8UC1); //初始化掩码矩阵
temp = Scalar::all(0);
namedWindow("src");
setMouseCallback("src",onMouseChangeListener);
createTrackbar("low","src",&lowDiff,maxGap,onBarChangeLisener);
createTrackbar("high","src",&highDiff,maxGap,onBarChangeLisener);
imshow("src",src);
int key=0;
while (1)
{
key=waitKey(0);
char c=(char)key;
if (key=='e') //按下E -->退出
{
break;
}
switch (c)
{
case 'g': isSrc_Gray = true; break; //按下g --> 灰度图
case 'r': isSrc_Gray = false;break; //按下r --> 彩色图
case 'm': isMask=true; break; //按下m --> 带掩码
case 'n': isMask=false; break; //按下n --> 不带掩码
default: break;
}
}
waitKey(0);
return 0;
}
void onMouseChangeListener(int event,int x,int y,int,void*)
{
if (event!=CV_EVENT_LBUTTONDOWN)
{
return; // 只响应左击事件
}
Rect ccmp;
Point seedPoint = Point(x,y); //漫水填充原始点=鼠标点击点
isSrc_Gray?(cvtColor(src,dst,CV_RGB2GRAY)):(src.copyTo(dst)); //确定目标图是否为灰度图
if (isMask) //需要掩码
{
threshold(temp,temp,1,128,CV_THRESH_BINARY); //确定mask阵
if (isSrc_Gray)
{
floodFill(dst,temp,seedPoint,Scalar(1),&ccmp,
Scalar(lowDiff),Scalar(highDiff),4);
}
else //彩色通道
{
floodFill(dst,temp,seedPoint,Scalar(255,0,0),&ccmp,
Scalar(lowDiff,lowDiff,lowDiff),Scalar(highDiff,highDiff,highDiff),4);
}
}
else //不带mask的floodFill函数
{
if (isSrc_Gray)
{
floodFill(dst,seedPoint,Scalar(1),&ccmp,Scalar(lowDiff),Scalar(highDiff),4);
}
else
{
floodFill(dst,seedPoint,Scalar(255,0,0),&ccmp,
Scalar(lowDiff,lowDiff,lowDiff),Scalar(highDiff,highDiff,highDiff),4);
}
}
imshow("dst",dst);
}
void onBarChangeLisener(int,void*)
{
lowDiff = lowDiff==0?1:lowDiff;
highDiff = highDiff==0?1:highDiff;
}