cvMorphology形态学原理解析及源码分析
⑴ 图像形态学处理的概念...1
⑵ 二值图像的逻辑运算...3
⑶ 膨胀和腐蚀...4
(4) 高级形态学变换...8
(5) 细化...10
⑴ 图像形态学处理的概念
int *values; //当nShiftR为自定义时,value是指向结构元素数据的指针
//如果结构元素的大小定义为8*6,那么values为48长的int数组,值为0或1。
cvCreateStructuringElementEx创建结构元素
IplConvKernel* cvCreateStructuringElementEx( int cols, int rows, intanchor_x, int anchor_y,
anchor_x 锚点的相对水平偏移量 anchor_y 锚点的相对垂直偏移量
CV_SHAPE_CROSS, 十字交叉型,交错元素 a cross-shaped element;
CV_SHAPE_CUSTOM, 用户自定义元素。这种情况下参数 values 在封闭矩形内定义核的形状,即象素的那个邻域必须考虑。
cvReleaseStructuringElement删除结构元素
void cvReleaseStructuringElement( IplConvKernel** element );
element 被删除的结构元素的指针,函数 cvReleaseStructuringElement 释放结构 IplConvKernel 。如果 *element 为 NULL, 则函数不作用。
cvCreateStructuringElementEx( int cols, introws,
cv::Sizeksize = cv::Size(cols,rows);
cv::Pointanchor = cv::Point(anchorX,anchorY);
// 检测输入数据,当用户自定义的时候value不能为空,value默认为NULL
CV_Assert(cols > 0 &&rows > 0 && anchor.inside(cv::Rect(0,0,cols,rows)) &&
(shape!= CV_SHAPE_CUSTOM || values != 0));
int element_size = sizeof(IplConvKernel) +size*sizeof(int);
// 为什么创建的内存要比实际的大呢?大了size*sizeof(int)+32
IplConvKernel*element = (IplConvKernel*)cvAlloc(element_size+ 32);
// enum { CV_SHAPE_RECT =0, CV_SHAPE_CROSS =1,
// CV_SHAPE_ELLIPSE =2, CV_SHAPE_CUSTOM =100 };
element->nShiftR =shape< CV_SHAPE_ELLIPSE ?shape : CV_SHAPE_CUSTOM;
element->values = (int*)(element + 1);
element->values[i] =values[i];
cv::Matelem = cv::getStructuringElement(shape,ksize, anchor);
element->values[i] =elem.data[i];
cv::Matcv::getStructuringElement(intshape, Sizeksize, Pointanchor)
CV_Assert(shape ==MORPH_RECT|| shape ==MORPH_CROSS|| shape ==MORPH_ELLIPSE);
//ifanchor.x=-1,anchor.x=ksize.width/2; if anchor.y=-1,anchor.y=ksize.height/2
// 并判断是否在rect(0, 0, ksize.width, ksize.height)内
anchor =normalizeAnchor(anchor,ksize);
// 当只有一个结构元素的时候cols=1 rows=1,长方形结构元素
inv_r2= r ? 1./((double)r*r) : 0;
for( i = 0; i < ksize.height; i++ )
uchar*ptr =elem.data +i*elem.step;
if( shape == MORPH_RECT|| (shape ==MORPH_CROSS&& i ==anchor.y) )
else if( shape == MORPH_CROSS )
intdx =saturate_cast<int>(c*std::sqrt((r*r - dy*dy)*inv_r2));
j2= std::min(c +dx +1, ksize.width);
⑵ 二值图像的逻辑运算
逻辑运算尽管本质上很简单,但对于实现以形态学为基础的图像处理算法是一种有力的补充手段。在图像处理中用到的主要逻辑运算是:与、或和非(求补),它们可以互相组合形成其他逻辑运算。
⑶ 膨胀和腐蚀
膨胀和腐蚀这两种操作是形态学处理的基础,许多形态学算法都是以这两种运算为基础的。
① 膨胀
结构元素B可以看作一个卷积模板,区别在于膨胀是以集合运算为基础的,卷积是以算术运算为基础的,但两者的处理过程是相似的。
⑴ 用结构元素B,扫描图像A的每一个像素
⑵ 用结构元素与其覆盖的二值图像做“与”操作
⑶ 如果都为0,结果图像的该像素为0。否则为1
cvDilate使用任意结构元素膨胀图像
voidcvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, intiterations=1 );
src输入图像. dst 输出图像. element 用于膨胀的结构元素。
若为 NULL,则使用(1+iterations*2)*(1+iterations*2)的长方形的结构元素 .iterations膨胀的次数
函数 cvDilate 对输入图像使用指定的结构元进行膨胀,该结构决定每个具有最小值象素点的邻域形状: dst=dilate(src,element): dst(x,y)=max((x',y') in element))src(x+x',y+y')
函数支持(in-place)模式。膨胀可以重复进行 (iterations) 次. 对彩色图像,每个彩色通道单独处理。
在试图找到连通分支(即具有相似的颜色或强度的像素点的大块互相分离的区域)时通常使用膨胀操作。
CV_IMPL void
cvDilate( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element,int iterations)
{
cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
// 输入输出必须是同等尺寸、同类型的
CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
cv::Pointanchor;
// 若没有结构元素输入,kernel=NULL,anchor=(1,1)
// 否则将结构元素中的值读入kernel和anchor
convertConvKernel(element,kernel,anchor );
// 边界差值方法采用边界复制
cv::dilate(src, dst, kernel, anchor, iterations,cv::BORDER_REPLICATE);
}
static voidconvertConvKernel( constIplConvKernel*src,cv::Mat&dst,cv::Point& anchor)
{
// 若没有输入结构元素
if(!src)
{
anchor= cv::Point(1,1);
dst.release();
return;
}
// 获取结构原点的坐标
anchor =cv::Point(src->anchorX,src->anchorY);
// 读取结构元素的值
dst.create(src->nRows,src->nCols,CV_8U);
int i, size = src->nRows*src->nCols;
for( i = 0; i < size; i++ )
dst.data[i] = (uchar)src->values[i];
}
void cv::dilate( InputArraysrc, OutputArraydst, InputArraykernel,
Pointanchor,int iterations,
intborderType,constScalar& borderValue)
{
morphOp(MORPH_DILATE,src,dst, kernel,anchor, iterations,borderType, borderValue);
}
static voidmorphOp( int op, InputArray _src, OutputArray_dst,
InputArray_kernel,
Pointanchor,int iterations,
intborderType,constScalar& borderValue)
{
Mat src = _src.getMat(),kernel= _kernel.getMat();
// 如果输入的时候不输入kernel,则kernel.data=NULL,那么ksize=(3,3)
Size ksize = kernel.data ?kernel.size() :Size(3,3);
// ifanchor.x=-1,anchor.x=ksize.width/2; if anchor.y=-1,anchor.y=ksize.height/2
// 并判断是否在rect(0, 0, ksize.width, ksize.height)内
anchor =normalizeAnchor(anchor,ksize);
// 这一句是多余的,因为在上面normalizeAnchor已经判断了
CV_Assert(anchor.inside(Rect(0, 0,ksize.width,ksize.height)) );
_dst.create(src.size(),src.type() );
Mat dst = _dst.getMat();
// 如果迭代步数为或者结构元素的尺寸为,不进行处理,直接输出
if( iterations == 0 || kernel.rows*kernel.cols == 1 )
{
src.copyTo(dst);
return;
}
// 如果没有输入结构元素,那么创建(1+iterations*2)*(1+iterations*2)的长方形结构元素
// 结构元素的中心点为(iterations, iterations),并将迭代步数设置为
if( !kernel.data )
{
kernel= getStructuringElement(MORPH_RECT, Size(1+iterations*2,1+iterations*2));
anchor= Point(iterations,iterations);
iterations= 1;
}
// 如果结构步数大于的话并且kernel为长方形的结构元素,重新创建结构元素
else if( iterations> 1 && countNonZero(kernel) == kernel.rows*kernel.cols )
{
anchor= Point(anchor.x*iterations,anchor.y*iterations);
kernel= getStructuringElement(MORPH_RECT,
Size(ksize.width + (iterations-1)*(ksize.width-1),
ksize.height +(iterations-1)*(ksize.height-1)),
anchor);
iterations= 1;
}
int nStripes = 1;
// Tegra是NVIDIA公司于2008年推出的基于ARM构架通用处理器品牌(即CPU,NVIDIA称为“Computer on a chip”片上计算机),能够为便携设备提供高性能、低功耗体验。
#if definedHAVE_TEGRA_OPTIMIZATION
if (src.data != dst.data &&iterations == 1 && //NOTE:threads are not used for inplace processing
(borderType & BORDER_ISOLATED) == 0&& //TODO: check border types
src.rows >= 64 ) //NOTE: justheuristics
nStripes = 4;
#endif
parallel_for_(Range(0,nStripes),
MorphologyRunner(src,dst, nStripes,iterations,op,kernel,anchor,borderType,borderType,borderValue));
//Ptr<FilterEngine>f = createMorphologyFilter(op, src.type(),
// kernel, anchor, borderType, borderType, borderValue );
//f->apply(src, dst );
//for( int i = 1;i < iterations; i++ )
// f->apply( dst, dst );
}
// 是否采用并行处理
void cv::parallel_for_(constcv::Range&range,constcv::ParallelLoopBody&body,doublenstripes=-1)
{
// 大部分代码省略,如果定义了并行框架,可以采用并行处理,一般不定义
(void)nstripes;
body(range);
}
class MorphologyRunner: public ParallelLoopBody
{
public:
MorphologyRunner(Mat_src, Mat _dst, int _nStripes,int _iterations,
int_op,Mat _kernel,Point _anchor,
int_rowBorderType,int_columnBorderType,constScalar& _borderValue):
borderValue(_borderValue)
{
src= _src;
dst= _dst;
nStripes= _nStripes;
iterations= _iterations;
op =_op;
kernel= _kernel;
anchor= _anchor;
rowBorderType= _rowBorderType;
columnBorderType= _columnBorderType;
}
// ()操作符,最主要的运算符号
void operator () ( const Range& range) const
{
int row0 = min(cvRound(range.start *src.rows / nStripes),src.rows);
int row1 = min(cvRound(range.end *src.rows / nStripes),src.rows);
/*if(0)
printf("Size = (%d, %d),range[%d,%d), row0 = %d, row1 = %d\n",
src.rows, src.cols,range.start, range.end, row0, row1);*/
Mat srcStripe = src.rowRange(row0,row1);
Mat dstStripe = dst.rowRange(row0,row1);
// 创建形态学滤波器
Ptr<FilterEngine>f= createMorphologyFilter(op,src.type(),kernel,anchor,
rowBorderType,columnBorderType, borderValue);
// 主要的处理步骤在这里面,还未解读
f->apply(srcStripe,dstStripe );
for( int i = 1; i < iterations;i++ )
f->apply(dstStripe,dstStripe );
}
private:
Mat src;
Mat dst;
int nStripes;
int iterations;
int op;
Mat kernel;
Point anchor;
int rowBorderType;
int columnBorderType;
Scalar borderValue;
};
② 腐蚀
对Z中的集合A和B,B对A进行腐蚀的整个过程如下:
⑴ 用结构元素B,扫描图像A的每一个像素
⑵ 用结构元素与其覆盖的二值图像做“与”操作
⑶ 如果都为1,结果图像的该像素为1。否则为0
腐蚀处理的结果是使原来的二值图像减小一圈。
cvErode使用任意结构元素腐蚀图像
void cvErode( const CvArr* src, CvArr* dst,IplConvKernel* element=NULL, int iterations=1 );
src 输入图像. dst 输出图像.element 用于腐蚀的结构元素。
若为 NULL, 则使用 (1+iterations*2)*(1+iterations*2)的长方形的结构元素iterations 腐蚀的次数
函数 cvErode 对输入图像使用指定的结构元素进行腐蚀,该结构元素决定每个具有最小值象素点的邻域形状: dst=erode(src,element): dst(x,y)=min((x',y') inelement))src(x+x',y+y')
函数可能是本地操作支持in-place,不需另外开辟存储空间的意思。腐蚀可以重复进行 (iterations) 次. 对彩色图像,每个彩色通道单独处理。
腐蚀操作通常消除图像中的斑点噪声,确保图像中较大的区域仍然存在。
cvErode的源代码与cvDialte的源代码相似,在此不再对其进行解读
CV_IMPL void
cvErode( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element,int iterations)
{
cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
cv::Pointanchor;
convertConvKernel(element,kernel,anchor );
cv::erode(src, dst, kernel, anchor, iterations,cv::BORDER_REPLICATE);
}
void cv::erode( InputArraysrc, OutputArraydst, InputArraykernel,
Pointanchor,int iterations,
intborderType,constScalar& borderValue)
{
morphOp(MORPH_ERODE,src,dst, kernel,anchor, iterations,borderType, borderValue);
}
(4) 高级形态学变换
void cvMorphologyEx( const CvArr* src, CvArr* dst, CvArr* temp,
IplConvKernel* element, int operation, int iterations=1 );
函数 cvMorphologyEx 在膨胀和腐蚀基本操作的基础上,完成一些高级的形态变换:
开运算 dst=open(src,element)=dilate(erode(src,element),element)
闭运算 dst=close(src,element)=erode(dilate(src,element),element)
形态梯度dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)
对图像进行这一操作,可以将团块blob的边缘以高亮区域突出出来,保留完整的外围边缘。
"顶帽" dst=tophat(src,element)=src-open(src,element)
"黑帽" dst=blackhat(src,element)=close(src,element)-src
cvMorphologyEx( const void* srcarr,void* dstarr, void*,
IplConvKernel*element,intop, int iterations )
cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
IplConvKernel*temp_element =NULL;
// 如果没有给定结构元素,则定义*3的长方形元素,元素原点为(1,1)
temp_element= cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_RECT);
convertConvKernel(temp_element,kernel,anchor );
cvReleaseStructuringElement(&temp_element);
cv::morphologyEx(src,dst, op, kernel, anchor,iterations, cv::BORDER_REPLICATE );
void cv::morphologyEx( InputArray_src, OutputArray_dst, int op,
InputArraykernel,Pointanchor,int iterations,
intborderType,constScalar& borderValue)
_dst.create(src.size(),src.type());
erode(src,dst,kernel,anchor,iterations,borderType,borderValue );
dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
erode(src,dst,kernel,anchor,iterations,borderType,borderValue );
dilate(dst,dst,kernel,anchor,iterations,borderType,borderValue );
dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
erode(dst,dst,kernel,anchor,iterations,borderType,borderValue );
erode(src,temp,kernel,anchor,iterations,borderType,borderValue );
dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
erode(src,temp,kernel,anchor,iterations,borderType,borderValue );
dilate(temp,temp,kernel,anchor,iterations,borderType,borderValue );
dilate(src,temp,kernel,anchor,iterations,borderType,borderValue );
erode(temp,temp,kernel,anchor,iterations,borderType,borderValue );
CV_Error(CV_StsBadArg,"unknownmorphological operation" );
(5) 细化
图像细化一般作为一种图像预处理技术出现,目的是提取源图像的骨架,即是将原图像中线条宽度大于1个像素的线条细化成只有一个像素宽,形成“骨架”,形成骨架后能比较容易的分析图像,如提取图像的特征。
细化分成串行细化和并行细化,串行细化即是一边检测满足细化条件的点,一边删除细化点;并行细化即是检测细化点的时候不进行点的删除只进行标记,而在检测完整幅图像后一次性去除要细化的点。
常用的图像细化算法有hilditch算法,pavlidis算法和rosenfeld算法等。注:进行细化算法前要先对图像进行二值化,即图像中只包含“黑”和“白”两种颜色。
void cvThin( IplImage* src,IplImage* dst, int iterations=1)
参数:src原始IPL_DEPTH_8U型二值图像。dst目标存储空间,必须事先分配好,且和原图像大小类型一致。iterations,迭代次数
cvMorphology形态学原理解析及源码分析相关推荐
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...
- Java定时任务(一) Timer及TimerTask的案例解析及源码分析
Java定时任务(一) Timer及TimerTask的案例解析及源码分析 一.概述: 定时任务这个概念在Java的学习以及项目的开发中并不陌生,应用场景也是多种多样.比如我们会注意到12306网站 ...
- [转]slf4j + log4j原理实现及源码分析
slf4j + log4j原理实现及源码分析 转载于:https://www.cnblogs.com/jasonzeng888/p/6051080.html
- slf4j + log4j原理实现及源码分析
2019独角兽企业重金招聘Python工程师标准>>> #0 系列目录# 2种日志接口框架,4种日志实现框架 jdk-logging.log4j.logback日志介绍及原理 jcl ...
- Stable Diffusion 原理介绍与源码分析(一)
Stable Diffusion 原理介绍与源码分析(一) 文章目录 Stable Diffusion 原理介绍与源码分析(一) 前言(与正文无关,可以忽略) 总览 说明 Stable Diffusi ...
- GAT 算法原理介绍与源码分析
GAT 算法原理介绍与源码分析 文章目录 GAT 算法原理介绍与源码分析 零. 前言 (与正文无关, 请忽略) 广而告之 一. 文章信息 二. 核心观点 三. 核心观点解读 四. 源码分析 4.1 G ...
- 65、Spark Streaming:数据接收原理剖析与源码分析
一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ###overri ...
- 【Unity】 Spine渲染原理解析与源码解读
Spine渲染原理解析与源码解读 安装环境 从Spine编辑器导出 将资源导入Unity 基础概念 其他相关概念 Spine架构 Spine运行时的各个模块 有状态(Stateful) 和 无状态(S ...
- wireshark协议解析器 源码分析 封装调用
源码分析 Wireshark启动时,所有解析器进行初始化和注册.要注册的信息包括协议名称.各个字段的信息.过滤用的关键字.要关联的下层协议与端口(handoff)等.在解析过程,每个解析器负责解析自己 ...
最新文章
- linux diff 补丁文件夹,LINUX下制作补丁文件 diff,patch
- 823专业课计算机,辽宁科技大学823计算机专业基础综合(含数据结构、计算机组成原理、操作系统和计算机网络)考研复习经验...
- 微积分学习笔记四:空间向量基础
- python3 删除 文件 文件夹
- python中一共有多少个关键字-Python中所有的关键字
- oracle将一个表中字段的值赋值到另一个表中字段(批量)
- 基于python的nlp预备知识
- mysql中标记某条数据库_一个关系数据库表中的各条记录可以什么
- 深入update语句(延伸学习)
- PCIE2.0/PCIE3.0/PCIE4.0/PCIE5.0接口的带宽、速率计算
- 【贪心】Stall Reservations(luogu 2859/poj 3190)
- Nexus3.x.x上传第三方jar
- 还在用 Redux,要不要试试 GraphQL 和 Apollo?
- 【Spring】Spring Boot 支持 Https
- SAP License:定义在制品和结果分析过账OKG8
- ​不容错过的 13 个 JavaScript 实用技巧!
- 安全研究员公开 vBulletin 0day 的详情和 PoC
- 【译】EntityFramework6与EntityFrameworkCore的区别
- 微服务架构·基础篇[转]
- VBA技能:取整函数的使用
热门文章
- 【转】系统缓存全解析二:动态缓存(4)-第三方分布式缓存解决方案 Memcache(2)...
- UI1_UIView层操作
- acm常见算法及例题
- win8, VS2013 .NET 4.5在哪找svcutil.exe?
- 关于Qomo OpenProject的进度(2006.01.04)
- es6 --- Promise封装读取文件操作
- JavaScript --- 取得鼠标事件的坐标
- Java 的工厂方法及代理模式
- 前端知识点梳理(二)
- python每天1道面试题(3)--字符串组合