大致原理介紹:
圖像處理過程中經常需要用到二值化圖像並提取關鍵點的處理方式,但是如果只指定一個灰度閥值,得到的處理結果往往不能讓人滿意,亮度相對較高,閥值相對較低的地方會出現大片的“白斑”,。因此就需要將圖像分割爲多個小塊,然後指定一個最小亮度閥值 lignmin 和一個最大亮度閥值 lightmax,之後通過計算爲每個圖像小塊線性匹配出一個合適的閥值,從而得到較爲理想的效果。
如下圖,Y軸 lightmin 和 lightmax 分別是指定的灰度閥值下界和上界,X軸是分割後小依據平均灰度從小到大排列的圖像小塊
數據結構:
加載位圖使用OPENGL 的GLAUX 中的 auxDIBImageLoad(fielpath) 函數,然後初始圖像數據保存在一個 AUX_RGBImageRec (GLAUX提供的數據結構,注意:圖像從左下角開始掃描的)類型數據結構中。需要用到的自定義結構有 struct imgMap,保存圖像分割數據和一個存儲 imgArea * 的指針鏈表,用來存儲分割後的圖像小塊 。struct imgArea 是存儲每個分割小塊的數據結構,包含了小塊 平均亮度,大小, 原圖像中的位置和指向下一個小塊的的指針 等數據。
typedef struct _imgArea
{
unsigned char avevalue; // 平均亮度
unsigned char selvalue; // 選擇的亮度閥值
// 寬度和高度以像素爲單位
int width;
int height;
// 如果 imgArea 所屬的 imgMap->islefetbottom=TRUE,則存儲
// 的是imgArea左下角第一個像素在 imgMap->pimg->data 中對
// 應像素的指針;否則就是左上角第一個像素在 imgMap->pimg
// ->data 中對應的指針
unsigned char *begin;
// 每行像素佔用的字節數(虛擬),可以方便遍歷 imgArea 中的
// 像素,( begin + n*step )可以方便的指向第 N 行的首個像
// 素的地址。
// 注意:虛擬 的含義是說 step != imgArea->width * sizeof(unsigned char) * 3
// 實際上 step = imgMap->pimg->sizeX * sizeof(unsigned char) * 3
int step;
struct _imgArea *next;
}imgArea;
typedef struct _imgMap
{
BOOL isleftbottom;
AUX_RGBImageRec *pimg;
unsigned char *lights;
int mapsizeX;
int mapsizeY;
imgArea *data;
imgArea **ascorder_assist;
}imgMap;
二值化圖像:
首先是根據 AUX_DIBImageRec 創建 imgMap。
imgMap *createimgMap(
AUX_RGBImageRec *pimg,unsigned char *lights,
int mapsizeX,int mapsizeY,BOOL isleftbottom=TRUE
)
{
imgMap *newMap = new imgMap();
newMap->pimg = pimg;
// 初始化 imgMap 頭部
newMap->isleftbottom = isleftbottom;
//setorderLights(lightsize, lights);
newMap->lights = lights;
newMap->mapsizeX = mapsizeX;
newMap->mapsizeY = mapsizeY;
newMap->data = nullptr; // 未初始化的數據塊
newMap->ascorder_assist = nullptr;
// 初始化數據塊
CREATE_IMGMAP(newMap);
// 設置 ascorder_assist 各成員的 selvalue
int x=0; // 遍歷 newMap->ascorder_assist[]
int areanums = newMap->mapsizeX*newMap->mapsizeY;
float factor = ( float(lights[1])-float(lights[0]) )/( float(newMap->ascorder_assist[areanums-1]->avevalue)-float(newMap->ascorder_assist[0]->avevalue) );
newMap->ascorder_assist[0]->selvalue = lights[0];
for(x=1;x < areanums;++x)
{
newMap->ascorder_assist[x]->selvalue = lights[0] + (int)( factor*(newMap->ascorder_assist[x]->avevalue-newMap->ascorder_assist[0]->avevalue));
}
return newMap;
}
然後對imgMap進行分割,並將分割後得到的小塊根據 imgArea.avevalue 進行遞增排序。
void initimgMapData_leftbottom(imgMap *pmap)
{
int x=0,y=0; // 遍歷 pmap->data 之用
void *ptemp = nullptr;
imgArea *parea = pmap->data;
int areawidth_basic = ( pmap->pimg->sizeX )/( pmap->mapsizeX );
int areaheight_basic = ( pmap->pimg->sizeY) /( pmap->mapsizeY );
int areawidth_extra = ( pmap->pimg->sizeX )%( pmap->mapsizeX );
int areaheight_extra = ( pmap->pimg->sizeY )%( pmap->mapsizeY );
unsigned char *pixelperLine = nullptr; // 遍歷每行 imgArea 之用
int imgStep = pmap->pimg->sizeX*UCHAR_SIZE*3; // 圖片的每行像素寬度
int realStep = areawidth_basic*UCHAR_SIZE*3; // 每行前 mspzieX-1 個imgArea 的像素寬度
for(y=0;y < pmap->mapsizeY;++y)
{
pixelperLine = pmap->pimg->data + y*( areaheight_basic*imgStep );
for(x=0;x < pmap->mapsizeX;++x)
{
// 新 imgArea 插入鏈表
ptemp = (void *)new imgArea();
( (imgArea *)ptemp )->next = nullptr;
if( nullptr == pmap->data )
{
pmap->data = (imgArea *)ptemp;
parea = (imgArea *)ptemp;
}
parea->next = (imgArea *)ptemp;
parea = parea->next;
// 對行 imgArea 初始化
if( (pmap->mapsizeX - 1) == x ) parea->width = areawidth_basic + areawidth_extra;
else parea->width = areawidth_basic;
if( (pmap->mapsizeY - 1) == y ) parea->height = areaheight_basic + areaheight_extra;
else parea->height = areaheight_basic;
parea->step = imgStep;
parea->begin = pixelperLine + x*realStep;
// 估算平均灰度 value(採用 米字 估算)
unsigned char *perline = nullptr;
unsigned char *pixel = nullptr;
float count = 0.0f; // 米字像素的總亮度
float factor = (float)(parea->height) / (float)(parea->width); // 乘算因子
int j=0, k=0; // 遍歷之用
for(k=0;k < parea->width;++k)
{
j = (int)(k * factor);
pixel = parea->begin + k*3;
count += PIXEL_LIGHT( (pixel + parea->height/2*(parea->width*3)) );
count += PIXEL_LIGHT( (pixel + j*parea->step) );
count += PIXEL_LIGHT( (pixel + (parea->height-j-1)*parea->step) );
}
perline = parea->begin + parea->width*3;
for(j=0;j < parea->height;++j)
{
pixel = perline + parea->step;
count += PIXEL_LIGHT(pixel);
}
parea->avevalue = (int)( count / (parea->width*3 + parea->height) );
}
}
// 現在將 data 鏈表的內容複製到 ascorder_assist 數組
pmap->ascorder_assist = new imgArea*[pmap->mapsizeX * pmap->mapsizeY];
for( parea=pmap->data,x=0;parea != nullptr;parea = parea->next,++x )
{
pmap->ascorder_assist[x] = parea;
}
// 將 ascorder_assist 的內容按升序排列
int mapxy = pmap->mapsizeX * pmap->mapsizeY;
for(x=0;x < mapxy;++x)
{
for(y=0;y < (mapxy-1-x);++y)
{
if( pmap->ascorder_assist[y]->avevalue > pmap->ascorder_assist[y+1]->avevalue )
{
parea = pmap->ascorder_assist[y+1];
pmap->ascorder_assist[y+1] = pmap->ascorder_assist[y];
pmap->ascorder_assist[y] = parea;
}
}
}
}
最後調用 multiValeThreshold(...) 對圖像進行二值化。
// 改進後的二值化函數
void multiValeThreshold(
AUX_RGBImageRec *psrc,AUX_RGBImageRec *pdst,
unsigned char *lights,int mapsizeX,int mapsizeY
)
{
// 將 prc 複製到 pdst
if( pdst )
{
if( pdst->data )
{
free(pdst->data); pdst->data = nullptr;
}
free(pdst); pdst=nullptr;
}
pdst = new AUX_RGBImageRec( );
pdst->sizeX = psrc->sizeX;
pdst->sizeY = psrc->sizeY;
int datasize = psrc->sizeX*psrc->sizeY*UCHAR_SIZE*3;
pdst->data = new unsigned char[datasize];
memset(pdst->data,0,datasize);
memcpy_s(pdst->data,datasize,psrc->data,datasize);
// 創建一個局部變量對pdst進行二值化處理
imgMap *pmap = createimgMap(pdst, lights,mapsizeX,mapsizeY,TRUE);
imgArea *parea = nullptr; // 遍歷 imgMap 指針
unsigned char *pixelperLine = nullptr; // 遍歷 imgArea 指針
unsigned char *pixel = nullptr;
int x=0,y=0;
for( parea=pmap->data;parea != nullptr;parea=parea->next )
{
for(y=0;y < parea->height;++y)
{
// pixelperLing 始終指向每行的第一個像素的地址
pixelperLine = parea->begin + y*(parea->step);
for(x=0;x < parea->width;++x)
{
pixel = pixelperLine + x*UCHAR_SIZE*3;
( (PIXEL_LIGHT(pixel)) > (float)(parea->selvalue) )? SETPIXEL_MAX(pixel):SETPIXEL_ZERO(pixel);
}
}
}
return;
}
本篇結束。
如有任何錯誤或者更好的方法或建議,歡迎指正