最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

数字图像处理源程序及详解之灰度直方图

来源:动视网 责编:小OO 时间:2025-09-25 21:36:32
文档

数字图像处理源程序及详解之灰度直方图

图像的点运算 图像的点运算是图像处理中相对简单的技术,它主要用于改变一幅图像的灰度分布范围。点运算通过一个变换函数将图像的像素一一转换,最终构成一幅新的图像。由于操作对象是图像的一个个像素,故得名为“点运算”。点运算的最大特点是输出像素值只与当前输入像素值有关。点运算的图像处理过程可以用以下公式表示:g(x,y)=T[f(x,y)]其中f(x,y)表示输入图像,g(x,y)表示处理后的图像。函数T是对f的一种变换操作,在这里它表示灰度变换公式。可以看到,对于点运算而言,最重要的是确定灰度变换公
推荐度:
导读图像的点运算 图像的点运算是图像处理中相对简单的技术,它主要用于改变一幅图像的灰度分布范围。点运算通过一个变换函数将图像的像素一一转换,最终构成一幅新的图像。由于操作对象是图像的一个个像素,故得名为“点运算”。点运算的最大特点是输出像素值只与当前输入像素值有关。点运算的图像处理过程可以用以下公式表示:g(x,y)=T[f(x,y)]其中f(x,y)表示输入图像,g(x,y)表示处理后的图像。函数T是对f的一种变换操作,在这里它表示灰度变换公式。可以看到,对于点运算而言,最重要的是确定灰度变换公
图像的点运算 

  

图像的点运算是图像处理中相对简单的技术,它主要用于改变一幅图像的灰度分布范围。点运算通过一个变换函数将图 像的像素一一转换,最终构成一幅新的图像。由于操作对象是图像的一个个像素,故得名为“点运算”。点运算的最大特点是输出像素值只与当前输入像素值有关。 点运算的图像处理过程可以用以下公式表示:

g(x, y)=T[f(x, y)]

其中f(x, y)表示输入图像,g(x, y)表示处理后的图像。函数T是对f的一种变换操作,在这里它表示灰度变换公式。可以看到,对于点运算而言,最重要的是确定灰度变换公式。变换公式一旦确 定,点运算对于图像的处理效果就确定了。

本章研究的主要内容包括灰度直方图、线性变换、非线性变换、阈值变换、灰度拉伸及灰度均衡等。若无特别说明,本 章的点运算函数所针对的待处理对象即为8位灰度图。

■ 本章学习地图

◆ 学会利用灰度直方图查看图像信息

◆ 了解各种灰度变换公式

◆ 掌握各种灰度变换的实现方法

◆ 进一步了解各种点运算对于图像效果的影响

9.1  灰度直方图

本节介绍灰度直方图的相关概念和实现原理,它是提取图像信息的重要工具之一。

9.1.1  灰度直方图

任何一幅图像都包含着丰富的图像信息,对于图像处理而言,如何提取这些信息并找出其中的特征就显得十分关键。 灰度直方图直观地显示了图像灰度分布的情况,这些信息在图像灰度变换等处理过程中显得十分重要。在本章随后的内容中,也会经常通过直方图来分析变换后的图 像效果。

图9-1显示一幅灰度图及它所对应的灰度直方图。 可以看到,灰度直方图是一个二维图。从数学上来说,它描绘了图像各灰度值的统计特性,显示了各个灰度级出现的次数或概率。从图形上来说,其横坐标表示图像 的灰度值,取值范围是0到255。其纵坐标则通过高度来表示出现次数的多少或者概率的高低。在本章中,纵坐标表示像素出现的次数,最大值为该图像在0至 255阶灰度上分布像素出现次数的最大值。

图9-1  利用灰度直方图显示图像灰度分布

需要说明的是,如果没有特别指出,在本章后续内容 中的所有变换都是基于图9-1中左侧的图像进行的。

接下来分析直方图的作用。图9-2所示的4幅图像 都是根据同一幅图像进行基本灰度特征处理后获得的,分别是高亮度、低亮度、高对比度、低对比度4种类型的图像。每幅图像右侧都显示了对应的直方图。仔细观 察可以发现如下特征。

图9-2  4种基本图像及对应的直方图

高亮度图像的直方图组成集中在灰度高的一侧。8位 灰度图能表示256种灰度,也就是灰度取值范围为0至255。其中0表示黑色,255表示白色。对于高亮度图像,整个画面的颜色偏亮,故灰度直方图偏向灰 度高的一侧。相反,低亮度图像的直方图则偏向灰度较低的一侧。

在高对比度的图像中,直方图的覆盖范围很广。图像 在任意一段灰度范围中都有一定的像素数量。同时,高对比图像的灰度分布相比其他图像而言较为均匀,整个直方图显得比较平滑。而低对比度图像的灰度则主要分 布在中间狭窄的区域中,图像就像被冲淡了一样。

9.1.2  基本原理

灰度直方图的基本思想是统计。对于拥有256种灰度的图像,灰度值为k的像素个数由一个离散函 数确定:

其中nk表 示当前图像灰度值为k的像素个数,则对应的出现概率可以使用如下公式表示:

,显然有成 立。

其中n表示图像像素个数的总和,可 以用图像宽度与高度的乘积来表示。

本章灰度直方图的坐标系为。 横坐标表示输入灰度值k,而纵坐标表示对应灰度值的统计个数nk。可见,绘制直方图最重要的是确定灰 度值为k的像素个数。直方图在绘制时采用相对高度,即纵坐标的最大值为ymax=max(n0,n1,n2……,n254,n255), 如果ymax的绘制高度为1,灰度值k的绘制高度为。

9.1.3  编程实现

1.效果预览

本节通过对话框实现灰度直方图的显示,其效果见图 9-3所示。

图9-3  灰度直方图对话框

灰度直方图对话框实现了如下功 能。

l          灰度直方图的显示。

l          允许用户修改显示灰度的上限和下限。对于一些图像而言,可能某个像素值的计数远远大于其他像素的个数,这样往往看不清其他灰度的分布。例如白色背景的图 片,拥有灰度值255的像素个数相对较多。该对话框提供了一个灰度区间显示的功能,如图9-4所示。

图9-4  灰度直方图的区间显示

可以看到,改变显示灰度的范围后,用户能够更加清 楚地了解某一区域的灰度分布特征。

l          允许用户通过鼠标操作来改变显示灰度的上限和下限。方法很简单,只需要拖动对话框上两侧的蓝色虚线即可。

l          显示当前鼠标所在位置的灰度值及出现的几率。

2.构建MagicHouse

MagicHouse是贯穿本书第3篇的重点实 例。它是一款以图像处理为主,综合图像浏览等多种功能的实用软件。MagicHouse支持多种常见图片格式,并实现了以下功能。

l          图像的浏览

l          图像的特效显示

l          图像的点运算

l          图像的几何变换

l          图像的增强

l          图像的滤镜效果

本章将完成MagicHouse框架的搭建工作并 在其基础上添加点运算的功能,随后的3章内容都是在已有的MagicHouse框架上进行功能的完善和补充的。

MagicHouse框架是在第8章实例 GraphShower的基础上改进的,它继承了GraphShower的框架结构和图像浏览、特效显示等功能,并新增了如下功能。

1)运行模式

添加了“运行模式”菜单,提供两种运行模式:图片 浏览模式,该模式功能与GraphShower完全相同,提供图像的浏览功能;编辑模式,这个模式允许对图像进行各种处理,本章及后面3章的功能都是在该 模式下实现的。编辑模式下的图像会被锁定,不能进行图像的浏览。

2)JPEG解码器

MagicHouse使用了第4章编写的JPEG 解码器完成JPEG文件的解码工作。用户只需要将第4章的对应文件添加到MagicHouse的工程下即可。

3)Dib封装类

MagicHouse使用第5章介绍的MyDib 类获取Bmp文件的像素信息,MagicHouse支持的图像为8位灰度图与24位、32位彩色图像。

4)预览对话框

预览对话框用于显示图像处理后的效果图。它与第5 章中介绍的CPreViewDlg类很类似,不同的是这里采用GDI+显示图像。

5)统一的图像处理接口

MagicHouse的核心是 CMagicHouseView类,它不但提供了图像的显示和处理功能,还提供了图像的处理接口。下面是CMagicHouseView类中关于图像处理 十分重要的成员变量。

public:

BYTE*   m_pImageBuffer;             // 编辑图像原始像素数组

BYTE*   m_pImageTempBuffer;         // 处理后的像素数组

UINT        m_nPicWidth;                    // 当前编辑图像宽度

UINT        m_nPicHeight;               // 当前编辑图像高度

UINT        m_nTempWidth;               // 处理后图像的宽度

UINT        m_nTempHeight;              // 处理后图像的高度

在编辑模式下,所有图像都会保存在一个线性数组 中,且图像的每一个像素都是以32位的形式保存的。图像处理函数可以通过m_pImageBuffer指针获取原始图像信息,然后将处理后的图像保存在另 一个线性数组中。处理后的图像信息可以通过m_pImageTempBuffer指针访问。最后通过预览对话框显示处理后的图像效果。后续的图像处理函数 会经常使用这6个变量。

MagicHouse拥有良好的可扩展性,它对底 层的解析过程进行了良好的封装,并采用统一的接口对图像进行处理,它不仅是一款数字图像处理工具,更是一款理想的数字图像算法试验平台,读者可以在其基础 上扩展更多的图像处理功能。由于篇幅有限,这里不再给出MagicHouse框架的代码,读者可到指定网站下载。

3.概要设计

下面开始完成灰度直方图对话框的设计。

启动MagicHouse项目文件,打开“资源视 图”,添加ID为IDD_HISTOGRAM的对话框资源并按照图9-5完成设计。然后为其创建对话框类CHistogramDlg并按照表9-1关联对 应变量。

图9-5  灰度直方图对话框的设计

表9-1  灰度直方图对话框资源与变量的关系

编    号资 源 类 型资源ID关联变量类型关联变量名称
1图片控件IDC_HISTOGRAMCStaticm_stiHistogram
2文本框IDC_LIMIT_LOWERintm_nLimitLow
3文本框IDC_LIMIT_UPintm_nLimitUp
4静态文本IDC_STATIC_GRAYintM_nGray
5静态文本IDC_STATIC_PERfloatM_dPer
4.代码分析

下面结合灰度直方图的基本原理一步一步剖析对话框 的实现过程。

1)各级灰度数量的统计

统计数据是绘制灰度直方图的依据。该对话框在初始 化时,也就是在OnInitDialog函数中完成统计工作。正如前面介绍的一样,MagicHouse将图像保存在一个线性数组中,图像的每个像素都统 一采用32位形式存储,故8位灰度图的一个像素也会占用32位。为方便统计,对话框默认当前处理图像为灰度图,即R=G=B,故每次只需要统计其中一种颜 色的数量。

2)灰度直方图重绘过程

绘制工作主要由Refresh函数完成,它完成了 以下功能。

l          双缓存的创建

以双缓存模式绘制直方图,防止闪烁。

l          绘制直方图的坐标系和刻度

l          查找所有灰度中最多的出现次数

该次数会显示在Y轴的旁边,拥有该 次数灰度值的直方图最高。

l          以相对高度绘制直方图

其余灰度值的高度由其出现次数与最大出现次数的比 值决定。

l          利用鼠标操作改变显示灰度的上下限

灰度上下限在对话框中以两条蓝色的虚线表示,用户 可以通过鼠标操作改变两条虚线的位置来改变显示灰度的上下限。

用户在单击鼠标左键时程序会根据当前鼠标的位置, 判断鼠标是否在上下限虚线的范围内。如果在上限虚线的范围中,则将m_nIsDraging变量赋值为DT_UP,如果在下限虚线的范围中,则赋值为 DT_LOW,否则赋值为DT_NULL。

用户在移动鼠标时如果m_nIsDraging不 为DT_NULL,则表示用户此时正在拖动上限或者下限的虚线,此时就需要更改上下限的值,并将其限定在合法的取值范围中。

5.实例代码清单

下面列出HistogramDlg.h的代码清 单:

#pragma once

#include "afxwin.h"

// CHistogramDlg 对话框

class CHistogramDlg : public CDialog

{

    DECLARE_MESSAGE_MAP()

    DECLARE_DYNAMIC(CHistogramDlg)

public:

    CStatic     m_stiHistogram;             // 直方图显示区域

    int     m_nLimitLow;                    // 显示灰度的下限

    int     m_nLimitUp;                 // 显示灰度的上限

    long        m_lCounts[256];             // 各级灰度出现的个数

    long        m_nPixelCount;              // 图像像素总数

    CPoint  m_psMove;                   // 记录拖动时的鼠标位置

    int     m_nIsDraging;               // 鼠标是否正在拖动

    int     m_nGray;                        // 当前鼠标位置的灰度级数

    float   m_dPer;                     // 出现概率

    CHistogramDlg(CWnd* pParent = NULL);        // 标准构造函数

    virtual ~CHistogramDlg();

    afx_msg void OnEnChangeLimitLower();        // 灰度下限改变的响应函数

    afx_msg void OnEnChangeLimitUp();       // 灰度上限改变的响应函数

    afx_msg void OnPaint();                 // 绘制对话框

    virtual BOOL OnInitDialog();                // 对话框初始化时计算各级灰度数量

    afx_msg void OnMouseMove(UINT nFlags, CPoint point);

                                            // 鼠标移动时响应拖动动作

    afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);

                                            // 拖动时改变鼠标光标

    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

                                        // 鼠标按下时判断是否在灰度上下限直线范围中

    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

                                            // 释放鼠标的响应函数

    // 对话框数据

    enum { IDD = IDD_HISTOGRAM };

protected:

    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

private:

    void Refresh(void);                 // 刷新直方图

    // 拖动枚举

    enum DragingType

    {

        DT_NULL,                            // 无拖动

        DT_LOW,                         // 拖动下限

        DT_UP                           // 拖动上限

    };

};

下面列出HistogramDlg.h的核心代码清单:

// HistogramDlg.cpp : 实现文件

#include "stdafx.h"

#include "MagicHouse.h"

#include "HistogramDlg.h"

#include "Mainfrm.h"

#include "MagicHouseDoc.h"

#include "MagicHouseView.h"

// CHistogramDlg 消息处理程序

/***************************************************************************

*   作用: 对话框初始化时计算各级灰度数量

***************************************************************************/

BOOL CHistogramDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

    CMainFrame* pMain = (CMainFrame*)AfxGetMainWnd();

    CMagicHouseView* pView = (CMagicHouseView*)pMain->GetActiveView();

    for (UINT i = 0; i < pView->m_nPicWidth * pView->m_nPicHeight; i++)

    {

        int value = pView->m_pImageTempBuffer[i * 4];

        m_lCounts[value]++;

    }

    // 计算像素总个数

    m_nPixelCount = pView->m_nPicWidth * pView->m_nPicHeight;

    return TRUE;  // return TRUE unless you set the focus to a control

    // 异常:OCX 属性页应返回 FALSE

}

/***************************************************************************

*   作用: 刷新直方图

*   备注: 双缓存绘制方法

***************************************************************************/

void CHistogramDlg::Refresh()

{

    CDC* pDC = m_stiHistogram.GetDC();

    CRect rect;

    CDC memDC;

    CBitmap MemBitmap;

    // 获取绘图区域

    m_stiHistogram.GetClientRect(rect);

    // 设备描述表初始化

    memDC.CreateCompatibleDC(NULL);

    // 建立与屏幕显示兼容的内存显示设备

    MemBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());

    // 选取空白位图

    memDC.SelectObject(MemBitmap);

    memDC.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(255,255,255));

    Graphics graph(memDC.GetSafeHdc());

    // 使用白色背景

    graph.FillRectangles(&SolidBrush(Color::White), 

&Rect(0, 0, rect.Width(), rect.Height()), 1);

    // 绘制y轴

    graph.DrawLine(&Pen(Color::Black), 10, 10, 10, 280);

    graph.DrawLine(&Pen(Color::Black), 10, 10, 5, 15);

    graph.DrawLine(&Pen(Color::Black), 10, 10, 15, 15);

    // 绘制x轴

    graph.DrawLine(&Pen(Color::Black), 10, 280, 290, 280);

    graph.DrawLine(&Pen(Color::Black), 290, 280, 285, 285);

    graph.DrawLine(&Pen(Color::Black), 290, 280, 285, 275);

    // 绘制坐标原点

    CString strNum;

    Font font(L"宋体", 10);

    strNum = L"0";

    graph.DrawString(strNum, -1, &font, 

        PointF(8, 290), &SolidBrush(Color::Black));

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

    {

        if (i % 50 == 0)

            graph.DrawLine(&Pen(Color::Black), 10 + i, 280, 10 + i, 286);

        else if (i % 10 == 0)

            graph.DrawLine(&Pen(Color::Black), 10 + i, 280, 10 + i, 283);

    }

    // 绘制x轴刻度

    strNum = L"50";

    graph.DrawString(strNum, -1, &font, PointF(53, 290),

&SolidBrush(Color::Black));

    strNum = L"100";

    graph.DrawString(strNum, -1, &font, PointF(100, 290),

&SolidBrush(Color::Black));

    strNum = L"150";

    graph.DrawString(strNum, -1, &font, PointF(150, 290),

&SolidBrush(Color::Black));

    strNum = L"200";

    graph.DrawString(strNum, -1, &font, 

                     PointF(200, 290), &SolidBrush(Color::Black));

    strNum = L"255";

    graph.DrawString(strNum, -1, &font, PointF(255, 290),

&SolidBrush(Color::Black));

    // 绘制当前灰度区域

    Pen pen(Color::Blue);

    pen.SetDashStyle(DashStyleDash);

    graph.DrawLine(&pen, 10 + m_nLimitLow, 280, 10 + m_nLimitLow, 10);

    graph.DrawLine(&pen, 10 + m_nLimitUp, 280, 10 + m_nLimitUp, 10);

    long lMax = 0;

    REAL dHeight = 0.0;

    // 查找最大值

    for (int i = m_nLimitLow; i <= m_nLimitUp; i++)

        lMax = max(lMax, m_lCounts[i]);

    // y轴刻度

    strNum.Format(L"%d", lMax);

    graph.DrawString(strNum, -1, &font, 

                   PointF(10, 25), &SolidBrush(Color::Black));

    // 绘制柱状图

    for (int i = m_nLimitLow; i <= m_nLimitUp; i++)

    {

        dHeight = (REAL)(m_lCounts[i]) / lMax * 250;

        graph.DrawLine(&Pen(Color::Gray), i + 10.0f, 280.0f, i + 10.0f,

280 - dHeight);

    }

    // 复制内存画布内容

    pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);

    m_stiHistogram.ReleaseDC(pDC);

}

/***************************************************************************

*   作用: 鼠标移动时响应拖动动作

***************************************************************************/

void CHistogramDlg::OnMouseMove(UINT nFlags, CPoint point)

{

    CRect rect;

    m_stiHistogram.GetWindowRect(rect);

    if ( (nFlags & MK_LBUTTON) && m_nIsDraging )

    {

        // 拖动偏移量

        int offset = point.x - m_psMove.x;

        // 如果拖动的是上限

        if (m_nIsDraging == DT_UP)

        {

            // 如果没有超界

            if ( (offset + m_nLimitUp) <= 255 )

            {

                if ( (offset + m_nLimitUp) >= m_nLimitLow )

                    m_nLimitUp += offset;

                else

                    m_nLimitUp = m_nLimitLow;

            }

            else

                m_nLimitUp = 255;

        }

        else

        {

            // 如果没有超界

            if ( (offset + m_nLimitLow) >= 0 )

            {

                if ( (offset + m_nLimitLow) <= m_nLimitUp )

                    m_nLimitLow += offset;

                else

                    m_nLimitLow = m_nLimitUp;

            }

            else

                m_nLimitLow = 0;

        }

        UpdateData(FALSE);

        Refresh();

        m_psMove = point;

    }

    else

        m_nIsDraging = DT_NULL;

    ClientToScreen(&point);

    // 鼠标当前所在灰度位置,如果不在0~255之间则表示鼠标不在指定区域内

    int x = point.x - rect.left - 10;

    if (abs(x - m_nLimitUp) > 3 && abs(x - m_nLimitLow) > 3)

        m_nIsDraging = DT_NULL;

    // 如果鼠标在直方图区域中

    if (rect.PtInRect(point))

    {

        if (x >= m_nLimitLow && x <= m_nLimitUp)

        {

            m_nGray = x;

            m_dPer = float(m_lCounts[x]) / m_nPixelCount * 100;

        }

        UpdateData(FALSE);

    }

}

/***************************************************************************

*   作用: 鼠标按下时判断是否在灰度上下限直线范围中

***************************************************************************/

void CHistogramDlg::OnLButtonDown(UINT nFlags, CPoint point)

{

    CRect rect;

    CPoint oldPoint = point;

    m_stiHistogram.GetWindowRect(rect);

    ClientToScreen(&point);

    int x = point.x - rect.left - 10;

    if (abs(x - m_nLimitUp) <= 3)

    {

        m_psMove = oldPoint;

        m_nIsDraging = DT_UP;

    }

    else if (abs(x - m_nLimitLow) <= 3)

    {

        m_psMove = oldPoint;

        m_nIsDraging = DT_LOW;

    }

}

/***************************************************************************

*   作用: 释放鼠标的响应函数

***************************************************************************/

void CHistogramDlg::OnLButtonUp(UINT nFlags, CPoint point)

{

    m_nIsDraging = DT_NULL;

}

6.使用该对话框

打开“资源视图”中的菜单资源 IDR_MAINFRAME,添加一个新菜单栏“点运算”,并在其下拉菜单中添加一个新栏,如图9-6所示。然后将其Caption属性设为“灰度直方 图”,其ID为ID_POINT_HISTOGRAM。

图9-6  在菜单资源中添加新项

为其添加命令响应函数,具体内容如下:

void CMagicHouseView::OnPointHistogram()

{

    if (!m_bIsEditMode || m_nPos == -1)

    {

        MessageBox(L"请先打开图像文件,然后选择编辑模式!");

        return;

    }

    CHistogramDlg dlg;

    ResetImage();   // 该函数用于清空m_pImageTempBuffer数组

    dlg.DoModal();

}

文档

数字图像处理源程序及详解之灰度直方图

图像的点运算 图像的点运算是图像处理中相对简单的技术,它主要用于改变一幅图像的灰度分布范围。点运算通过一个变换函数将图像的像素一一转换,最终构成一幅新的图像。由于操作对象是图像的一个个像素,故得名为“点运算”。点运算的最大特点是输出像素值只与当前输入像素值有关。点运算的图像处理过程可以用以下公式表示:g(x,y)=T[f(x,y)]其中f(x,y)表示输入图像,g(x,y)表示处理后的图像。函数T是对f的一种变换操作,在这里它表示灰度变换公式。可以看到,对于点运算而言,最重要的是确定灰度变换公
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top