本文重点讲解LBP特征及OpenCV中LBP特征的基本处理。
目标检测,也叫目标提取,是一种基于目标几何和统计特征的图像分割。用级联分类器实现目标检测在AI人工智能识别中应用十分广泛。
正样本的尺寸不是必须一致的,从源码可以看到,这个是可以在输入图片文件的尺寸时设置大小从而实现在CreateSamples中进行裁剪的(参考cvCreateTrainingSamplesFromInfo中resize调整图片大小)。不过我建议你最好事先把尺寸统一处理好,除非你真的知道从图片的那个像素点开始裁剪。
数据来源尽可能做到多样化,比如样本为车,车的姿态场景应稍丰富些。同一正样本目标的图像太多会使局部特征过于明显,造成这个目标的训练过拟合,影响检测精度,不利于训练器泛化使用。
这里的输入文件名叫car.info,共计550行,可从这里下载的https://github.com/TutorProgramacion/opencv-traincascade/tree/master/image_dataset这里也行,里面已经生成possample.vec文件:汽车正负样本下载: https://download.csdn.net/download/tanmx219/10747369 检测样本下载: https://download.csdn.net/download/tanmx219/10623808
内容是这样的,其中0,0,100,40表示从(0,0)这个像素点开始裁剪,宽为100,高为40个像素, pos/pos-532.pgm 1 0 0 100 40 pos/pos-166.pgm 1 0 0 100 40 pos/pos-76.pgm 1 0 0 100 40 pos/pos-193.pgm 1 0 0 100 40 pos/pos-0.pgm 1 0 0 100 40
关于负样本的准备
原则上负样本图片中不能包含正样本目标;每个负样本之间应尽量保证各不相同,即确保负样本的多样性; 负样本的尺寸不是必须相同的,但负样本的尺寸不能小于正样本矢量集图像的宽和高; 整体上来说,负样本的准备是很简单的。 这里的负样本文件名称为neg.info。
使用openCV_createSamples选择好样本之后,就要生成OpenCV生vec向量文件,然后再进行训练以得到级联模型(xml),最后进行目标识别。 这里我们先讲OpenCV生vec向量文件的过程,理一下源码。 先说一下最后生成的那个vec文件,其文件结构是这样的
图片个数(4字节) 图片尺寸(4字节,灰度图的字节数size=宽x高) 0(4字节)0(1个字节的图片分隔符)Data(共计size个字节)0(1个字节的图片分隔符)Data(共计size个字节)0(1个字节的图片分隔符)Data(共计size个字节) …
可见,vec是一个十分简单的文件,生成过程同样很简单,如果自己编译,可以看到在OpenCV的工程目标下,有一个opencv_createSampels的项目,编译后可以得到opencv_createsamples.exe这个文件,我的目录结构是这样的
运行 opencv_createSamples -info ../dataCascade/cars.info -vec ../dataCascade/possamples.vec -num 550 -w 100 -h 40 pause 程序main里实质调用的函数是cvCreateTrainingSamplesFromInfo 其中 icvWriteVecHeader负责写入文件头, icvWriteVecSample负责写入文件分隔符
参考:https://docs.opencv.org/3.3.0/dc/d88/tutorial_traincascade.html
积分图也叫区域求和表,定义
在点 (x, y)处的面积和是该点左边和上边全部像素的和(包括该点本身在内);即,每个像素点对应的积分值,是该点左上角所有像素值的和。 有了积分表之后,就可以快速地求得任意面积的大小,如图,
假设ABCD为4个点对应的积分,那么ABCD这个区域内的像素值的和就是 Sum = D – B – C + A
在OpenCV3.4.1的源码中,有一个宏定义,
#define CALC_SUM_OFS_(p0, p1, p2, p3, ptr) \ ((ptr)[p0] - (ptr)[p1] - (ptr)[p2] + (ptr)[p3])
该宏完成一个简单的积分块计算,也就是I(p0) + I(p3) – I(p1) – I(p2), 其中I(p0)表示取p0这个点的积分值,你可以对照上面的图,把p0,p1,p2,p3分别等价于A、B、C、D这4个点。
什么时LBP?LBP有很多种形式,最常用的如下面所示的九宫格,示意图中计算的,是最中间那个4所在的位置的LBP值,其规则是,当一个值比中间这个4大时,该位高为1,否则为0。
5比中间的4大,所以该位为1 9比中间的4大,所以该位为1 1比中间的4小,所以该位为0 4比中间的4 ,也为1 6比中间的4大,所以该位为1 7比中间的4大,所以该位为1 2比中间的4小,所以该位为0 3比中间的4小,所以该位为0
这样,所有周边8个数比较过后,根据箭头的方向走一圈,得到这样一个8位的值11010011。这个值,就是中间4那个位置对应的LBP值。

作用:计算LBP码表, 该函数的功能和cascadeDetect.hpp中的LBPEvaluator::OptFeature::calc函数完全一致。 功能: 计算积分图的LBP特征(最简单的方块特征,8位,最大值255) 源码请和下面的示意图对照看,如前所述,注意LBP的方向。
inline int LBPEvaluator::OptFeature::calc( const int* p ) const
{
int cval = CALC_SUM_OFS_( ofs[5], ofs[6], ofs[9], ofs[10], p ); // center block character value
return (CALC_SUM_OFS_( ofs[0], ofs[1], ofs[4], ofs[5], p ) >= cval ? 128 : 0) | // block b0
(CALC_SUM_OFS_( ofs[1], ofs[2], ofs[5], ofs[6], p ) >= cval ? 64 : 0) | // block b1
(CALC_SUM_OFS_( ofs[2], ofs[3], ofs[6], ofs[7], p ) >= cval ? 32 : 0) | // block b2
(CALC_SUM_OFS_( ofs[6], ofs[7], ofs[10], ofs[11], p ) >= cval ? 16 : 0) | // block b5
(CALC_SUM_OFS_( ofs[10], ofs[11], ofs[14], ofs[15], p ) >= cval ? 8 : 0)| // block b8
(CALC_SUM_OFS_( ofs[9], ofs[10], ofs[13], ofs[14], p ) >= cval ? 4 : 0)| // block b7
(CALC_SUM_OFS_( ofs[8], ofs[9], ofs[12], ofs[13], p ) >= cval ? 2 : 0)| // block b6
(CALC_SUM_OFS_( ofs[4], ofs[5], ofs[8], ofs[9], p ) >= cval ? 1 : 0); // block b3
}
如上面的示意图,4表示最中间那个块,b0,b1,b2,b3 (|) b5, b6,b7,b8是其周围的8个块, 如果b0的积分值比4大,就置第8位bit为1, 否则为0 如果b1的积分值比4大,就置第7位bit为1, 否则为0 如果b2的积分值比4大,就置第6位bit为1, 否则为0 如果b5的积分值比4大,就置第5位bit为1, 否则为0 如果b8的积分值比4大,就置第4位bit为1, 否则为0 如果b7的积分值比4大,就置第3位bit为1, 否则为0 如果b6的积分值比4大,就置第2位bit为1, 否则为0 如果b3的积分值比4大,就置第1位bit为1, 否则为0 例如,如果得到的各位全是1,写成二进制就是1111 1111b,如果各位全是0,写成二进制就是0000 0000b。这样,就得到了一个完整的LBP值。
另一个函数我也把源码贴出来,原理上没有区别,只不过输入的参数不同
inline uchar CvLBPEvaluator::Feature::calc(const cv::Mat &_sum, size_t y) const
{
const int* psum = _sum.ptr((int)y);
int cval = psum[p[5]] - psum[p[6]] - psum[p[9]] + psum[p[10]];
return (uchar)((psum[p[0]] - psum[p[1]] - psum[p[4]] + psum[p[5]] >= cval ? 128 : 0) | // 0
(psum[p[1]] - psum[p[2]] - psum[p[5]] + psum[p[6]] >= cval ? 64 : 0) | // 1
(psum[p[2]] - psum[p[3]] - psum[p[6]] + psum[p[7]] >= cval ? 32 : 0) | // 2
(psum[p[6]] - psum[p[7]] - psum[p[10]] + psum[p[11]] >= cval ? 16 : 0) | // 5
(psum[p[10]] - psum[p[11]] - psum[p[14]] + psum[p[15]] >= cval ? 8 : 0) | // 8
(psum[p[9]] - psum[p[10]] - psum[p[13]] + psum[p[14]] >= cval ? 4 : 0) | // 7
(psum[p[8]] - psum[p[9]] - psum[p[12]] + psum[p[13]] >= cval ? 2 : 0) | // 6
(psum[p[4]] - psum[p[5]] - psum[p[8]] + psum[p[9]] >= cval ? 1 : 0)); // 3
}
LBP特征的产生
上面说了LBP的原理和计算,现在看一下OpenCV中LBP的产生
void CvLBPEvaluator::generateFeatures()
作用:将图像划分成尽可能多的子方块(特征),其中特征的数量保存在numFeatures中。
说明:一幅宽为width,高为height的图像, 以(x,y)为起点,形成一个方块rect(x,y,x+3w,y+3h),内含3x3=9个子方块(九宫格),(w,h)是子方块的宽和高,w,h逐渐递增,最后得到的最大方块为rect(x,y,winSize.width,winSize.height), 当x=0,y=0时就是整幅图像的区域;所以,最大子方块的大小是 w
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?