
搬运自本人 CSDN 博客: 《纹理特征提取方法:LBP, 灰度共生矩阵》
注:本文中大量行内 Latex 公式在中不支持,如果想要仔细参阅,请移步上面的 CSDN 博客链接。
在前面的博文 《图像纹理特征总体简述》 中,笔者总结了图像纹理特征及其分类。在这里笔者对其中两种算法介绍并总结。
参考网址:
《纹理特征提取》
《【纹理特征】LBP 》
《灰度共生矩阵(GLCM)理解》
《灰度共生矩阵的理解》
《图像的纹理特征之灰度共生矩阵 》
参考论文:
《基于灰度共生矩阵提取纹理特征图像的研究》——冯建辉
《灰度共生矩阵纹理特征提取的Matlab实现》——焦蓬蓬
LBP方法(Local binary patterns, 局部二值模式)是一种用来描述图像局部纹理特征的算子;它的作用是进行特征提取,提取图像的局部纹理特征。
LBP是一个计算机视觉中用于图像特征分类的一个方法,用于纹理特征提取。后来LBP方法与HOG特征分类器与其他机器学习算法联合使用。
LBP算法的核心思想,是以某个像素点为中心,与其邻域像素点共同计算。关于邻域像素点的选择方法,其实并不唯一:
这里选择环形邻域的方法进行说明:
窗口中心的像素点作为中心,该像素点的像素值作为阈值。然后将周围8个像素点的灰度值与该阈值进行比较,若周围某像素值大于中心像素值,则该像素点位置被标记为1;反之,该像素点标记为0。
如此这样,该窗口的8个点可以产生8位的无符号数,这样就得到了该窗口的LBP值,该值反应了该窗口的纹理信息。如下图所示:
图中,中心像素点的像素值作为阈值,其值v = 3;周围邻域8个像素值中,橘塌如有3个比阈值小的像素点置0,5个比阈值大的像素点置1。
LBP算法的计算公式如下:
$$ LBP_{P, R}(x_{c},y_{c}) = \sum_{p=0}^{P-1}s(g_{p} - g_{c})2^p, s(x)=\left{\begin{matrix}1 : x \geq 0 \ 0 : x \leq 0 \end{matrix}\right. $$
LBP纹理特征向量,一般以图像分块LBP直方图表示。具体步骤如下:
得到了整幅图像的LBP纹理特征后,便可以利用SVM或者其他机器学习算法进行分类了。
这两天笔者将会对源码进行测试封装,以后会上传到我的GitHub网站上。
灰度共生矩阵法(GLCM, Gray-level co-occurrence matrix),就是通过计算灰度图像得到它的共生矩阵,然后透过计算该共生矩阵得到矩阵的部分特征值,来分别代表图像的某些纹理特征(纹理的定义仍是难点)。灰度共生圆启矩阵能反映图像灰度关于<font color = red> 方向、相邻间隔、变化幅度等 </font>综合信息,它是分析图像的局部模式和它们排列规则的基础。
对于灰度共生矩阵的理解,需要明确几个概念:方向,偏移量和灰度共生矩阵的阶数。
计算纹理特征第一步,就是将多通道的图像(一般指RGB图像)转换为灰度图像,分别提取出多个通道的灰度图像。
纹理特征是一种结构特征,使用不同通道图像得到的纹理特征都是一样的,所以可以任意选择其一。
一般在一幅图像中的灰度级有256级,从0--255。但在计算灰度共生矩阵时我们并不需要256个灰度级,且计算量实在太大,所以一般分为8个灰度级或16个灰度级。
而且当分成8个灰度级时,如果直接将像素点的灰度值除以32取整,会引起影像清晰度降低,所以进行灰度级压缩时,首先我们会将图片进行直方图均衡化处理,增加灰度值的动态范围,这样就增加了影像的整体对比效果。
注:笔者后文中的例子中,为了简要说明,所以灰衫肢度等级简单设置为4。
计算特征值前,先选择计算过程中的一些参数:
下面分部且适当的使用一些例子说明计算过程:
为了达到简单说明计算纹理特征值的目的,笔者此处做简要的假设:灰度被分为4阶,灰度阶从0--3;窗口大小为6 × 6;
窗口A的灰度矩阵A如下:
窗口B的灰度矩阵B如下:
此处以左上角元素为坐标原点,原点记为(1, 1);以此为基础举例,第四行第二列的点记为(4, 2);
情景1:d = 1,求0°方向矩阵A的共生矩阵:
则按照0°方向(即水平方向 从左向右,从右向左两个方向 ),统计矩阵值(1, 2),则如下图所示:
$$
P_{A}(d=1, \theta =0^o)=\begin{vmatrix}
0 &8 &0 &7 \
8 &0 &8 &0 \
0 &8 &0 &7 \
7 &0 &7 &0
\end{vmatrix}
$$
情景2:d = 1,求45°方向矩阵A的共生矩阵:
按照情景1,同理可得此时的统计矩阵结果如下:
$$
P_{A}(d=1, \theta =45^o)=\begin{vmatrix}
12 &0 &0 &0 \
0 &14 &0 &0 \
0 &0 &12 &0 \
0 &0 &0 &12
\end{vmatrix}
$$
情景3:d = 1,求0°与45°方向矩阵B的共生矩阵:
与前面同理,可以得到矩阵B的统计及矩阵结果如下:
$$
P_{B}(d=1, \theta =0^o)=\begin{vmatrix}
24 &4 &0 &0 \
4 &8 &0 &0 \
0 &0 &12 &2 \
0 &0 &2 &4
\end{vmatrix}
$$
$$
P_{B}(d=1, \theta =45^o)=\begin{vmatrix}
18 &3 &3 &0 \
3 &6 &1 &1 \
3 &1 &6 &1 \
0 &1 &1 &2
\end{vmatrix}
$$
矩阵A, B的其余90°、135°矩阵与上面同理,所以笔者偷懒略去。
这样,我们就已经计算得到了单个窗口的灰度共生矩阵的各个方向的矩阵,下面就要用刚才算出的矩阵计算灰度共生矩阵特征值。
用P表示灰度共生矩阵的归一化频率矩阵,其中i, j表示按照某方向同时出现于两个像素的某两个级别的灰度值,所以P(i, j)表示满足这种情况的两个像素出现的概率。
以上述情景2中的矩阵为例:
原矩阵为:
$$
P(d=1, \theta =45^o)=\begin{vmatrix}
12 &0 &0 &0 \
0 &14 &0 &0 \
0 &0 &12 &0 \
0 &0 &0 &12
\end{vmatrix}
$$
归一化后,矩阵形式变为:
$$
P(d=1, \theta =45^o)=\begin{vmatrix}
12/50 &0 &0 &0 \
0 &14/50 &0 &0 \
0 &0 &12/50 &0 \
0 &0 &0 &12/50
\end{vmatrix}
$$
灰度共生矩阵理论的前辈Haralick等人用灰度共生矩阵提出了14中特征值,但由于灰度共生矩阵的计算量很大,所以为了简便,我们一般采用四个最常用的特征来提取图像的纹理特征:<font color=red> 能量、对比度、相关度、熵 </font>。
$ ASM = \sum_{i} \sum_{j}P(i, j)^2 $
能量是灰度共生矩阵各元素的平方和,又被称角二阶距。它是图像纹理灰度变化均一的度量,反映了图像灰度分布均匀程度和纹理粗细程度。
$ CON = \sum_{i} \sum_{j} (i-j)^2 P(i,j) $
对比度是灰度共生矩阵主对角线附近的惯性矩,它体现矩阵的值如何分布,反映了图像的清晰度和纹理沟纹的深浅。
$ CORRLN = [\sum_{i} \sum_{j}((ij)P(i,j)) - \mu_{x} \mu_{y}]/ \sigma_{x} \sigma_{y} $
相关度体现了空间灰度共生矩阵元素在行或列方向上的相似程度,反映了图像局部灰度相关性。
$ ENT = - \sum_{i} \sum_{j} P(i,j) \log P(i,j) $
熵体现了图像纹理的随机性。若共生矩阵中所有值都相等,取得最大值;若共生矩阵中的值不均匀,则其值会变得很小。
求出该灰度共生矩阵各个方向的特征值后,再对这些特征值进行均值和方差的计算,这样处理就消除了方向分量对纹理特征的影响。
一个滑动窗口计算结束后,该窗口就可以移动一个像素点,形成另一个小窗口图像,重复进行上一步的计算,生成新窗口图像的共生矩阵和纹理特征值;
以此类推,滑动窗口遍历完所有的图像像素点后,整个图像就形成了一个由纹理特征值构成的一个纹理特征值矩阵。
之后,就可以将这个纹理特征值矩阵转换成纹理特征图像。
笔者已经对源码进行测试了封装,并上传到了笔者的GitHub网站上。
GitHub: https://github.com/upcAutoLang/GLCM-OpenCV
OpenCV开发SVM算法是基于LibSVM软件包开发的,LibSVM是台湾大学林智仁(Lin Chih-Jen)等开发设计的一个简单、易于使用和快速有效举神凳的SVM模式识别与回归的软件包。用OpenCV使用SVM算法的大概流程是1)设置训练样本集需要两组数据,一组是数据的类别,一组是数据的向量信息。2)设置SVM参数利用瞎胡CvSVMParams类实现类内的成员变量svm_type表示SVM类型:CvSVM::C_SVC C-SVCCvSVM:正旅:NU_SVC v-SVCCvSVM::ONE_CLASS 一类SVMCvSVM::EPS_SVR e-SVRCvSVM::NU_SVR v-SVR成员变量kernel_type表示核函数的类型:CvSVM::LINEAR 线性:u‘vCvSVM::POLY 多项式:(r*u'v + coef0)^degreeCvSVM::RBF RBF函数:exp(-r|u-v|^2)CvSVM::SIGMOID sigmoid函数:tanh(r*u'v + coef0)成员变量degree针对多项式核函数degree的设置,gamma针对多项式/rbf/sigmoid核函数的设置,coef0针对多项式/sigmoid核函数的设置,Cvalue为损失函数,在C-SVC、e-SVR、v-SVR中有效,nu设置v-SVC、一类SVM和v-SVR参数,p为设置e-SVR中损失函数的值,class_weightsC_SVC的权重,term_crit为SVM训练过程的终止条件。其中默认值degree = 0,gamma = 1,coef0 = 0,Cvalue = 1,nu = 0,p = 0,class_weights = 03)训练SVM调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams4)用这个SVM进行分类调用函数CvSVM::predict实现分类5)获得支持向量除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量。实现代码如下:view plain// step 1: float labels[4] = {1.0, -1.0, -1.0, -1.0} Mat labelsMat(3, 1, CV_32FC1, labels) float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} } Mat trainingDataMat(3, 2, CV_32FC1, trainingData) // step 2: CvSVMParams params params.svm_type = CvSVM::C_SVC params.kernel_type = CvSVM::LINEAR params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6) // step 3: CvSVM SVM SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params) // step 4: Vec3b green(0, 255, 0), blue(255, 0, 0) for (int i=0iimage.rowsi++) { for (int j=0jimage.colsj++) { Mat sampleMat = (Mat_float(1,2) i,j) float response = SVM.predict(sampleMat) if (fabs(response-1.0) 0.0001) { image.atVec3b(j, i) = green } else if (fabs(response+1.0) 0.001) { image.atVec3b(j, i) = blue } } }// step 5: int c = SVM.get_support_vector_count() for (int i=0i i++) { const float* v = SVM.get_support_vector(i) } OpenCV支持的目标检测的方法是利用样本的Haar特征进行的分类器训练,得到的级联boosted分类器(Cascade Classification)。注意,新版本的C++接口除了Haar特征以外也可以使用LBP特征。先介绍一下相关的结构,级联分类器的计算特征值的基础类FeatureEvaluator,功能包括读 *** 作read、复制clone、获得特征类型getFeatureType,分配图片分配窗口的 *** 作setImage、setWindow,计算有序特征calcOrd,计算绝对特征calcCat,创建分类器特征的结构create函数。级联分类器类CascadeClassifier。目标级联矩形的分组函数groupRectangles。接下来,我尝试使用CascadeClassifier这个级联分类器类检测视频流中的目标(haar支持的目标有人脸、人眼、嘴、鼻、身体。这里尝试比较成熟的人脸和眼镜)。用load函数加载XML分类器文件(目前提供的分类器包括Haar分类器和LBP分类器(LBP分类器数据较少))具体步骤如下:1)加载级联分类器调用CascadeClassifier类成员函数load实现,代码为:view plainCascadeClassifier face_cascade face_cascade.load("haarcascade_frontalface_alt.xml") 2)读取视频流这部分比较基础啦~~从文件中读取图像序列,读取视频文件,读取摄像头视频流看过我之前的文章,这3种方法应该了然于心。3)对每一帧使用该分类器这里先将图像变成灰度图,对它应用直方图均衡化,做一些预处理的工作。接下来检测人脸,调用detectMultiScale函数,该函数在输入图像的不同尺度中检测物体,参数image为输入的灰度图像,objects为得到被检测物体的矩形框向量组,scaleFactor为每一个图像尺度中的尺度参数,默认值为1.1,minNeighbors参数为每一个级联矩形应该保留的邻近个数(没能理解这个参数,-_-|||),默认为3,flags对于新的分类器没有用(但目前的haar分类器都是旧版的,CV_HAAR_DO_CANNY_PRUNING利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域,CV_HAAR_SCALE_IMAGE就是按比例正常检测,CV_HAAR_FIND_BIGGEST_OBJECT只检测最大的物体,CV_HAAR_DO_ROUGH_SEARCH只做初略检测),默认为0.minSize和maxSize用来限制得到的目标区域的范围。这里调用的代码如下:view plainface_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) ) 4)显示目标这个也比较简单,调用ellips函数将刚才得到的faces矩形框都显示出来更进一步,也可以在得到的每一幅人脸中得到人眼的位置,调用的分类器文件为haarcascade_eye_tree_eyeglasses.xml,先将脸部区域选为兴趣区域ROI,重复上诉步骤即可,这里就不详细介绍了。当然,感兴趣的朋友也可以试试其他的xml文件作为分类器玩一下啊,感觉LBP特征虽然xml文件的大小很小,但效果还可以,不过我没有做过多的测试。光说不练假把式,最后贴上效果图和源代码的下载地址代码下载地址:http://download.csdn.net/detail/yang_xian521/3800468OpenCV配套的教程Tutorials对于Video的部分,没有实例进行说明,我只能摸石头过河啦,之前试过一个camShift做目标检测,这次试一试光流法做运动估计。这里使用的光流法是比较常用的 Lucas-Kanade方法。对于光流法的原理,我就不过多介绍了,主要讲使用OpenCV如何实现。首先利用goodFeaturesToTrack函数得到图像中的强边界作为跟踪的特征点,接下来要调用calcOpticalFlowPyrLK函数,输入两幅连续的图像,并在第一幅图像里选择一组特征点,输出为这组点在下一幅图像中的位置。再把得到的跟踪结果过滤一下,去掉不好的特征点。再把特征点的跟踪路径标示出来。说着好简单哦~~程序的效果和代码下载http://download.csdn.net/detail/yang_xian521/3811478视频捕捉的对象中,背景通常保持不变。一般分析中关注移动的前景物体,威力提取出前景物体,需要建立背景的模型,将模型和当前帧进行比对检测前景物体。前景提取应用非常广泛,特别是在智能监控领域中。如果有不含前景物体的背景图片,提取前景的工作相对容易,只需要比对当前帧和背景图片的不同,调用函数absdiff实现。但是大多数情况,获得背景图片是不可能的,比如在复杂的场景下,或者有光线条件的变化。因此,就需要动态的变换背景。一种简单的办法是对所观察到的图片取平均,但这样做也有很多弊端,首先,这种办法在计算背景图片的前需要输入大量的图片,其次我们进行取平均的过程中不能有前景物体进入。所以一种相对好的办法是动态建立背景图片并实时更新。具体的实现过程主要分为两部分:一部分是调用absdiff函数找出当前图片和背景图片的区别,这之中使用了threshold函数去除为前景,当前图片像素与背景图片像素变化超过一定阈值的时候才认定其为前景;另一个工作是更新背景图片,调用函数accumulateWeighted,根据权重参数可以调整背景更新的速度,将当前图片更新到背景中,这里巧妙利用得到的前景提取结果作为mask,在更新背景图片的过程中避免了前景的干扰。程序效果如图,代码下载地址为http://download.csdn.net/detail/yang_xian521/3814878虽然可以调整阈值参数和权重更新速度调节前景提取的结果,但从测试视频可以发现,树叶的运动对结果的干扰还是不小的,特别对于第一帧出现前景的情况,由于后续更新背景都是对前景mask后对背景进行更新的,所以第一帧的前景部分对背景的影响因子很难被更新掉。这里提出一种改进的办法——混合高斯模型。可以使一个像素具有更多的信息,这样可以有效的减少类似树叶的不停飘动,水波的不停荡漾这种对前景的干扰。这个精密的算法比之前我所介绍的简单方法要复杂很多,不易实现。还好,OpenCV已经为我们做好了相关工作,将其封装在类BackgroundSubtractorMOG,使用起来非常方便。实现代码如下:view plainMat frame Mat foreground// 前景图片 namedWindow("Extracted Foreground") // 混合高斯物体 BackgroundSubtractorMOG mog bool stop(false) while (!stop) { if (!capture.read(frame)) { break } // 更新背景图片并且输出前景 mog(frame, foreground, 0.01) // 输出的前景图片并不是2值图片,要处理一下显示threshold(foreground, foreground, 128, 255, THRESH_BINARY_INV)欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)