你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
用Visual C++处理数字图象
 

  要:本文以笔者实现的数字图象处理程序为例,讲述了DIB的结构及操作、灰度直方图的绘制方法及模板卷积的实现方法。

关键字:数字图象处理  设备无关位图DIB  灰度直方图  模板卷积  Visual C++

 

人类对外部世界的感知大部分来自图象,然而许多图象不能直接使用或直接使用不能满足需求,因此我们必须对它进行一定的处理,以获取我们感兴趣的信息或形式。

随着计算机技术的发展,数字图象处理越来越体现出它的强大的生命力。从专业的遥感图象处理到大众化的Photoshop,无一不得益于数字图象处理技术。下面我们就以我们实现的数字图象处理程序中的部分功能模块对数字图象处理作一些初步的探讨。

一、设备无关位图DIB

图象在计算机中应以一定的形式来表示,在Windows平台中,我们使用设备无关位图(Device-Independent Bitmap)

无论是在文件中还是在内存中,设备无关位图结构几乎相同。由一个文件头BITMAPFILEHEADER(只存在于文件中),一个位图信息块BITMAPINFO(包含位图信息BITMAPINFOHEADER和一个颜色表RGBQUAD数组)及一个位图数据块组成。

   文件头结构的定义及含义如下:

        typedef struct tagBITMAPFILEHEADER {

        WORD        bfType;     //  位图文件标志,必须为BM(0x4D42)

                DWORD       bfSize;     //  标志文件长度,单位为字节。

        WORD        bfReserved1;    //  保留,必须为0

                WORD        bfReserved2;    //  保留,必须为0

        DWORD       bfOffBits;

//  标志从BITMAPFILEHEADER到位图数据的偏移量,单位为字节。

    } BITMAPFILEHEADER;

 

    紧跟文件头的是位图信息块BITMAPINFO,它由一个位图信息结构体BITMAPINFOHEADER和一个颜色表RGBQUAD数组组成,其定义及含义如下:

        typedef struct tagBITMAPINFO {

                BITMAPINFOHEADER    bmiHeader; //  如下。

        RGBQUAD             bmiColors[1];   //  颜色表信息。

    } BITMAPINFO;

 

        typedef struct tagBITMAPINFOHEADER{

                DWORD       biSize;        

//  BITMAPINFOHEADER结构的长度,单位为字节。

        LONG        biWidth;        //  位图宽度,单位为像素。

                LONG        biHeight;       //  位图高度,单位为像素。

        WORD        biPlanes;   //  位图位面数,必须为1

        WORD        biBitCount;         //  位图每个像素的位数。

                DWORD       biCompression;      //  标志位图的压缩类型。

        DWORD       biSizeImage;          

        LONG        biXPelsPerMeter;

        LONG        biYPelsPerMeter;

        DWORD       biClrUsed;

                DWORD       biClrImportant;

    } BITMAPINFOHEADER;

     

        typedef struct tagRGBQUAD {

                BYTE        rgbBlue;        //  标志颜色中蓝色分量的亮度。

        BYTE        rgbGreen;       //  标志颜色中绿色分量的亮度。

        BYTE        rgbRed;         //  标志颜色中红色分量的亮度。

        BYTE        rgbReserved;        //  保留,必须为0

    } RGBQUAD;

BITMAPINFO后就是图象数据了。图象的颜色数不同,每个像素所占用的字节数也不同。需要注意的是,出于对齐Cache边界的考虑,位图中每行的字节数必须是4的整数倍,不足的要用0补齐,所以位图每行所占用的字节数并不等于图象的宽度乘以每个像素所占用的字节数。

我们可以开发一个简单的类CDib来管理DIB,下面是这个类的头文件和实现文件:

// Dib.h: interface for the CDib class.

#if !defined(AFX_DIB_H__5C523E35_AE65_11D3_82A8_BFD134BF3343__INCLUDED_)

#define AFX_DIB_H__5C523E35_AE65_11D3_82A8_BFD134BF3343__INCLUDED_

 

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

 

class CDib : public CObject

{

public:

    void Show( CDC* pDC );

    CDib( LPCSTR lpcszFileName );   //  构造函数

    virtual ~CDib();                    //  析构函数

 

    //  获得位图高度,单位为像素

    LONG GetBmpHeight(){return m_lpBmpInfoHeader->biHeight;}

    //  获得位图宽度,单位为像素

    LONG GetBmpWidth(){return m_lpBmpInfoHeader->biWidth;}

    //  获得位图信息块指针

    LPBITMAPINFO GetBmpInfoPtr(){return m_lpBmpInfo;}

    //  获得位图信息头指针

    LPBITMAPINFOHEADER GetBmpInfoHeaderPtr(){return m_lpBmpInfoHeader;}

    //  获得位图颜色表指针

    LPRGBQUAD GetBmpColorTablePrt(){return m_lpColorTable;}

    //  获得图象数据指针

    LPBYTE GetImageDataPtr(){return m_lpImageData;}

    //  获得每行的字节数

    UINT GetBytesPerRow(){return m_nBytesPerRow;}

    //  获得位图颜色数目

    UINT GetBmpNumColors(){return m_numColors;}

private:

    VOID LoadBitmapFile( LPCSTR lpcszFileName );        //  读取位图文件

 

    LPBITMAPINFO            m_lpBmpInfo;        //  指向文件信息块的指针

    LPBITMAPINFOHEADER  m_lpBmpInfoHeader;      //  指向文件信息头的指针

    LPRGBQUAD           m_lpColorTable;         //  指向颜色表的指针

    LPBYTE              m_lpImageData;          //  指向图象数据的指针

    UINT                m_numColors;            //  图象的颜色数目

    UINT                m_nBytesPerRow;         //  每行字节数

};

 

#endif // !defined(AFX_DIB_H__5C523E35_AE65_11D3_82A8_BFD134BF3343__INCLUDED_)

 

// Dib.cpp: implementation of the CDib class.

#include "stdafx.h"

#include "Test.h"

#include "Dib.h"

#include "HistogramDialog.h"

#include "ImageSegmentation.h"

 

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

 

CDib::CDib(LPCSTR lpcszFileName)

{

    LoadBitmapFile( lpcszFileName );

}

 

CDib::~CDib()

{

    delete []m_lpBmpInfo;

}

 

VOID CDib::LoadBitmapFile(LPCSTR lpcszFileName)

{

    BITMAPFILEHEADER    bmpFileHeader;

    CFile           file( lpcszFileName, CFile::modeRead );

 

    file.Read( (void*)&bmpFileHeader, sizeof( bmpFileHeader ) );

 

    if( bmpFileHeader.bfType != 0x4d42 )

    {   //  不是位图文件

        AfxMessageBox( "Not a bitmap file." );

 

        m_lpBmpInfo     = NULL;

        m_lpBmpInfoHeader   = NULL;

        m_lpColorTable      = NULL;

        m_lpImageData       = NULL;

        m_numColors     = 0;

    }

    else

    {

        DWORD fileLength    = file.GetLength();

        DWORD dibSize       = fileLength - sizeof( bmpFileHeader );

        BYTE*  pDib         = new BYTE[ dibSize ];

        file.Read( (void*)pDib, dibSize );

        file.Close();

 

        m_lpBmpInfo         = (LPBITMAPINFO) pDib;

        m_lpBmpInfoHeader   = (LPBITMAPINFOHEADER) pDib;

        m_lpColorTable      = (RGBQUAD*)( pDib + m_lpBmpInfoHeader->biSize );

 

        //  计算图象颜色数

        if ( (m_lpBmpInfoHeader->biClrUsed == 0)

&& (m_lpBmpInfoHeader->biBitCount < 9) )

            m_numColors = (1 << m_lpBmpInfoHeader->biBitCount);

        else

        m_numColors = (UINT) m_lpBmpInfoHeader->biClrUsed;

 

        //  获得图象数据指针

        m_lpImageData   =(LPBYTE)m_lpColorTable+m_numColors* sizeof(RGBQUAD);

        //  计算位图每行所占用的字节数

        m_nBytesPerRow = ( GetBmpWidth()+3 )/4*4;

    }

}

 

void CDib::Show(CDC *pDC)

{

    if( !m_lpBmpInfo )

        return;

   

    HPALETTE hPal, hOldPal;

 

    //  判断图象是否有调色板

    if( m_lpBmpInfoHeader->biBitCount <= 8 )

    {   //  有调色板则分配空间并填充

        LPLOGPALETTE pLogPal = (LPLOGPALETTE) new char[2 * sizeof(WORD) + m_numColors * sizeof(PALETTEENTRY)];

        pLogPal->palVersion = 0x300;

        pLogPal->palNumEntries = m_numColors;

        LPRGBQUAD pDibQuad = (LPRGBQUAD) m_lpColorTable;

        for(UINT i = 0; i < m_numColors; i++)

        {

            pLogPal->palPalEntry[i].peRed = pDibQuad->rgbRed;

            pLogPal->palPalEntry[i].peGreen = pDibQuad->rgbGreen;

            pLogPal->palPalEntry[i].peBlue = pDibQuad->rgbBlue;

            pLogPal->palPalEntry[i].peFlags = 0;

            pDibQuad++;

        }

        //  创建并使用调色板

        hPal    = ::CreatePalette( (LPLOGPALETTE)pLogPal );

        hOldPal = ::SelectPalette( pDC->m_hDC, hPal, FALSE );

        ::RealizePalette( pDC->m_hDC );

    }

   

    LONG    bmpWidth    = GetBmpWidth();

    LONG    bmpHeight   = GetBmpHeight();

 

    pDC->SetStretchBltMode(COLORONCOLOR);

    StretchDIBits( pDC->m_hDC,

        0, 0, bmpWidth, bmpHeight,

        0, 0, bmpWidth, bmpHeight,

        (void*)m_lpImageData, m_lpBmpInfo,

        DIB_RGB_COLORS, SRCCOPY

        );

 

    if( m_lpBmpInfoHeader->biBitCount <= 8 )

    {

        ::SelectPalette( pDC->m_hDC, hOldPal, FALSE );

        ::DeleteObject( hPal );

    }

}

 

为简单起见,下面的内容都以灰度图象为例。

二、灰度直方图

    灰度直方图是分析数字图象的强有力的工具。它以灰度值为横轴,以具有某一灰度值的像素在图象中出现的次数为纵轴。由此可见,灰度直方图反映了一幅图象的灰度分布情况。

    我们在程序中实现了一个绘制灰度直方图的对话框。

创建一个如图一的对话框资源模板,

 

 

 

 


图一  直方图对话框资源模板

       

ID

类型

属性

IDC_STATIC

组框   

General页的Caption为“图象灰度直方图”,其余缺省

IDC_HISTOGRAM

静态文本框

General页的Caption为空,

Style页的Simple,其余缺省

DC_GRAYLEVEL

静态文本框

同上

IDOK

按键

缺省

 

其中IDC_HISTOGRAM用于绘制灰度直方图,创建时调整其宽高为172x75IDC_GRAYLEVEL用于绘制直方图横轴灰度颜色,创建时调整其宽高为172x8

运行ClassWizard为此对话框模板创建一个新类CHistogramDialog,向此类加入下列私有成员变量:

        LONG m_naHistogram[256];   

//  m_naHistogram[i]用于存储灰度为i的像素在图象中出现的次数

        LONG m_nMaxCount;       //  m_naHistogram[i]的最大值

将此类的构造函数原型改为

        CHistogramDialog(CDib* pDib, CWnd* pParent = NULL);

并在构造函数中加入代码

CHistogramDialog::CHistogramDialog(CDib* pDib, CWnd* pParent /*=NULL*/)

    : CDialog(CHistogramDialog::IDD, pParent)

{

    LPBYTE      pData;         //  位图数据指针

    UINT        BytesPerRow;   //  每行字节数

    LONG        nHeight, nWidth;    //  位图高度

    LONG        w, h;

 

    pData       = pDib->GetImageDataPtr();

    BytesPerRow = pDib->GetBytesPerRow();

    nHeight         = pDib->GetBmpHeight();

    nWidth      = pDib->GetBmpWidth();

 

    memset( (void*)m_naHistogram, 0, 256*sizeof(LONG) );

    for( h=0; h<nHeight; h++ )

        for( w=0; w<nWidth; w++ )

            m_naHistogram[ *(pData+h*BytesPerRow+w) ]++;

 

    m_nMaxCount = 0;

    for( i=0; i<256; i++ )

    {

        if( m_naHistogram[i]>m_nMaxCount )

            m_nMaxCount = m_naHistogram[i];

    }

}

 

void CHistogramDialog::OnPaint()

{

    CPaintDC    dc(this); // device context for painting

    CRect       rect;

    CWnd*       pWnd;

    int         i, j;

 

    pWnd = GetDlgItem( IDC_HISTOGRAM );

    pWnd->GetWindowRect( &rect );

    ScreenToClient( &rect );

    pWnd->Invalidate();

    pWnd->UpdateWindow();

    dc.Rectangle( &rect );

    rect.DeflateRect( 1, 1 );

    //  绘制直方图

    for( i=0; i<256; i++ )

    {

        dc.MoveTo( rect.left + i, rect.bottom );

        dc.LineTo( rect.left+i, rect.top * m_naHistogram[i]/m_nMaxCount );

    }

    pWnd = GetDlgItem( IDC_GRAYLEVEL );

    pWnd->GetWindowRect( &rect );

    ScreenToClient( &rect );

    pWnd->Invalidate();

    pWnd->UpdateWindow();

    dc.Rectangle( &rect );

    rect.DeflateRect( 1, 1 );

    //  绘制横坐标

    for( i=0; i<256; i++ )

        for( j=rect.top; j<rect.bottom; j++ )

            dc.SetPixel( rect.left+i, j, RGB(i,i,i) );

    // Do not call CDialog::OnPaint() for painting messages

}

 


图二就是图三所示图象的灰度直方图。

图二 灰度直方图(源图象如图三)

 

三、模板卷积

    数字图象处理中常用模板卷积来实现滤波器来对图象进行滤波处理。其步骤为:

1.       将一个矩形模板(实际上是一个二维矩阵)在图象中漫游,并将模板中心与图象中某个像素位置重合;

2.       将模板中的系数与图象中对应位置的像素相乘;

3.       将所有乘积相加;

4.       将和赋给图象中对应模板中心位置的像素。

 

需要注意的是,我们在计算机中表示的灰度图象数据通常都是正数,然而通过模板卷积运算后可能会出现负数,所以我们还要将图象数据恢复到以前的灰度空间。

下面是我们实现的模板卷积函数(为简单起见,我们没有考虑图象边缘像素)

 

/*  卷积模板大小可调节滤波器

    参数:

            mask        模板指针

            wndWidth    模板的宽度

            wndHeight   模板的高度

*/

void CImageSegmentation::AdjustableFilter(int *mask, int wndWidth, int wndHeight)

{

    LONG        w, h;

    INT         i, j;

    INT         halfHeight, halfWidth;  //  滤波器窗口半高宽

    LPBYTE      pData, pDataTemp;   //  位图数据指针

    LPINT       pBuffer, pBufferTemp;   //  位图数据缓冲区指针

    LPINT       pMask;

    //  确保滤波器窗口高宽均为奇数且大于一

    ASSERT( wndHeight%2 && wndWidth%2 && wndHeight>1 && wndWidth>1 );

    halfHeight  = ( wndHeight-1 )/2;

    halfWidth   = ( wndWidth-1)/2;

    pDataTemp = pData = m_lpImageData;

    //  分配缓冲区

    pBufferTemp = pBuffer = (LPINT)new INT[ m_nBytesPerRow * m_nHeight ];

    memset( pBuffer, 0, m_nBytesPerRow*m_nHeight*sizeof(INT) );

    //  模板卷积

    for( h=1; h<m_nHeight-1; h++ )

    {

        for( w=1; w<m_nWidth-1; w++ )

        {

            pMask = mask;

            for( i=-halfHeight; i<=halfHeight; i++ )

                for( j=-halfWidth; j<=halfWidth; j++ )

                    *(pBufferTemp+w) += *( pData + (h+i)*m_nBytesPerRow + w+j ) * (*pMask++);

            *(pBufferTemp+w) /= *pMask;

        }

        pBufferTemp += m_nBytesPerRow;

    }

 

    //  灰度范围变化

    SpatialTransform( pBuffer );

 

    //  将缓冲区数据拷贝到位图数据区中

    pBufferTemp = pBuffer;

    for( i=0; i<m_nHeight; i++ )

        for( j=0; j<m_nBytesPerRow; j++ )

            *pData++ = (BYTE)*pBufferTemp++;

       

    delete []pBuffer;

}

//  灰度范围变换

void CImageSegmentation::SpatialTransform( INT* pBuffer )

{

    LONG        w, h;

    INT     maxGrayLevel, minGrayLevel;

 

    //  统计图象的灰度区间

    maxGrayLevel = minGrayLevel = 0;

    for( h=0; h<m_nHeight; h++ )

        for( w=0; w<m_nWidth; w++ )

        {

            if( maxGrayLevel < *(pBuffer+h*m_nBytesPerRow+w) )

                maxGrayLevel = *(pBuffer+h*m_nBytesPerRow+w);

            if( minGrayLevel > *(pBuffer+h*m_nBytesPerRow+w) )

                minGrayLevel = *(pBuffer+h*m_nBytesPerRow+w);                       }

    //  灰度空间变换

    for( h=0; h<m_nHeight; h++ )

        for( w=0; w<m_nWidth; w++ )

*(pBuffer+h*m_nBytesPerRow+w)= 255*( *(pBuffer+h*m_nBytesPerRow+w)-minGrayLevel ) /

                    (maxGrayLevel-minGrayLevel );

}

我们使用模板mask[10] = { -1,-1,-1,-1,-1,-1,-1,-1,8,1}可将图三变换为图四。

图象处理是一门复杂的学科,而作者水平有限,希望以上程序能给您一点启发,起到抛砖引玉的作用。

 

 

 

 

 

图三  灰度图象

 

 


  图四  模板卷积后的图象

参考文献

1. 章毓晋. 1999. 图像处理和分析. 清华大学出版社

2. 孙家广,扬长贵. 1995. 计算机图形学(新版). 清华大学出版社

3. Clayton Walnum. 1995. Windows 95 Games SDK Strategy Guide. Que Corporation

4. Microsoft.1998. MSDN Library Visual Studio 6.0. Microsoft Corporation

 

 

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089