Zernike矩
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;
? ? ??
}
總結
- 上一篇: hough变换是如何检测出直线和圆的
- 下一篇: Opencv概述