形态学操作 - 开操作、闭操作、顶帽、黑帽
-
开操作:open
- 先腐蚀后膨胀: d s t = o p e n ( s r c , e l e m e n t ) = d i l a t e ( e r o d e ( s r c , e l e m e n t ) ) dst = open(src,element) = dilate(erode(src, element)) dst=open(src,element)=dilate(erode(src,element))
- 可以去掉小的对象,假设对象是前景色,背景是黑色:
-
闭操作
- 先膨胀后腐蚀(bin2): d s t = c l o s e ( s r c , e l e m e n t ) = e r o d e ( d i l a t e ( s r c , e l e m e n t ) ) dst = close(src, element) = erode(dilate(src, element)) dst=close(src,element)=erode(dilate(src,element))
- 可以填充小的洞(file hole),假设对象是前景色,背景是黑色:下面的黑点消失了
-
形态学梯度 - Morphological Gradient
- 膨胀减去腐蚀: d s t = m o r p h g r a d ( s r c , e l e m e n t ) = d i l a t e ( s r c , e l e m e n t ) − e r o d e ( s r c , e l e m e n t ) dst = morph_{grad}(src, element) = dilate(src, element) - erode(src, element) dst=morphgrad(src,element)=dilate(src,element)−erode(src,element)
- 又称为基本梯度:其他还包括 内部梯度、方向梯度
-
顶帽 - top hat
- 顶帽 是原图像与开操作之间的差值图像
-
黑帽
- 黑帽是闭操作图像与源图像的差值图像
-
相关API:
morphologyEx(src, dest, CV_MOP_BLACKHAT, kernel);
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
using namespace std;
using namespace cv;
int main() {
std::string path = "../fei.JPG";
cv::Mat img = cv::imread(path, 5);
string str_input = "input image";
string str_output = "output image";
if(img.empty())
{
std::cout << "open file failed" << std::endl;
return -1;
}
//开闭、顶黑帽操作
Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point(-1, -1));
Mat dest1, dest2, dest3,dest4;
//CV_MOP_OPEN定义在 #include <opencv2/imgproc/types_c.h> 头文件中,需要包含进来
morphologyEx(img, dest1, CV_MOP_OPEN, kernel);
imshow(str_input, img);
imshow("open image", dest1);
morphologyEx(img, dest2, CV_MOP_CLOSE, kernel);
imshow(str_input, img);
imshow("close image", dest2);
morphologyEx(img, dest3, CV_MOP_BLACKHAT, kernel);
imshow(str_input, img);
imshow("black hat image", dest3);
morphologyEx(img, dest4, CV_MOP_TOPHAT, kernel);
imshow(str_input, img);
imshow("top hat image", dest4);
Mat dest5, dest6, dest7;
morphologyEx(img, dest5, CV_MOP_ERODE, kernel);
imshow(str_input, img);
imshow("erode image", dest5);
morphologyEx(img, dest6, CV_MOP_DILATE, kernel);
imshow(str_input, img);
imshow("dilate image", dest6);
morphologyEx(img, dest7, CV_MOP_GRADIENT, kernel);
imshow(str_input, img);
imshow("gradient image", dest7);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
显示结果:
注: 其中,CV_MOP_OPEN
定义在 <opencv2/imgproc/types_c.h>
头文件中,在clion IDE中需要包含该头文件才能编译通过,可以看到该头文件中还包括以下几个枚举值,
enum {
CV_MOP_ERODE =0,
CV_MOP_DILATE =1,
CV_MOP_OPEN =2,
CV_MOP_CLOSE =3,
CV_MOP_GRADIENT =4,
CV_MOP_TOPHAT =5,
CV_MOP_BLACKHAT =6
}
其中使用了CV_MOP_ERODE
、CV_MOP_DILATE
,发现和前面调用erode
、dilate
函数的结果一致,morphologyEx
调用和erode
函数调用是否有区别(从图像看出来效果是一样的),有待进一步探索、学习。
- 更新
- 查看了下源码,可以看到膨胀 就是直接调用
erode
函数,所以两个是一样的效果 - 从性能方面看,直接调用
在这里插入代码片erode
的代价更小一些。
- 查看了下源码,可以看到膨胀 就是直接调用
void morphologyEx( InputArray _src, OutputArray _dst, int op,
InputArray _kernel, Point anchor, int iterations,
int borderType, const Scalar& borderValue )
{
CV_INSTRUMENT_REGION();
Mat kernel = _kernel.getMat();
if (kernel.empty())
{
kernel = getStructuringElement(MORPH_RECT, Size(3,3), Point(1,1));
}
#ifdef HAVE_OPENCL
Size ksize = kernel.size();
anchor = normalizeAnchor(anchor, ksize);
CV_OCL_RUN(_dst.isUMat() && _src.dims() <= 2 && _src.channels() <= 4 &&
anchor.x == ksize.width >> 1 && anchor.y == ksize.height >> 1 &&
borderType == cv::BORDER_CONSTANT && borderValue == morphologyDefaultBorderValue(),
ocl_morphologyEx(_src, _dst, op, kernel, anchor, iterations, borderType, borderValue))
#endif
Mat src = _src.getMat(), temp;
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
#if !IPP_DISABLE_MORPH_ADV
//CV_IPP_RUN_FAST(ipp_morphologyEx(op, src, dst, kernel, anchor, iterations, borderType, borderValue));
#endif
switch( op )
{
case MORPH_ERODE:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_DILATE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_OPEN:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_CLOSE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_GRADIENT:
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break;
case MORPH_TOPHAT:
if( src.data != dst.data )
temp = dst;
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( temp, temp, kernel, anchor, iterations, borderType, borderValue );
dst = src - temp;
break;
case MORPH_BLACKHAT:
if( src.data != dst.data )
temp = dst;
dilate( src, temp, kernel, anchor, iterations, borderType, borderValue );
erode( temp, temp, kernel, anchor, iterations, borderType, borderValue );
dst = temp - src;
break;
case MORPH_HITMISS:
CV_Assert(src.type() == CV_8UC1);
if(countNonZero(kernel) <=0)
{
src.copyTo(dst);
break;
}
{
Mat k1, k2, e1, e2;
k1 = (kernel == 1);
k2 = (kernel == -1);
if (countNonZero(k1) <= 0)
e1 = Mat(src.size(), src.type(), Scalar(255));
else
erode(src, e1, k1, anchor, iterations, borderType, borderValue);
if (countNonZero(k2) <= 0)
e2 = Mat(src.size(), src.type(), Scalar(255));
else
{
Mat src_complement;
bitwise_not(src, src_complement);
erode(src_complement, e2, k2, anchor, iterations, borderType, borderValue);
}
dst = e1 & e2;
}
break;
default:
CV_Error( CV_StsBadArg, "unknown morphological operation" );
}
}