纹理特征中HOG和LBP是两大很重要的特征,利用好这两个特征的一些性质可以很好提高后续识别精度
一、原理的简单解释
LBP,是局部二值模式,最原始的LBP模式,可以很明显用下面的图描述
对于中心像素点,一共有8个邻域值,大于中心则取值为1,小于中心则取值为0,只有0,1两种取值,一共有2^8=256种模式,可以反映这一块区域的纹理信息;
值得注意的是,它具有旋转不变性(针对于顺时针的),也就是说无论从那个邻域值开始记录都是同样的结果;
一般操作是将所有的8位二进制数旋转直至最小值,取最小值为最终模式,这样能抗图像旋转带来的影响
原始的LBP便是通过这种模式实现的,代码比较简单,可以供参考
#include <iostream>
#include <opencv2/opencv.hpp>
#include "math.h"
using namespace std;
using namespace cv;
Mat getLBP(Mat &img){
Mat gray_src;
gray_src = img;
// cvtColor(img, gray_src, COLOR_BGR2GRAY);
//定义LBP图像的长宽,由于最外围一圈无8领域,所以长宽相比于原图各减少2
int width = img.cols - 2;
int hight = img.rows - 2;
//初始化一全为0的矩阵
Mat lbpImg = Mat::zeros(hight, width, CV_8UC1);
for (int row = 1; row < img.rows - 1; row++)
{
for (int col = 1; col < img.cols - 1; col++)
{
uchar c = gray_src.at<uchar>(row, col);
uchar code = 0;
//对于八个邻域值做处理
//|= 按位或,a |= b 和 a = a | b 等价;
//左移<<就是将二进制的每一个数都往左移动一位,高位舍去,低位补0(超过存储上限8位的属于高位,需舍去)
code |= (gray_src.at<uchar>(row - 1, col - 1) > c) << 7;
code |= (gray_src.at<uchar>(row - 1, col ) > c) << 6;
code |= (gray_src.at<uchar>(row - 1, col + 1) > c) << 5;
code |= (gray_src.at<uchar>(row, col + 1) > c) << 4;
code |= (gray_src.at<uchar>(row + 1, col +1) > c) << 3;
code |= (gray_src.at<uchar>(row + 1, col ) > c) << 2;
code |= (gray_src.at<uchar>(row + 1, col - 1) > c) << 1;
code |= (gray_src.at<uchar>(row , col ) > c) << 0;
//赋值操作,注意row和col是从0开始的;
lbpImg.at<uchar>(row-1, col-1) = code;
}
}
return lbpImg;
}
int main(){
Mat src;
src = imread("D:\\001_10\\311.bmp", 0);
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
imshow("input image", src);
waitKey(0);
Mat dst;
dst = getLBP(src);
imshow("output image", dst);
waitKey(0);
return 0;
}
原图进行灰度处理,才能使用imshow输出
输出结果为(大概能看出鱼的轮廓)
补充 CV_8UC1 的含义
CV_<bit_depth>(S|U|F)C<number_of_channels>
//每一项含义解释
bit_depth (比特数) 一般取值有8bite,16bites,32bites,64bites
若为8,表示对于图片的每个像素点在内存空间占8位
S|U|F S代表signed int,有符号整型
U代表unsigned int,无符号整型
F代表float,单精度浮点型
C<number_of_channels> 一张图片的通道数
1--灰度图片grayImg,是单通道图像
2--RGB彩色图像,是3通道图像
3--带Alph通道的RGB图像,是4通道图像
而这256种模式中存在大量冗余,所以寻找了一种等价模式LBP来解决这种情况,
也就是把所有模式分为两类,一类位均匀化模式,一类为非均匀化模式;
均匀化模式:8位二进制数(首尾相连)中存在0到1、1到零的数目小于等于两个
例如:全0,全1,1个1、2个1相连、3个1相连直至7个1相连各8种均匀化模式,加起来就是1+1+7*8=58种;
非均匀化模式:除均匀化模式的所有的统计为1种;
故一共59种模式。
二、代码及解释
大致思路:
1.先得到等价模式LBP;
2.计算一个LBP特征图像块的直方图;
3.计算LBP特征图像的直方图LBPH;
4.经过上述三步得到一个图像的LBP特征;
5.调用4中单个图像LBP,实现所有样本的LBP特征提取。
先放上代码:
//等价模式LBP特征计算
template <typename _tp>
void getUniformPatternLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
//LBP特征值对应图像灰度编码表,直接默认采样点为8位
uchar temp = 1;
uchar table[256] = {0};
for(int i=0;i<256;i++)
{
if(getHopTimes(i)<3)
{
table[i] = temp;
temp++;
}
}
//是否进行UniformPattern编码的标志
bool flag = false;
//计算LBP特征图
for(int k=0;k<neighbors;k++)
{
if(k==neighbors-1)
{
flag = true;
}
//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;i<src.rows-radius;i++)
{
for(int j=radius;j<src.cols-radius;j++)
{
//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
//进行LBP特征的UniformPattern编码
if(flag)
{
dst.at<uchar>(i-radius,j-radius) = table[dst.at<uchar>(i-radius,j-radius)];
}
}
}
}
}
//计算跳变次数
int getHopTimes(int n)
{
int count = 0;
bitset<8> binaryCode = n;
for(int i=0;i<8;i++)
{
if(binaryCode[i] != binaryCode[(i+1)%8])
{
count++;
}
}
return count;
}
//计算一个LBP特征图像块的直方图
Mat getLocalRegionLBPH(const Mat& src, int minValue, int maxValue, bool normed)
{
//定义存储直方图的矩阵
Mat result;
//计算得到直方图bin的数目,直方图数组的大小
int histSize = maxValue - minValue + 1;
//定义直方图每一维的bin的变化范围
float range[] = { static_cast<float>(minValue),static_cast<float>(maxValue + 1) };
//定义直方图所有bin的变化范围
const float* ranges = { range };
//计算直方图,src是要计算直方图的图像,1是要计算直方图的图像数目,0是计算直方图所用的图像的通道序号,从0索引
//Mat()是要用的掩模,result为输出的直方图,1为输出的直方图的维度,histSize直方图在每一维的变化范围
//ranges,所有直方图的变化范围(起点和终点)
calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &ranges, true, false);
//归一化
if (normed)
{
result /= (int)src.total();
}
//结果表示成只有1行的矩阵
return result.reshape(1, 1);
}
//计算LBP特征图像的直方图LBPH
Mat getLBPH(Mat src, int numPatterns, int grid_x, int grid_y, bool normed)
{
int width = src.cols / grid_x;
int height = src.rows / grid_y;
//定义LBPH的行和列,grid_x*grid_y表示将图像分割成这么些块,numPatterns表示LBP值的模式种类
Mat result = Mat::zeros(grid_x * grid_y, numPatterns, CV_32FC1);
if (src.empty())
{
return result.reshape(1, 1);
}
int resultRowIndex = 0;
//对图像进行分割,分割成grid_x*grid_y块,grid_x,grid_y默认为8
for (int i = 0; i<grid_x; i++)
{
for (int j = 0; j<grid_y; j++)
{
//图像分块
Mat src_cell = Mat(src, Range(i*height, (i + 1)*height), Range(j*width, (j + 1)*width));
//计算直方图
Mat hist_cell = getLocalRegionLBPH(src_cell, 0, (numPatterns - 1), true);
//将直方图放到result中
Mat rowResult = result.row(resultRowIndex);
hist_cell.reshape(1, 1).convertTo(rowResult, CV_32FC1);
resultRowIndex++;
}
}
return result.reshape(1, 1);
}
对于单个图像进行处理,也就是调用前面的代码
//单个图像
Mat get_single_pic_lbp(Mat& src){
Mat src = imread("D:\\2.bmp", 0);
Mat dst;
//先得到等价模式LBP结果
getUniformPatternLBPFeature<uchar>(src, dst, 1, 8); //采样点为8个
//得到LBP的特征向量
Mat result = getLBPH(dst, 59, 8, 8, true); //单个图像特征由64*256=16384降为64*59=3778维特征
// cout << result << endl;
return result;
}
所有图片一起调用上面单个图片的LBP特征,并将所有返回值存起来,就可以得到一个超级大的矩阵,所以一般要进行降维处理,然而会发现PCA降维效果一点也不行,所以要想想别的办法处理;
主函数中进行测试
int main(){
//lbp_pca
MatrixXd all_pic_lbp;
all_pic_lbp = get_all_lbp_feature();
pca_1(all_pic_lbp, 3776, 348);
return 0;
}
三、总结
LBP中的特征等价于看待不同模式,也需要划分为不同几个范围,得到直方图结果,进行统计分析,根据不同的实际情况,可以选择不同的LBP得到更好的结果。网上原理以及代码很多,只要懂得原理,就可以任意修改为自己想要的模式了。