Zernike在1934年引入了一组定义在单位圆 上的复值函数集{ },{ }具有完备性和正交性,使得它可以表示定义在单位圆盘内的任何平方可积函数。其定义为:
表示原点到点 的矢量长度; 表示矢量 与 轴逆时针方向的夹角。
是实值径向多项式:
称为Zernike多项式。
Zernike多项式满足正交性:
其中
是 的共轭多项式。
由于Zernike多项式的正交完备性,所以在单位圆内的任何图像 都可以唯一的用下面式子来展开:
式子中 就是Zernike矩,其定义为:
注意式子中 和 采用的是不同的坐标系( 采用直角坐标,而 采用的极坐标系,在计算的时候要进行坐标转换)
对于离散的数字图像,可将积分形式改为累加形式:
我们在计算一副图像的Zernike矩时,必须将图像的中心移到坐标的原点,将图像的像素点映射到单位圆内,由于Zernike矩具有旋转不变性,我们可以将 作为图像的不变特征,其中图像的低频特征有p值小的提取,高频特征由p值高的 提取。从上面可以看出,Zernike矩可以构造任意高阶矩。
由于Zernike矩只具有旋转不变性,不具有平移和尺度不变性,所以要提前对图像进行归一化,我们采用标准矩的方法来归一化一副图像,标准矩定义为:
,
由标准矩我们可以得到图像的"重心",
我们将图像的"重心"移动到单位圆的圆心(即坐标的原点),便解决了平移问题。
我们知道 表征了图像的"面积",归一图像的尺度无非就是把他们的大小变为一致的,(这里的大小指的是图像目标物的大小,不是整幅图像的大小,"面积"也是目标物的"面积")。
综合上面结果,对图像进行 变换,最终图像 的Zernike矩就是平移,尺寸和旋转不变的。
Zernike 不变矩相比 Hu 不变矩识别效果会好一些,因为他描述了图像更多的细节内容,特别是高阶矩,但是由于Zernike 不变矩计算时间比较长,所以出现了很多快速的算法,大家可以 google 一下。
用 Zernike 不变矩来识别手势轮廓,识别率大约在 40%~50% 之间,跟 Hu 不变矩一样, Zernike 不变矩一般用来描述目标物形状占优势的图像,不适合用来描述纹理丰富的图像,对于纹理图像,识别率一般在 20%~30% 左右,很不占优势。
ZernikeMoment.h文件代码:
#pragma once
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
#define MaxP(x,y) (float)(x>y?x:y)
#define MinP(x,y) (float)(x<y?x:y)
#define PI 3.14
typedef struct
{
float rou;
float sigma;
}
RS_POINT;
typedef struct
{
float x;
float y;
}CARTESIAN_POINT;
typedef struct
{
float r;
float t;
}POLAR_POINT;
typedef struct
{
float Cnm;
float Snm;
}ZERNIKE;
class ZernikeMoment{
private:
IplImage* oriImg;
IplImage* grayImg;
float Cnm,Snm;
float Z_mode;
public:
ZernikeMoment();
~ZernikeMoment();
float getRpqr(float p,float q,float r);
RS_POINT* get_rou_sigma(float x,float y);
CARTESIAN_POINT* get_x_y(float r,float s,int N);
int Img2Gray(void);
void ClearOpenCV(void);
float get_8_XYValue(int x,int y);
float get_32_XYValue(int x,int y);
float getZernike(int n,int m);
void Caculate_8_Zernike(int n,int m);
void Caculate_32_Zernike(int n,int m);
};
Zernike矩.cpp文件中的代码:
// ZernikeTest.cpp : 定义控制台应用程序的入口点。
/************************************************************
读入图像是在RGB2GRAY函数中,自己修改对应图像的路径
*************************************************************/
#include "stdafx.h"
#include <math.h>
#include "ZernikeMoment.h"
//#include "cv.h"
//#include "highgui.h"
//#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
//using namespace cv;
double factorial(long n)//////////////
{
if(n < 0)
return(0.0) ;
if(n == 0)
return(1.0) ;
else
return(n * factorial(n-1)) ;
}
float zernikeR(int n, int l, float r)/////////////////
{
int m ;
float sum = 0.0 ;
if( ((n-l) % 2) != 0 )
{
cout <<"zernikeR(): improper values of n,l\n" ;
return(0) ;
}
for(m = 0; m <= (n-l)/2; m++)
{
sum += (pow((float)-1.0,(float)m)) * ( factorial(n-m) ) /
( factorial(m) * (factorial((n - 2*m + l) / 2)) *
(factorial((n - 2*m - l) / 2)) ) *
( pow((float)r, (float)(n - 2*m)) );
}
return(sum) ;
}
ZernikeMoment::ZernikeMoment()
{
oriImg = NULL;
grayImg = NULL;
Z_mode = 0;
}
ZernikeMoment::~ZernikeMoment()
{
}
//Fast Compute Zernike Polynomials Rpq(r)
float ZernikeMoment::getRpqr(float p,float q,float r)//////////////////////
{
/**** verify that p-q is even ***
**********************************/
float Rpqr = 0;
float Bppp=1;
int times = (p-q)/2;
int numbers = times+1;
float Bpqp = pow((p+q)/(p-q+2),times);
float* Bpqk = new float[numbers];
Bpqk[0] = Bpqp;
int k=(int)p;
int t=(int)p;
//Bpqk[0] is Bpqp , Bpqk[1] is Bpq(p-2) ... Bpqk[numbers-1] is Bpqq respectively
for(int i=0;i<(numbers-1);i++)
{
float coeff = ((k+q)*(k-q)) / ((k+p)*(p-k+2));
Bpqk[i+1] = (-1)*(Bpqk[i])*coeff;
k=k-2;
}
int temp = numbers-1;
//Compute Rpqr
for(k=(int)q;k<=t;k=k+2)
{
Rpqr = Rpqr + (Bpqk[temp])*(pow(r,k));
temp--;
}
delete[] Bpqk;
float a = Rpqr;
float b = Rpqr;
return Rpqr;
}
//Ordinary Compute Rpqr
//convert to rou, sigma coordinate,this function was never used.
RS_POINT* ZernikeMoment::get_rou_sigma(float x,float y)
{
RS_POINT *rs_p = new RS_POINT();
float rou = MaxP(abs(x),abs(y));
float sigma;
if(abs(x)==rou)
sigma = (2*(rou-x)*y)/abs(y)+x*y/rou;
if(abs(y)==rou)
sigma = 2*y -x*y/rou;
rs_p->rou = rou;
rs_p->sigma = sigma;
return rs_p;
}
//Convert rou-sigma to x-y coordinate,also never used.
CARTESIAN_POINT* ZernikeMoment::get_x_y(float rou,float sigma,int N)
{
float r = 2*rou/N;
float t = (PI*sigma)/4*rou;
CARTESIAN_POINT *xy_point = new CARTESIAN_POINT();
xy_point->x = r*cos(t);
xy_point->y = r*sin(t);
return xy_point;
}
//Get the x,y pixel value of Image ,8 depths
float ZernikeMoment::get_8_XYValue(int x,int y)//////////////////
{
int height = grayImg->height;
int widthStep = grayImg->widthStep;
char* Data = grayImg->imageData;
uchar c_value = ((uchar *)(Data+x*widthStep))[y];
float value = (float)c_value;
return value;
}
//Get the x,y pixel value of Image ,32 depths
float ZernikeMoment::get_32_XYValue(int x,int y)/////////////////
{
int height = grayImg->height;
int widthStep = grayImg->widthStep;
char* Data = grayImg->imageData;
float value = ((float *)(Data+x*widthStep))[y];
return value;
}
//RGB to Gray
//本函数部分代码被注释了,因为改为直接读入灰度图了
int ZernikeMoment::Img2Gray(void)/////////////
{
int a =1;
//if((oriImg = cvLoadImage("E:\\XH.jpg", 1)) != 0 )
if((grayImg = cvLoadImage("lena.jpg", 0)) != 0 )//读入一张灰度图
//if((grayImg = cvLoadImage("C:\\Users\\dell\\Desktop\\测试用图\\T5.bmp", 1)) != 0 )
{ //grayImg = cvCreateImage(cvSize(oriImg->width,oriImg->height),IPL_DEPTH_8U,1);
//cvCvtColor(oriImg,grayImg,CV_BGR2GRAY);
return 1;}
return 0;
}
//Cleanning Work,release memory,etc
void ZernikeMoment::ClearOpenCV(void)///////////////
{
if(oriImg!=NULL){
cvReleaseImage( &oriImg );
oriImg = NULL;
}
if(grayImg!=NULL){
cvReleaseImage( &grayImg );
grayImg = NULL;
}
}
//Function to caculate Zernike_8_(n,m), a very important function.
void ZernikeMoment::Caculate_8_Zernike(int n,int m)////////////////////
{
int height = grayImg->height;
int widthStep = grayImg->widthStep;
float N = MinP(height,widthStep);
float N2 = N/2;
float Rpqr_C =0;
float Rpqr_S =0;
for(float r=1;r<N2;r++)
{
float temp_C = 0;
float temp_S = 0;
for(float s=1;s<=8*r;s++)
{
float xy_v = get_8_XYValue(r,s);
temp_C = temp_C + cos((PI*m*s)/(4*r))*xy_v;
temp_S = temp_S + sin((PI*m*s)/(4*r))*xy_v;
}
//float Rpqr = getRpqr(n,m,(2*r)/N);
float Rpqr = zernikeR(n,m,(2*r)/N);
Rpqr_C = Rpqr_C + temp_C* Rpqr;
Rpqr_S = Rpqr_S + temp_S* Rpqr;
}
Cnm = Rpqr_C*(2*n+2)/pow(N,2);
Snm = Rpqr_S*(2*n+2)/pow(N,2);
float l_c = pow(Cnm,2);
float l_s = pow(Cnm,2);
float l_p = l_c + l_s;
Z_mode = pow((float)l_p,(float)0.5);
}
//Function to caculate Zernike_32_(n,m), a very important function.
void ZernikeMoment::Caculate_32_Zernike(int n,int m)////////////
{
int height = grayImg->height;
int widthStep = grayImg->widthStep;
float N = MinP(height,widthStep);
float N2 = N/2;
float Rpqr_C =0;
float Rpqr_S =0;
for(float r=1;r<N2;r++)
{
float temp_C = 0;
float temp_S = 0;
for(float s=1;s<=8*r;s++)
{
float xy_v = get_32_XYValue(r,s);
temp_C+= cos((PI*m*s)/(4*r))*xy_v;
temp_S+= sin((PI*m*s)/(4*r))*xy_v;
}
float Rpqr = getRpqr(n,m,(2*r)/N);
Rpqr_C = Rpqr_C + temp_C* Rpqr;
Rpqr_S = Rpqr_S + temp_S* Rpqr;
}
Cnm = Rpqr_C*(2*n+2)/pow(N,2);
Snm = Rpqr_S*(2*n+2)/pow(N,2);
Z_mode = pow((float)pow(Cnm,2)+pow(Cnm,2),(float)0.5);
}
float ZernikeMoment::getZernike(int n,int m)//////////////
{
int pass = Img2Gray();
if(!pass)
return -1;
int depth = 0;
int nChannels = 0;
nChannels = grayImg->nChannels;
// if(nChannels!=1)
// return -1;
depth = grayImg->depth;
switch(depth)
{
case IPL_DEPTH_8U: Caculate_8_Zernike(n,m); break;
case IPL_DEPTH_32F: Caculate_32_Zernike(n,m); break;
default: break;
}
ClearOpenCV();
return Z_mode;
}
int main( int argc, char** argv )
{
int succees = 0;
//Compute zernike modes ,for n = 4;
float *z_modes = new float[9];
ZernikeMoment *z_m = new ZernikeMoment();
int index = 0;
z_modes[index++] = z_m->getZernike(0,0);
z_modes[index++] = z_m->getZernike(1,1);
z_modes[index++] = z_m->getZernike(2,0);
z_modes[index++] = z_m->getZernike(2,2);
z_modes[index++] = z_m->getZernike(3,1);
z_modes[index++] = z_m->getZernike(3,3);
z_modes[index++] = z_m->getZernike(4,0);
z_modes[index++] = z_m->getZernike(4,2);
z_modes[index++] = z_m->getZernike(4,4);
if(z_m!=NULL)
delete z_m;
cout<<"zernike modes sequence: "<<endl;
for(int i=0;i<9;i++)
cout<<z_modes[i]<<",\n";//
cout<<endl;
return 0;
}