用VC++实现图像检索技术(转)

 

转自:http://www.cppblog.com/wrhwww/archive/2008/09/10/61516.html
一. 理论和方法介绍

  a) 采用颜色检索方法的目的:

 
  对多媒体数据的检索,早期的方法是用文本将多媒体数据进行标识,这显然不是基于多媒体信息本身内容的检索,对多媒体数据中包含的信息是一中及大的浪费;

  基于内容的检索是多媒体数据库的关键技术,如何实现这块技术,是值得商榷的,而最好的方法是使用无需领域知识的检索方法,因此,基于颜色的方法就是实现的关键;

  本文介绍了颜色直方图和颜色对方法在基于内容检索时的实现思路和理论;

  其实颜色直方图简单来说,就是统计图像中具有某一特定颜色的象素点数目而形成的各颜色的直方图表示,不同的直方图代表不同图片的特征。

  b) 利用颜色直方图进行检索:

  该方法也可以应用于视频数据库的查询中,有以下三种方式:

  (1)指明颜色组成--该法需要用户对图像中的颜色非常敏感,而且使用起来也不方便,检索的查准率和查全率并不高,因此文章中并未介绍该法的实现思路

  (2)指明一幅示例图像--通过与用户确定的图像的颜色直方图的相似性匹配得到查询结果,这是文章介绍的两种方法的根本

  (3)指明图像中一个子图--分割图像为各个小块,然后利用选择小块来确定图像中感兴趣的对象的轮廓,通过建立更复杂的颜色关系(如颜色对方法)来查询图像,该方法是文章的重心所在

  c) 颜色直方图实现思路的介绍:

  两图片是否相似可以采用欧氏距离来描述:

   Ed=(G,S)=用VC++实现图像检索技术(图一)(Ed越小相似度就越大)
  检索后,全图直方图的相似度的定量度量可以用如下公式表示:

   Sim(G,S)= 用VC++实现图像检索技术(图二)
   (N为颜色级数,Sim越靠近1两幅图片越相似)

  可以对上面2中的公式加改进对某些相对重要的颜色乘上一个权重,就可以做寻找某一前景或组合的查询。

  全图的颜色直方图算法过于简单,因此带来很多问题,如:可能会有两幅根本不同的图像具有完全一样的颜色直方图,不反映颜色位置信息,这样导致查准率和查全率都不高,因此问文章提出了一个改进,即将图像进行了分割,形成若干子块,这样就提供了一定程度的位置信息,而且可以对含用户感兴趣的子块加大权重,提高检索的查询智能性和查准查全率,相应的公式有,子块Gij与Sij的相似性度量为:

  用VC++实现图像检索技术(图三) 
  (P为所选颜色空间的样点数)

  再引入子块权重Wij,选取L个最大的Sim值作Simk(Gk,Sk),就有:
用VC++实现图像检索技术(图四)
    
(Wk 的选取应根据图像的特点决定,可以使图像中间或用户指定的区域权重大,以反映图像的位置信息)

  d) 颜色对实现思路介绍:

  主要目的:借助图像中相邻子块之间的颜色直方图的配对建模,实现对图像中的具体对象的查询,支持对象的移位、旋转和部分变形;

  颜色对方法特别适合于对边界明显的对象的查询;

  实现思路:计算用户输入图像的子块直方图片à用户选定包含查询对象的子块à计算这些子块与周围相邻的子块的颜色对表à将这些颜色对中差值小于某一域值的颜色对删除以消除颜色噪声à选取颜色对表中数值最大的几个颜色对做为图片的代表特征à搜索目标图像的每一子块的颜色对表寻找与这写代表颜色对的匹配à统计单一匹配次数à若有某一比例以上的颜色对匹配到,图像即被检索到。

  相似性度量:
  用VC++实现图像检索技术(图五)
    
  (N为所用查询颜色对数目)
  qj、gj:颜色对j在查询图像Q和目标图像G中出现的次数

  查询时颜色对的匹配应该是不精确的,应该允许的误差为2%以内 

  二. 具体程序实现

  a) 基于子块颜色直方图方法的程序实现:

  将图片分成4×4格局,按从左到右、从上到下的顺序,分别计算各子块的颜色直方图,因此需要设定一个三维数组,前两维为子块的坐标,最后一维为颜色级,但现在采样得到的象素点的颜色值是RGB形式的,因此,需要将RGB形式转换为可以用比较合理的有限数表示的颜色级,而人眼对亮度是最为敏感的,因此可以将RGB转换为亮度值Y,公式为:

  Y=R×0.299+G×0.587+B×0.114

  这样就确定的一个256级的颜色级别,而统计颜色直方图的三维数组就可以定义为:int Color[4][4][256],当采样到某一颜色级时候,将相应的位置加一即可。

  根据以上的子块间的相似公式:
用VC++实现图像检索技术(图六),知道某一颜色级对应的数有可能是分母,当两个颜色级的数都为0的时候,显然是不能统计的,因此需要一个数组记录实际统计过的颜色级数,也需要一个数组记录4×4子块的两幅图像的各子块的相似度。

  对于用户选定的块其实是代表查询对象的,因此应该加大权重,相对来说就是减小其他块的权重,然后可以将乘过对应权重的块的相似度相加,得到最终的相似度,然后将所有目标图像与用户输入的图像的相似度从大到小排序,选出值最大的几张作为最后的查询结果显示出来返回。

  以上是具体实现设想,程序实现如下:

//基于颜色直方图的方法
pDC->TextOut(10,168,"检索结果:");
CBmpProc *pDestBmp;
CString comp_pic_path;
double fsim[15]; file://15张待比较的目标图片与用户输入图片的相似度存放的数组
int psim[15]; file://与fsim想对应的图片编号数组,以便显示
for(int comp_pic=1;comp_pic<=15;comp_pic++){
comp_pic_path.Format("image%d.bmp",comp_pic);
bmp.LoadFromFile(comp_pic_path); // 从库中读入位图
pDestBmp = (CBmpProc*)new(CBmpProc); // 用new分配类目标
pDestBmp->LoadFromObject(bmp, &CRect(0,0,128,128));
// 从bmp中的指定区域读入图像,以便图片匹配的进行
pDestBmp->CalculateColor(*pDC); file://计算目标图片的颜色直方图
int x1,x2,y1,y2,x3,x4,y3,y4;
x1=obj_set.m_x1;x2=obj_set.m_x2;x3=obj_set.m_x3;x4=obj_set.m_x4;
y1=obj_set.m_y1;y2=obj_set.m_y2;y3=obj_set.m_y3;y4=obj_set.m_y4;
file://用户输入的对象所在子块(既用户选定的4个子块)的坐标
double sim[4][4]; file://子块之间的相似度数组
int ccount[4][4]; file://有过统计的颜色数目记录数组
for(int i=0;i<4;i++)
for(int j=0;j<4;j++){
sim[i][j]=0;
ccount[i][j]=0;
}
file://以下两个for按公式计算两幅图像的各对应子块之间的相似度
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
for(int k=0;k<256;k++){
if((pDestBmp->Color[i][j][k]>=pBmp->Color[i][j][k])&&pDestBmp->Color[i][j][k]!=0){
sim[i][j]+=(1-((fabs(pDestBmp->Color[i][j][k]-pBmp->Color[i][j][k]))/(pDestBmp->Color[i][j][k])));
ccount[i][j]++;
}
if((pDestBmp->Color[i][j][k]Color[i][j][k])&&pBmp->Color[i][j][k]!=0){
sim[i][j]+=(1-((fabs(pDestBmp->Colori][j][k]-pBmp->Color[i][j][k]))/(pBmp->Color[i][j][k])));
 ccount[i][j]++;
  }
 }
for(i=0;i<4;i++)
for(int j=0;j<4;j++){
sim[i][j]=sim[i][j]/ccount[i][j];

file://计算两图像最终的相似度结果
double final_sim=0;
for(i=0;i<4;i++)
for(int j=0;j<4;j++){
file://对用户指定的块设置权重为1
if((i==x1&&j==y1)||(i==x2&&j==y2)||(i==x3&&j==y3)||(i==x4&&j==y4))
final_sim+=sim[i][j];
else
file://其他块降低权重为0.7,提高对对象匹配的精确度
final_sim+=(sim[i][j]*0.7);
}
file://将15幅被比较图像与用户输入源图像的最后计算出来的相似度结果记录在数组中
fsim[comp_pic-1]=final_sim; 
delete (CBmpProc*)pDestBmp;
}
int count=15;double tempf;int tempp;
for(int l=0;l<15;l++){
psim[l]=l+1; file://设定编号数组
}
file://将15个相似度从大到小排列,并且改变次序的时候编号数组和跟着改变
for(int i=count;i>0;i--){
for(int j=0;jif(fsim[j]tempf=fsim[j];
tempp=psim[j];
fsim[j]=fsim[j+1];
psim[j]=psim[j+1];
fsim[j+1]=tempf;
psim[j+1]=tempp;
}
}
int disp=0;
int space=-128;
file://将相似度最大的的两张图片显示出来
for(int disp_pic=1;disp_pic<=2;disp_pic++){
comp_pic_path.Format("image%d.bmp",psim[disp_pic]);
bmp.LoadFromFile(comp_pic_path); // 从库中读入位图
pDestBmp = (CBmpProc*)new(CBmpProc); // 用new分配类目标
pDestBmp->LoadFromObject(bmp, &CRect(0,0,128,128)); // 从bmp中的指定区域读入图像
disp++;
space+=128;
pDC->Rectangle(10+space-1,190-1,138+space+1,318+1);
pDestBmp->Draw(*pDC, &CRect(10+space,190,138+space,318));
// 将pBmp中的图像绘入DC的指定区域
space+=6; 
}
delete (CBmpProc*)pBmp; // 删除类目标,delete会自动调用类的析构函数。
AfxMessageBox("检索完成"); 
}

  b) 基于颜色对的方法的程序实现

  该方法也需要分成4×4子块,计算颜色直方图,具体计算颜色直方图的方法上面已经有过详细的解释。
该方法主要在于对颜色对表示结构的实现,颜色对是某一图片的代表特征,因此在程序中必须有定量表示,现在采取用两个子块颜色直方图的欧氏距离表示,因此计算某一子块的颜色对表就是按八方向计算其与周围的子块之间的欧氏距离,将结果存放于一个double o_dis[8]的数组中,然后将这个数组从大到小排序,排序完成后再将数组中相互之间值的差小于某一域值(取8个颜色对的平均值的2%)的颜色对祛除(按序两两比较再移动数组里的变量实现),最后将结果先填入图像的特征颜色对表(有4×8=32个变量,是一个结构数组,结构记录用户选定子块的坐标和与其相对应的被选中的颜色对值)。

  最后,对4个用户选定的子块依次计算完毕,就可以调用SortColorPair()函数,对特征颜色对表做出处理(先从大到小排序,然后祛除差值小于总平均值的2%的特征颜色对)。

  在比较的时候,按顺序计算出目标图像的子块颜色对表,和以上的特征颜色对表匹配,如果匹配到,则标记该颜色对(设定另一标记0数组),并且将匹配数变量加一,如果最后匹配到的数目是60%以上,就算目标图像被搜索到。

  具体程序实现如下:

//计算子块(x,y)的颜色对表,采取"八方向邻接技术"
int CBmpProc::CalculateColorPair(int x, int y)
{
file://颜色对采取欧氏距离来描述
double o_dis[8];
for(int k=0;k<8;k++){
o_dis[k]=0;
}
file://计算(x,y)与周围所有子块的颜色直方图的欧氏距离
file://---------------------------------------------
for(int i=0;i<256;i++){
if((x-1)>=0&&(y-1)>=0)
o_dis[0]=o_dis[0]+(Color[x-1][y-1][i]-Color[x][y][i])*(Color[x-1][y-1][i]-Color[x][y][i]);
else
o_dis[0]=-1;
if((y-1)>=0)
o_dis[1]=o_dis[1]+(Color[x][y-1][i]-Color[x][y][i])*(Color[x][y-1][i]-Color[x][y][i]);
else
o_dis[1]=-1;
if((x+1)<=3&&(y-1)>=0)
o_dis[2]=o_dis[2]+(Color[x+1][y-1][i]-Color[x][y][i])*(Color[x+1][y-1][i]-Color[x][y][i]);
else
o_dis[2]=-1;
if((x-1)>=0)
o_dis[3]=o_dis[3]+(Color[x-1][y][i]-Color[x][y][i])*(Color[x-1][y][i]-Color[x][y][i]);
else
o_dis[3]=-1;
if((x+1)<=3)
o_dis[4]=o_dis[4]+(Color[x+1][y][i]-Color[x][y][i])*(Color[x+1][y][i]-Color[x][y][i]);
else
o_dis[4]=-1;
if((x-1)>=0&&(y+1)<=3)
o_dis[5]=o_dis[5]+(Color[x-1][y+1][i]-Color[x][y][i])*(Color[x-1][y+1][i]-Color[x][y][i]);
else
o_dis[5]=-1;
if((y+1)<=3)
o_dis[6]=o_dis[6]+(Color[x][y+1][i]-Color[x][y][i])*(Color[x][y+1][i]-Color[x][y][i]);
else
o_dis[6]=-1;
if((x+1)<=3&&(y+1)<=3)
o_dis[7]=o_dis[7]+(Color[x+1][y+1][i]-Color[x][y][i])*(Color[x+1][y+1][i]-Color[x][y][i]);
else
o_dis[7]=-1;
}
for(int j=0;j<8;j++){
if(o_dis[j]>=0)
o_dis[j]=sqrt(o_dis[j]);
}
file://------------------------------------------------
file://欧氏距离计算结束
int flag=0;
int num=0;
for(int pairnum=0;pairnum<32;pairnum++){
if(pair[pairnum].x!=-1){
num++;
}
}//因为在计算子块的颜色对表的时候已经写了特征颜色对数组,因此要先统计一下特征颜色对数组里已经//有多少有数值了,以便下次的写入可以接在后面,而不至于覆盖了前面的数值
file://计算颜色对差值小于某个"域值"的这个域值
double ave=0;
for(int e=0;e<8;e++){
ave+=o_dis[e];
}
ave=ave/8;ave=ave*0.02; file://采取与子块周围颜色对的平均值的2%计为域值
file://对该子块的颜色对表进行从大到小的排序,采取冒泡排序
int count=8; double temp;
for(i=count;i>0;i--){
for(int j=0;jif(o_dis[j]temp=o_dis[j];
o_dis[j]=o_dis[j+1];
o_dis[j+1]=temp;
}
}
file://消除那些颜色对差值小于某个"域值"的颜色对,以消除那些没有意义的小对象
for(k=0;kif(fabs(o_dis[k]-o_dis[k+1])for(int l=k+1;lo_dis[l]=o_dis[l+1];
}
count--;
k--;
o_dis[count]=-1;
}
}
file://将该字块计算得到的颜色对表填入该图像的特征颜色对表
for(int scan=0;scan<8;scan++){
if(o_dis[scan]>0){
pair[num].x=x;
pair[num].y=y;
pair[num].o_dis=o_dis[scan]; 
num++;
}
}
return 1;


//计算该图像的最终确定的特征颜色对表
BOOL CBmpProc::SortColorPair()
{
file://32个数据项中有count个有实际数值
for(int count=0;count<32;count++){
if(pair[count].x==-1)
break;
}
struct color_pair temp;
file://对颜色对表从大到小排列序(冒泡排序法)
for(int i=count;i>0;i--){
for(int j=0;jif(pair[j].o_distemp=pair[j];
pair[j]=pair[j+1];
pair[j+1]=temp;
}
}
file://计算域值以消除差值小于这个值的颜色对
double ave=0;
for(int e=0;eave+=pair[e].o_dis;
}
ave=ave/count;
ave=ave*0.02;
file://消除差值小于域值的颜色对
for(int k=0;kif(fabs(pair[k].o_dis-pair[k+1].o_dis)for(int l=k+1;lpair[l]=pair[l+1];
}
count--;
k--;
}
}
file://置特征颜色对数目变量
pair_count=count;
return true;
}
将计算颜色直方图的代码表达如下:
file://以下函数计算颜色直方图
BOOL CBmpProc::CalculateColor(CDC &dc)
{
if (!IsValid())
return FALSE;
ASSERT(m_pInfo);
ASSERT(m_pInfo->bmiHeader.biSize == sizeof(BITMAPINFOHEADER));
// 复制源图
CDC compDC;
// 创建与当前显示设备兼容的内存设备描述表
compDC.CreateCompatibleDC(&dc);
compDC.SelectObject(this);
COLORREF clr; file://定义一个COLORREF结构,因为提取的象素点的颜色是以RGB形式表示的
int pix_color;
int red,green,blue;
int x,y;
for(int fd=0;fd<4;fd++)
for(int sd=0;sd<4;sd++)
for(int td=0;td<256;td++){
Color[fd][sd][td]=0;
}
file://计算颜色直方图
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
for(int k=0;k<32;k++)
for(int l=0;l<32;l++){
x=j*32+l;
y=i*32+k;
clr=compDC.GetPixel(x,y);
red=GetRValue(clr);
green=GetGValue(clr);
blue=GetBValue(clr);
file://因为RGB颜色共256^3种,不可能都保存到数组中,因此要先进行一定的提取工作,因为人对亮度的感
file://觉是最明显的,所以可以先将RGB颜色值转成亮度值,这个公式即转换公司,刚好亮度数值是256级的,//就可以统计颜色直方图了
pix_color=red*0.299+green*0.587+blue*0.114; 
Color[i][j][pix_color]++; 
file://对该象素点的颜色直方图数组中的相信位置加一,是直方图的物理实现
}
return true;
}

  以上三个函数实现对某一图像内部的具体计算,而对于基于颜色对方法的外部计算如下:

//计算用户确定的4块位置与其周围位置的颜色对(颜色对现采取用相邻两块的直方图的欧氏距离表示)
pBmp->CalculateColorPair(obj_set.m_x1,obj_set.m_y1);
pBmp->CalculateColorPair(obj_set.m_x2,obj_set.m_y2);
pBmp->CalculateColorPair(obj_set.m_x3,obj_set.m_y3);
pBmp->CalculateColorPair(obj_set.m_x4,obj_set.m_y4);
file://其实在以上的4部计算中,已经形成了初步的颜色对表,在此只不过是将表中的数据从大到小排列出来//并且祛除差值小于某一域值的颜色对
file://计算颜色对结束,形成颜色对表
pBmp->SortColorPair(); 
file://颜色对表计算出来,表中的数据既是用户输入的该图像的代表特征
pDC->TextOut(10,168,"检索结果:");
CBmpProc *pDestBmp;
CString comp_pic_path;
int disp=0;
int space=-128;
file://读取带比较的图像(在此初定15幅--现定义这15幅图像即图片数据库)
for(int comp_pic=1;comp_pic<=15;comp_pic++){
comp_pic_path.Format("image%d.bmp",comp_pic);
bmp.LoadFromFile(comp_pic_path); // 从库中读入位图
pDestBmp = (CBmpProc*)new(CBmpProc); // 用new分配类目标
pDestBmp->LoadFromObject(bmp, &CRect(0,0,128,128)); // 从bmp中的指定区域读入图像
file://计算当前被比较的图像的颜色直方图
pDestBmp->CalculateColor(*pDC);
int match=0; file://颜色对匹配数目
double ave=0; file://确定匹配时候不能使用精确匹配,所以需要一个差值小于某一域值时的域值
for(int s=0;spair_count;s++){
ave+=pBmp->pair[s].o_dis;
}
ave=ave/pBmp->pair_count; file://这个域值的基数即是用户输入的图片的颜色对表中颜色对的平均值
ave=ave*0.02; file://确定误差小于2%的颜色对均属于这个域值 

int pairflag[32]; file://颜色对匹配标志数组,即某一颜色对如果在目标图像中找到,下一次就不能再匹配
for(int t=0;t<32;t++){
pairflag[t]=-1;
}
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
file://按顺序计算目标图像中一子块与其周围子块的颜色对,然后在用户输入的图像的颜色对表中查询计算出//来的颜色对
pDestBmp->CalculateColorPair(i,j); 
for(int scan=0;scan<8;scan++){
if(pDestBmp->pair[scan].x==-1)
break;
}
for(int comp=0;compfor(int count=0;countpair_count;count++){
if((fabs(pBmp->pair[count].o_dis-pDestBmp->pair[comp].o_dis))file://差值小于某域值,则匹配到
pairflag[count]=0; file://置颜色对匹配标志位
match++; file://匹配数加一
break;
}
}
}
file://重新置目标图像的颜色对表为空,因为现在的实现方式是在计算某一子块的颜色对时已经写过了颜色对//表,为保证颜色对表的真确性,必须在查询下一子块的时候重新置颜色对表为空
for(int re=0;repDestBmp->pair[re].x=-1;
}
}
file://如果有60%以上的特征颜色对匹配到,就说明该图像已经被检索到
if(match>=(pBmp->pair_count*0.60)){
file://以下是对检索到的图像的界面上的排版显示
disp++;
space+=128;
file://画图像边框
pDC->Rectangle(10+space-1,190-1,138+space+1,318+1);
pDestBmp->Draw(*pDC, &CRect(10+space,190,138+space,318)); // 将pBmp中的图像绘入DC的指定区域
space+=6;
}
delete (CBmpProc*)pDestBmp; // 删除类目标,delete会自动调用CBmpProc类的析构函数。
}
delete (CBmpProc*)pBmp; // 删除类目标,delete会自动调用类的CBmpProc析构函数。
AfxMessageBox("检索完成");
posted @ 2012-12-11 00:46  gaoxw0511  阅读(229)  评论(0编辑  收藏  举报