一、什么是纹理特征
- 纹理特征是从图像中计算出来的一个值,对区域内部灰度级变化的特征进行量化。
- 不是基于像素点的特征,需要在包含多个像素点的区域中进行统计计算。
- 具有旋转不变性,且对噪声有较强的抵抗能力。
- 当图像分辨率变化的时候,计算出来的纹理可能会有较大偏差。
- 适用于检索具有粗细、疏密等方面较大差别的纹理图像。
一般纹理特征有两种表示方法:(1)共生矩阵;(2)Tamura纹理特征:
二、灰度共生矩阵
1.空间灰度共生矩阵
灰度共生矩阵就是从N×N的图像f(x,y)的灰度为i的像素出发,统计与i距离为δ=(dx2+dy2)^1/2,灰度为 j的像素同时出现的概率P(i,j,δ,θ)。用数学表达式则为:
上述表述可能会比较抽线,接下来我们举一个例子表述 一下:
这是原始的图片像素矩阵,可以看到其最大像素为3,最小像素为0,因此可知最后得出的共生矩阵应为4x4的矩阵(行为像素i,列为像素j,共生矩阵中坐标(i,j)表示像素为i的点走到距离为δ=(dx2+dy2)^1/2处灰度为 j的像素出现的次数(归一化形成概率))。
0度方向,只能左右走一个单位长度。此时dx=1(-1),dy=0。
根据上述的原始矩阵和距离规定我们可以求解出下面的共生矩阵:
把下面的矩阵看成是一个4x4的数组P,P[0][0]表示像素值为 0的点左或者右移动一格像素值仍然为1的次数(注:不记录重复的情况,比如原矩阵中[0][0]向右是[0][1]像素为 0记一次,但是 [0][1]向左到[0][0]像素为0就不计数了),总体结果如下:
常用的还有45度方向,90度方向和135度方向,就不一一结束了。
2.代码实现
下述代码只实现了0度方向的共生矩阵,其他方向的自行解决。
#include<iostream>
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<vector>
#include<algorithm>
#include <numeric>
#include<iterator>
//迭代器
#include<cmath>
using namespace std;
using namespace cv;
typedef vector<vector<uchar>> VecGLCM;
void VecGLCMCount0(VecGLCM& GM_VecGLCM,cv::Mat PriImage, int nCols, int nRows);
double ComputeEntropy(VecGLCM& GM_VecGLCM, int size);
int main()
{
double Entropy; //熵值
cv::Mat image1 = imread("C4F00001.jpg",IMREAD_GRAYSCALE);
if (image1.empty())
{
cout << "图片读取失败 " << endl;
}
int nRows = image1.rows;
int nCols = image1.cols;
cout << "行数:"<<image1.rows << endl;
cout << "列数"<<image1.cols << endl;
//namedWindow("image1", 0);
//imshow("image1", image1);
//waitKey(0);
VecGLCM VecGlcm(256);
for (int i = 0; i < 256; i++)
{
VecGlcm[i].resize(256);
}
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < 256; j++)
{
VecGlcm[i][j] = 0;
}
}
VecGLCMCount0(VecGlcm, image1, nCols, nRows);
Entropy= ComputeEntropy(VecGlcm, 256);
cout << "熵值:"<<Entropy << endl;
return 0;
}
//==============================================================================
// 函数名称: VecGLCMCount0
// 参数说明: PriImage为初始的图片,nCols为列,nRows为行数
// 函数功能: 进行0度方向的共生矩阵求解
//==============================================================================
void VecGLCMCount0(VecGLCM& GM_VecGLCM,cv::Mat PriImage, int nCols, int nRows)
{
int VecGLCM_Col;
int VecGLCM_Row;
uchar* p;
for (int i = 0; i < nRows; i++)
{
p = PriImage.ptr<uchar>(i);//获取每行首地址
for (int j = 0; j < nCols - 1; ++j)
{
VecGLCM_Col = p[j];
VecGLCM_Row = p[j + 1];
GM_VecGLCM[VecGLCM_Col][VecGLCM_Row]++;
}
}
}
//==============================================================================
// 函数名称: ComputeEntropy
// 参数说明: GM_VecGLCM为共生矩阵,size为矩阵的大小(size X size)
// 函数功能: 求共生矩阵的熵
//==============================================================================
double ComputeEntropy(VecGLCM& GM_VecGLCM, int size)
{
double sum = 0;
vector<vector<uchar>>::iterator IE;
long long tatol = 0;
for(int i=0;i<GM_VecGLCM.size();i++)
{
tatol += accumulate(GM_VecGLCM[i].begin(),GM_VecGLCM[i].end(),0) ;
}
vector<uchar>::iterator it;
for (IE = GM_VecGLCM.begin(); IE < GM_VecGLCM.end(); IE++)
{
for (it = (*IE).begin(); it < (*IE).end(); it++)
{
if ((*it) != 0) sum += -(double(*it)/tatol) * log(double(*it)/tatol);
//cout << *it << " ";
}
}
return sum;
}
3.利用纹理特征实现图片分类
熵(上述代码已经实现)
- 用于测量灰度级分布随机性的一种特征参数叫做熵。
- 若图像没有任何纹理,则灰度共生矩阵几乎为零矩阵,则熵值接近为零;若图像有较多的细小纹理,则灰度共生矩阵中的数值近似相等,则图像的熵值最大。
- 熵值的定义:
根据熵的大小提前共生矩阵的信息,利用熵代表纹理特征就可以对图片进行分类啦。当然能量、对比度、均匀度 也有类似功能,具体可根据相关项目进行对比选择。
能量
反应均匀性和平滑性。
对比度
反映图像点对中前后点间灰度差的度量。
均匀度
反映图像的均匀程度。