VC 图像处理学习教程

zenggongqun

贡献于2012-07-12

字数:0 关键词: C/C++开发

VCVCVCVC数字图像处理学习教程数字图像处理学习教程数字图像处理学习教程数字图像处理学习教程 作者:刘涛作者:刘涛作者:刘涛作者:刘涛 2007-102007-102007-102007-10 VC数字 图像处 理编程 讲座之 一 前言 数字 图像处理技术 与理论 是计算 机应用 的一个重 要领域, 许多工程应用 都涉及 到图 像处理, 一直有一 个强烈 的愿望, 想系统的 写一个关 于数字 图 像处理的 讲座 ,由 于工作 学习很 忙,时 至今日 才得以 实现。 “图”是物 体透射光 或反射光 的 分布, “像”是人的 视觉 系统对图 的接收在 大 脑中形 成的印 象或认 识。 图 像是 两者的 结合。 人 类获取 外 界信息 是靠听 觉、 视觉 、触觉 、 嗅觉、 味觉等 ,但绝 大部 分(约 80%左右 )来自 视觉 所接收的 图像信 息。 图像处理 就 是对图 像信息 进行加 工处理, 以满 足人的 视觉心 理 和实 际应用的 需要。简 单的说, 依靠计算 机对图 像进行各 种目的的 处理我们 就称之为 数字图 像处理。 早期的 数字图像处理的 目 的是以人为 对象, 为了 满足人的 视觉 效果而改善 图像的 质量, 处理过程中 输入的 是质量差 的图像, 输出的 是质量好的 图像, 常用的 图像处理方法有 图 像增 强、复原 等。随着 计算 机技术的 发展,有 一类图像处理 是 以机器为 对象,处理的 目的 是使机器能 够自 动识别目 标,这称 之为图 像的识别 ,因为这其中 要牵 涉到一 些复杂 的模式识 别的 理论,所以 我们后 续的讲座 只讨论 其中最基本 的内容 。由于 在许多实 际应用的 编程中往往都 要 涉及到数字 图像处理, 涉及 到其中 的一些 算法, 这也 是许多 编程爱 好者感兴 趣的 一个内 容,我们 这个讲座 就 是讨论 如何利 用微软的 Visual C++开发工 具来实 现一 些常用 的数字 图像处理算 法 , 论述了 图像处理 的 理论, 同时给出了 VC实现的 源代 码。本 讲座主 要的内 容分为 基础篇 、中级 篇和高级 篇, 具体包 含的主 要内容 有: 1.图像文 件的 格式; 2.图像编 程的 基础 -操作 调色板 ; 3.图像数据 的 读取、 存储和 显示、 如何获取 图像的 尺寸 等; 4.利用 图像来美 化界面; 5.图像的 基本 操作: 图像移 动、图 像旋转、 图像镜 像、 图像的 缩放、 图像的 剪切板 操作; 6.图像显 示的 各种特技效 果 ; 7.图像的 基本 处理: 图像的 二值化 、 图像的 亮度和 对比度 的调整 、 图像的 边缘增 强、 如何得到 图像的 直方图 、 图像直 方图的 修正、 图像的 平滑 、 图像的 锐化等 、图 像的伪 彩色、 彩色图 像转换为 黑白 图像、 物体边缘的 搜索 等等 ; 8.二值图 像的 处理: 腐蚀、 膨胀、 细化、 距离变换等 ; 9.图像分析 : 直线、 圆、特定 物体的 识别 ; 10.JEPG、GIF、PCX等格式文 件相 关操 作; 11.图 像文件格式 的 转换; 12.图 像的常 用变换: 付利 叶变换、 DCT变换、 沃尔 什变换等 ; 13.AVI视频流的 操 作; 图像处理技 术 博大精深 ,不仅需 要有很 强的数学 功底,还需 要熟练掌 握一 门计算机语言 ,在 当前流行 的语言 中,我个人觉的 Visual C++这个开 发平 台是图 像开发人员 的 首选工 具。本 讲座只 是起到 抛砖引 玉的作 用,希 望和广 大读者 共同交流。 VC数字 图像处 理编程 讲座之 二 图像的文件格 式 要利 用计算机对数字 化图 像进行 处理, 首先要对图 像的文 件格式要 有清楚 的认识, 因为我们 前面说过, 自然 界的图像以 模拟信 号的形式存在 , 在 用计算 机进行 处理以 前, 首先要 数字化, 比如 摄像头 ( CCD)摄取 的信 号在送往计 算 机处理前 ,一 般情况下 要经过数 模转换 , 这个任务 常常 由图像采 集卡 完成,它 的输出一 般为裸图 的形式; 如果用 户想要生 成目标图 像文件, 必须根据 文件的 格式做相 应的处理。 随着 科技的发展 ,数码 像机、 数码摄 像机已 经进入寻 常百姓家 ,我们 可以利 用这些设 备作为图 像处理系 统的输入设 备来为 后续的 图像处理提供 信 息源。无论 是什么 设备,它 总是 提供按一 定的 图像文 件格式来提 供信 息, 比较常 用的有 BMP格式、 JPEG 格式、 GIF 格式等 等, 所以我们 在进行 图像处理 以 前,首 先要对图 像的 格式要 有清晰 的认 识,只 有在此基础 上才可 以 进行进一 步的 开发处理。 在讲述图 像文 件格式前 ,先对图 像作一 个简单的 分类。除 了最简单的 图像外 ,所有 的图像都有 颜色, 而单色图 像则是 带有颜色的 图像中 比较简 单 的格式, 它 一般由 黑色区 域和白 色区域组成 , 可以用 一个比 特表示一 个像素 , “1”表示黑 色, “0”表示白 色, 当然也 可以倒 过来表 示,这种 图像称 之为 二值图 像。我们 也可以用 8个比 特(一个字 节)表 示一个像素 ,相 当于把黑 和白等分为 256个级 别, “0”表示为 黑, “255”表示为 白,该 字节的数值 表示相 应像素 值的 灰度值或亮 度 值, 数值越接近 “0”, 对应像素 点越黑 , 相反, 则对应像素 点 越白, 此种 图像我们 一般 称之为 灰度 图像。 单色图 像和 灰 度图 像又统 称为黑 白图像, 与之对应 存在 着彩色图 像, 这种 图像要 复杂一 些,表 示图像时 ,常 用的图 像彩色模式有 RGB 模式、 CMYK模式和 HIS 模 式, 一般情 况下 我们只 使用 RGB 模式, R对应红 色, G对应绿色 , B对应蓝 色, 它们 统称为 三基色, 这三中 色彩的 不 同搭配, 就可以 搭配成各 种 现 实 中的 色彩, 此时彩色图 像的 每一 个像素 都需要 3个样 本组成的 一组数据 表 示,其中 每个样 本用 于表示该 像素 的一个基本 颜色。 对于 现存的 所有的 图像文 件格式 , 我们在 这里主 要介绍 BMP 图像文 件格式 , 并且 文件里 的图像数据 是 未压缩 的, 因 为图像的 数字 化处理主 要是 对 图像中 的各 个像素 进行相 应的处理 , 而 未压 缩的 BMP 图像中 的像素 数值正 好与 实际要 处理的 数字图 像相对应 , 这种 格式的 文件最 合适我们 对之进 行 数 字化处理 。 请 读者记 住, 压缩 过的图 像是无法直 接进行 数字 化处理的 ,如 JPEG、GIF 等格式的 文 件, 此时 首先要 对图像文 件解压 缩, 这就 要涉及 到 一 些比 较复杂 的压缩 算法。 后 续章节 中我们 将针对特殊 的 文件格式如 何转换为 BMP格式的 文件问 题作 专门的 论述, 经过转 换 , 我们 就可以 利用得到 的 未 压缩的 BMP文件格式 进行 后续 处理。 对于 JPEG、GIF 等格式, 由 于涉及 到压缩 算法, 这要求 读者掌 握一定 的信息 论方面的 知识 ,如果 展开的 话,可 以写 一本书 ,限于 篇幅原 因,我们 只作 一般性的 讲解, 有兴 趣的朋 友可以 参考相 关书籍 资料。 一、 BMP BMP BMP BMP 文件结构 1. BMP 文件组成 BMP 文件由 文件头 、 位图 信息 头、 颜色信 息 和图形 数据四 部分组成 。 文 件头主 要包含 文件的 大小、 文 件类型 、 图 像数据 偏离文 件头的 长度等 信息 ; 位图 信息头包 含图象的 尺寸信息 、图像用 几个比 特数值来表 示一个像素 、 图像是否 压缩、图 像所用的 颜色数等 信息。 颜色信息 包含图像所用 到 的颜色 表, 显 示图像时 需用 到这个颜色 表 来生成调 色板 , 但如 果图像为 真彩色 , 既图 像的每个像 素用 24个比 特来表 示, 文件中 就没 有这一 块信息 , 也就不 需 要 操作 调 色 板。 文件 中的 数 据块 表示 图 像 的相 应 的像 素值 , 需要 注意 的 是: 图像 的像 素 值 在文 件 中的 存 放 顺序为 从 左到 右 , 从下到 上 , 也就是 说,在 BMP 文件中 首先存放的 是图 像的最后 一行像素 ,最后 才存储图 像的第一 行像素 ,但对与 同一行的 像素,则 是按照 先左边后 右边的的 顺序存储的 ;另 外一个 需要 读者朋 友关注 的细节 是: 文件存储图 像的 每一 行像素 值时, 如果 存储该 行像素 值所占 的字节 数为 4 的倍数 , 则正常 存储, 否则 ,需要 在后端补 0, 凑足 4的倍数。 2. BMP文件头 BMP 文件头 数据 结构含有 BMP 文件的 类型 、文件大 小和 位图起始 位置等 信息 。其结 构定义 如下 : typedef struct tagBITMAPFILEHEADER { WORD bfType; // 位图 文件的 类型, 必须为 “BM” DWORD bfSize; // 位图 文件的 大小, 以字节 为单位 WORD bfReserved1; // 位图 文件保留 字, 必须为 0 WORD bfReserved2; // 位图 文件保留 字, 必须为 0 DWORD bfOffBits; // 位图 数据的 起始位置, 以 相对于 位图文 件头的 偏移量表 示, 以字节 为单位 3. 位图 信息头 BMP 位图 信息头 数据用 于说明 位图的 尺寸等 信息。 其结构 如下: 注意 :对于 BMP文件格式 , 在处理单 色图 像和 真彩色图 像的 时候, 无论 图象数据 多么 庞大, 都不 对图 象数据 进行任何压 缩 处理, 一般情 况下 , 如 果位图 采用 压缩格式, 那么 16色图 像采用 RLE4 压缩 算法, 256色图 像采用 RLE8 压缩 算法。 4. 颜色表 颜色表 用于 说明位图 中的 颜色, 它有若 干个表 项,每一 个表 项是一个 RGBQUAD类型 的结构 ,定义 一种颜色。 RGBQUAD 结构 的定义 如下 : 颜色表中 RGBQUAD结构 数据的 个数由 BITMAPINFOHEADER 中的 biBitCount项来确 定, 当 biBitCount=1,4,8 时, 分 别有 2,16,256个颜 色表 项,当 biBitCount=24 时, 图像为 真彩色, 图像中 每个像素 的 颜色用 三个字 节表示, 分别 对应 R、G、B值, 图像文 件没有 颜色表 项。位图 信 息 头和 颜色表 组成位图 信息 , BITMAPINFO结构 定义如 下 : 注 意: RGBQUAD 数据 结构中 , 增加 了一个保留 字段 rgbReserved, 它 不代表 任何颜色 , 必须 取固定 的值为 “0”,同时 , RGBQUAD结构 中定 义 的颜色值 中 , 红色 、 绿色和 蓝色的 排列顺序 与一 般真彩色图 像文 件的 颜色数据 排列 顺序恰好相 反 , 既: 若 某个位图 中的 一个像素 点的 颜色的 描述为 “00, 00,ff ,00”,则 表示该 点为红 色,而 不是蓝 色。 5. 位图 数据 }BITMAPFILEHEADER;该 结构占据 14个字 节。 typedef struct tagBITMAPINFOHEADER{ DWORD biSize; // 本结 构所占 用字节 数 LONG biWidth; // 位图 的宽度 ,以像素 为单位 LONG biHeight; // 位图 的高度 ,以像素 为单位 WORD biPlanes; // 目标设 备的 平面数不 清, 必须为 1 WORD biBitCount// 每个像素 所需 的位数, 必 须是 1(双色 ), 4(16色),8(256 色)或24( 真彩色 )之一 DWORD biCompression; // 位图 压缩类型 ,必 须是 0(不压 缩 ),1(BI_RLE8 压缩 类型 )或2(BI_RLE4压缩 类型 )之一 DWORD biSizeImage; // 位图 的大小 ,以字 节为单位 LONG biXPelsPerMeter; // 位图 水平分辨率 , 每米像素 数 LONG biYPelsPerMeter; // 位图 垂直分辨率 , 每米像素 数 DWORD biClrUsed;// 位图 实际使 用的颜色表 中 的颜色数 DWORD biClrImportant;// 位图 显示过程中 重 要的颜色数 }BITMAPINFOHEADER;该 结构占据 40个字 节。 typedef struct tagRGBQUAD { BYTErgbBlue;// 蓝色的 亮度 (值范 围为 0-255) BYTErgbGreen; // 绿色的 亮度 (值范 围为 0-255) BYTErgbRed; // 红色的 亮度 (值范 围为 0-255) BYTErgbReserved;// 保留 ,必须为 0 }RGBQUAD; typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; // 位图 信息头 RGBQUAD bmiColors[1]; // 颜色表 }BITMAPINFO; 位图 数据记 录了位图 的每一 个像素 值或该 对应像素 的 颜色表 的索引 值,图 像记录 顺序是 在扫描 行内是 从左到 右 ,扫描 行之间 是从下 到上。 这种格式 我们 又称为 Bottom_Up位图 , 当 然与 之相对的 还有 Up_Down形式的 位图 , 它 的记 录顺序 是从上到 下的 , 对于 这种 形式的 位图, 也 不 存在压 缩形 式。 位图 的一个像素 值所占 的 字节数:当 biBitCount=1时, 8 个像素占 1 个字 节;当 biBitCount=4时, 2个像素占 1个字 节;当 biBitCount=8时, 1个像素占 1个字 节;当 biBitCount=24 时,1个像素占 3 个字 节,此时 图像为 真彩色图 像。 当图 像不是 为真彩色时 , 图像文 件中包 含颜色表 ,位图 的数据 表示对应 像素 点在 颜色表 中相应的 索引 值, 当为 真彩色时 , 每一个像素 用 三个字 节表示图 像相 应像素 点彩色值 , 每个字 节分别 对应 R、G、B分 量的 值,这时 候图 像文件中 没有 颜色表 。上面我已 经讲过 了 , Windows规定 图像文 件中一 个扫描 行所占 的字节 数必须是 4的倍数 (即以 字为单位 ),不 足的以 0填充 ,图像文 件中 一个扫 描行所占 的字 节数计算 方法: 位图 数据的 大小按 下式计算 (不压 缩情况下 ): 上述是 BMP文件格式 的 说明, 搞清楚 了以上的 结构 ,就可以 正确 的操作 图像文 件,对它 进行 读或写 操作了 。 二、 GIFGIFGIFGIF 图像文件格式 GIF 图象格式 的 全称为 Graphics Interchange Format,从这个 名 字可以 看出, 这种图 像格式主 要是 为了通过网 络 传输图 像而设 计的。 GIF 文 件不 支持 24 位真彩色 图 像,最 多只能 存储 256色的 图像或灰度 图 像; GIF 格式文 件也 无法存储 CMY和HIS模型 的图像数据 ; 另外, GIF 图像文 件 的 各种 数 据区 域 一般 没 有固 定 的数 据 长度 和 存 储 顺序, 所以为 了方 便 程 序寻 找数 据 区, 将数 据区中 的 第一 个 字节 作 为标 志 符; 最后需 要读 者 注意 的 是 GIF 文件存储 图 像数据 是有二 种排列 顺序: 顺序排列 或交叉排 列 。交叉排列 的 方式适合网 络 传 输, 这样一 来允许用 户在 不完全 掌握图 像数据 之前, 获取当 前图 像的轮廓 数据 。 GIF 文件格式 分为 87和89两个版本 , 对于 87这个版本 , 该 文件主 要是有 五个部 分组成 , 它, 们 是按顺序 出现的 : 文 件头块、 逻辑屏 幕描 述块 、 可选择的 调 色板块、 图像数据 块、 最后 是标志 文件结 束的尾块, 该 块总是 取固定 的值 3BH。其中 第一 和第二 两个块用 GIF 图像文 件头 结构描 述: GIF 格式中 的调 色板有 通用调 色板和 局部调 色板之分, 因为 GIF 格式允 许一 个文件中 存储多 个图 像,因 此有这两 种调 色板, 其中通用 调色板 适于 文件中 的所有 图像,而 局部调色板 只适用 于某一 个图像。 格式中的 数据区 域一般分为 四个部 分,图像数据 识 别区域, 局部调色板 数据, 采用压 缩算法 得到 的图象数据 区 域和结 束标志 区域。 在GIF89版本 中, 它 包含 七个部 分, 分别 是 文件头 、 通用 调 色板数据 、 图 像数据 区和 四个补 充数据 区, 它 们主要 是用于 提示程序 如何处理 图 像 的。 三、 JEPGJEPGJEPGJEPG图像文件 JEPG简称 为联合摄影 专 家小组 , 作为 一种技术 , 主 要用于 数字化图 像的 标准编 码, JPEG 主要 采用有 损的压 缩编码 方式, 它比 GIF、BMP 图像 文 件要 复杂的 多, 这 不 是短短的 几页 篇幅可以 将清 楚的, 万 幸 的是, 我 们 可以 通过一 些别的 方法将 该格式转化为 BMP格式 。 读 者 需要 知道的 是在对 JEPG 文件格式 编 码时, 通常需 要分为 以下四 步:颜色转 化、 DCT变换、 量化、 编码 。 以上介 绍了一 些常用的 图像文件, 对比 较复杂的 格式,如 GIF 和JEPG,仅 仅作了极其浮 浅的 介绍,后 文我们 会和它们 作进一步 的接触。 实际应 用中 ,还有 许多图 像格式, 文章 中都没 有提到 ,读者 如果需 要做进一 步的 研究, 还需要 参考一 些关于 图像格式方 面的 资料。 DataSizePerLine= (biWidth* biBitCount+31)/8;// 一个扫 描行 所占的 字节数 DataSize= DataSizePerLine* biHeight。 GIFHEADER:{ DB Signature; //该字 段占六个字 节 ,为了 用于指 明图像为 GIF 格式, 前三个字 符必 须为 “GIF”,后 三字符用 于指 定是哪 个版本 , 87或89。 DW ScreenWidth;// DW ScreenDepth;//占两 个字节 ,以像素 为单位表 示图 像的 宽、高 DB GlobalFlagByte;//该字 节的各 个位用 于调色版的 描 述 DB BackGroundColor;//代表 图象的 背景颜色的 索 引 DB AspectRatio;图像的 长宽 比 } VC数字 图像处 理编程 讲座之 三 BMP BMP BMP BMP 图像的基本操 作 上一 讲我们 主要介 绍了图 像的格式 , 其 中 重点说明了 BMP文件的 存储格式 , 同 时对 JEPG和GIF 等常 用格式作 了简 单的介 绍。 本 节 主要讲述如 何 操作 BMP文件, 如对其读 、 写和显 示等。 在实 现数字图 象处理的 过程中, 主要是 通过对图 像中的每一 个像素 点运用 各种图像处理算 法来达到 预期 的效果 ,所以 进行图像处理的 第 一步, 也 是我们 最关心 的问题, 是如何得到 图像中 每一个像素 点的 亮度值; 为了观 察和验证处理的 图 像效果, 另一个需 要解决的 问题是 如何将处理前 后 的图像 正确 的显示出 来。 我们这章 内容 就是解决这 些 问题。 随着 科技的发展 ,图像处理技 术 已经渗透到 人类生 活的各 个领域并得到 越来越多 的 应用,但是 突出的 一个矛 盾是图像的 格式也 是越来越多 ,目 前 图像处理 所涉及 的 主要的 图像格式就 有 很多种 ,如 TIF、JEMP、BMP等等 ,一般情 况下 ,为了 处理简 单方便 ,进行 数字图 像处理所采 用 的都是 BMP 格式的 图像文 件 ( 有时 也称为 DIB 格式的 图像文 件) , 并且 这种 格式的 文件是 没有 压缩的 。 我们 通过操 作这种 格式的 文件 , 可以 获取正 确显示图 像所 需 的调色板 信息,图 像的尺寸信 息,图像中 各个像素点 的亮度信息 等等,有了 这些数据, 开发人员就 可以对图像施 加各种处理 算 法 ,进 行 相 应 的处 理 。 如果 特殊情 况下需 要处理其它 某 种格式的 图像,如 GIF、JEMP等格式的 图 象文件, 可以 首先将 该格式转换为 BMP 格式, 然后 再进行 相应的 处理。 这 一点 需要读 者清楚 。 BMP 格式的 图像文 件又可以 分为 许多 种类, 如真彩色位 图 、 256色位图 ,采用 RLE(游程编 码 )压缩 格式的 BMP位图 等等。 由于在 实际的 工程应 用和 图像算 法效果 验证中 经常要 处理的是 256级并且 是没 有压缩的 BMP 灰度 图像, 例如通过黑 白 采集卡 采集得到 的图 像就是 这种格式, 所以 我们 在 整个讲座 中 范例所处理 的 文件格式都是 BMP灰度 图像。 如果读 者对这种 格式的 位图 能够作 到熟练的 操作 ,那么 对于其余形 式的 BMP位图 的操作 也不 会很 困难。 BMP 灰度 图像作为 Windows 环境下 主要 的图像格式 之一 ,以 其格式简 单, 适应性强 而倍受欢 迎。 正如 我们在 上一讲中 介绍 过的那 样,这种 文件 格式就 是每一 个像素用 8bit 表示 , 显示出 来的图 像是黑 白效果 , 最黑的 像素的 灰度 ( 也叫作 亮度) 值为 “0”, 最白 的像素 的灰度 值为 “255”, 整个图 像 各个像素 的 灰度值随 机的 分布在 “0”到“255”的区 间中, 越黑 的像素 , 其灰度 值越接近于 “0”, 越白 (既 越亮) 的 像素, 其灰度 值越接近 于 “255”;与此 对应的 是在 该文件类型 中 的颜色表 项的 各个 RGB 分量值是 相 等的, 并且颜色表 项 的数目是 256个。 在进行 图像处理时 ,操 作图像中 的像素 值就要得到 图像阵列 ;经过处理 后 的图像的 像素值需 要存储起来; 显 示图像时 要正确实 现调色板 、得到 位 图的 尺寸信 息等。 结合这些 问题 ,下面我们 针对性 的 给出了 操作灰度 BMP图像时 的部 分函数实 现代 码及注 释。 一、 BMP BMP BMP BMP 位图操作 首先我们回顾一下上讲中的重要信息: BMP 位图包括位图文件头结构 BITMAPFILEHEADER、位图信息头结构 BITMAPINFOHEADER、位图颜色表 RGBQUAD 和位图像素数据四部分。处理位图时要根据文件的这些结构得到位图文件大小、 位 图的宽、高、实现调色板、得到位图像素值等等。这里要注意的一点是在 BMP 位图中,位图的每行像素值要填充到一个四字节边界, 即位图每行所占的存储长度为四字节的倍数,不足时将多余位用 0填充。 有了上述知识,可以开始编写图像处理的程序了,关于在 VC 的开发平台上如何开发程序的问题这里不再赘述,笔者假定读者都 具有一定的 VC 开发经验。在开发该图像处理程序的过程中,笔者没有采用面向对象的方法,虽然面向对象的方法可以将数据封装起 来,保护类中的数据不受外界的干扰,提高数据的安全性,但是这种安全性是以降低程序的执行效率为代价的,为此,我们充分利用 了程序的文档视图结构,在程序中直接使用了一些 API 函数来操作图像。在微软的 MSDN 中有一个名为 Diblook的例子,该例子演 示了如何操作 Dib 位图,有兴趣的读者可 以参考一下,相信一定会有所收获。 启动 Visual C++, 生 成一个名为 Dib 的多文档程序,将 CDibView类的基类设为 CscrollView 类, 这样作的目的是为了在显 示 位图时支持滚动条,另外在处理图像应用程序的文档类( CDibDoc.h)中声明如下宏及公有变量: 最后将程序的字符串表中的字符串资源 IDR_DibTYPE 修改为: “nDibnDibnDib Files(*.bmp;*.dib)n.bmpnDib.DocumentnDib Document”。 这 样作的目的是为了在程序文件对话框中可以选择 BMP 或DIB 格 式的位图文件。 1、读取灰度 BMP 位图 可以根据 BMP 位图文件的结构,操作 BMP 位图文件并读入图像数据,为此我们充分利用了 VC 的文档视图结构,重载了文挡 类 的OnOpenDocument() 函数, 这样用户就可以在自动生成程序的打开文件对话框中选择所要打开的位图文件, 然后程序将自动 调 用该函数执行读取数据的操作。该函数的实现代码如下所示: #define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//计算图像每行象素所占的字节数目; HANDLE m_hDIB;//存放位图数据的句柄; CPalette* m_palDIB;//指向调色板 Cpalette 类的指针; CSize m_sizeDoc;//初始化视图的尺寸,该尺寸为位图的尺寸; BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName) { LOGPALETTE*pPal;//定义逻辑调色板指针; pPal=new LOGPALETTE;//初始化该指针; CFile file; CFileException fe; if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe)) {//以“读”的方式打开文件; AfxMessageBox("图像文件打不开! "); return FALSE; } DeleteContents();//删除文挡; BeginWaitCursor(); BITMAPFILEHEADER bmfHeader;//定义位图文件头结构; LPBITMAPINFO lpbmi; DWORD dwBitsSize; HANDLE hDIB; LPSTR pDIB;//指向位图数据的指针; BITMAPINFOHEADER*bmhdr;//指向位图信息头结构的指针 dwBitsSize = file.GetLength();//得到文件长度 if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) !=sizeof(bmfHeader)) return FALSE;//读取位图文件的文件头结构信息; if (bmfHeader.bfType != 0x4d42) //检查该文件是否为 BMP 格式的文件; return FALSE; hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwBitsSize); //为读取图像文件数据申请缓冲区 if (hDIB == 0) { return FALSE; } pDIB = (LPSTR)::GlobalLock((HGLOBAL)hDIB); //得到申请的缓冲区的指针; if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) != dwBitsSize - sizeof(BITMAPFILEHEADER) ) 上面的方法是通过 CFile 类对象的操作来读取位图文件的,它需要分析位图中的文件头信息,从而确定需要读取的图像长度。这 种方法相对来说有些繁琐, 其实还可以以一种相对简单的方法读取位图数据, 首先在程序的资源中定义 DIB 类型资源, 然后添加位 图 到该类型中,将图像数据以资源的形式读取出来,这时候就可以根据所获取的数据中的位图信息结构来获取、显示图像数据了。下面 的函数实现了以资源形式装载图像文件数据,该函数的实现代码如下所示: { ::GlobalUnlock((HGLOBAL)hDIB); hDIB=NULL; return FALSE; }//此时 pDIB 数据块中读取的数据包括位图头信息、位图颜色表、图像像素的灰度值; bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针赋值; ::GlobalUnlock((HGLOBAL)hDIB); if ((*bmhdr).biBitCount!=8)//验证是否为 8bit 位图 { AfxMessageBox("该文件不是灰度位图格式! "); return FALSE; } m_hDIB=hDIB;//将内部变量数据赋于全局变量; //下面是记录位图的尺寸; m_sizeDoc.x=bmhdr->biWidth; m_sizeDoc.y=bmhdr->biHeight; //下面是根据颜色表生成调色板; m_palDIB=new Cpalette; pPal->palVersion=0x300;//填充逻辑颜色表 pPal->palNumEntries=256; lpbmi=(LPBITMAPINFO)bmhdr; for(int i=0;i<256;i++) {//每个颜色表项的 R、G、B值相等,并且各个值从 “0”到“255”序列展开; Pal->palPalentry[i].peRed=lpbmi->bmiColors[i].rgbRed; pPal->palPalentry[i].peGreen=lpbmi->bmiColors[i].rgbGreen; pPal->palPalentry[i].peBlue= lpbmi->bmiColors[i].rgbBlue;; pPal->palPalentry[i].peFlags=0; } m_palDIB->CreatePalette(pPal); //根据读入的数据得到位图的宽、高、颜色表 ; if(pPal) delete pPal; EndWaitCursor(); SetPathName(lpszPathName);//设置存储路径 SetModifiedFlag(FALSE); // 设置文件修改标志为 FALSE return TRUE; } ///////////////////////////////////////////////////////////////// HANDLE LoadDIB(UINT uIDS, LPCSTR lpszDibType) { LPCSTR lpszDibRes =MAKEINTRESOURCE(uIDS);//根据资源标志符确定资源的名字; HINSTANCE hInst=AfxGetInstanceHandle();//得到应用程序的句柄; HRSRC hRes=::FindResource(hInst,lpszDibRes, lpszDibType);//获取资源的句柄, 这里 lpszDibType 为资源的名字 “DIB”; 2、灰度位图数据的存储 为了将图像处理后所得到的像素值保存起来, 我 们重载了文档类的 OnSaveDocument( ) 函数 , 这样 用 户 在 点 击 Save 或SaveAs 子菜单后程序自动调用该函数,实现图像数据的存储。该函数的具体实现如下: If(hRes==NULL) return NULL HGLOBAL hData=::LoadResource(hInst, hRes);//转载资源数据并返回该句柄; return hData; } /////////////////////////////////////////////////////////////////// BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName) { CFile file; CFileException fe; BITMAPFILEHEADER bmfHdr; // 位图文件头结构; LPBITMAPINFOHEADER lpBI;//指向位图头信息结构的指针; DWORD dwDIBSize;; if (!file.Open(lpszPathName, CFile::modeCreate |CFile::modeReadWrite | CFile::shareExclusive, &fe)) { AfxMessageBox("文件打不开 "); return FALSE; }//以读写的方式打开文件; BOOL bSuccess = FALSE; BeginWaitCursor(); lpBI = (LPBITMAPINFOHEADER)::GlobalLock((HGLOBAL) m_hDIB); if (lpBI == NULL) return FALSE; dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD); //图像的文件信息所占用的字节数; DWORD dwBmBitsSize;//BMP 文件中位图的像素所占的字节数 dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount)) *lpBI->biHeight;// 存储时位图所有像素所占的总字节数 dwDIBSize += dwBmBitsSize; //BMP 文件除文件信息结构外的所有数据占用的总字节数; lpBI->biSizeImage = dwBmBitsSize; // 位图所有像素所占的总字节数 //以下五句为文件头结构填充值 bmfHdr.bfType =0x4d42; // 文件为 "BMP"类型 bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件总长度 bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize + 256*sizeof(RGBQUAD); //位图数据距离文件头的偏移量; file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//向文件中写文件头信息; file.WriteHuge(lpBI, dwDIBSize); //将位图信息(信息头结构、颜色表、像素数据)写入文件; ::GlobalUnlock((HGLOBAL) m_hDIB); EndWaitCursor(); 二、 调色板的操作 通过上面 的 操作, 我们已 经可以 获取图 像中的 数据了 ,现在 的又一 个问题 是如何在 窗口 中显示出 图像数据 。 灰度图 像要正 确显示, 必须 实现逻辑 调色板 和系 统调色板 。首 先我们 介绍一 下逻辑调 色板 结构 LOGPALETTE,该 结构定 义如下 : Windows 系统 使用调 色板管 理器来管 理与 调色板 有关的 操作, 通 常 活动 窗口的 调色板 即是当 前系统 调色板 , 所有 的 非活动 窗口都必 须按 照此系 统 调色板 来显 示自己 的颜色, 此时 调色板 管理器将 自动 的用系 统调色板 中的 最近似颜色 来映 射相 应的显 示颜色。 如果 窗口或应用 程序 按自 己的调 色板显 示颜色, 就 必须将 自己的 调色板 载入到 系统调 色板中 ,这种 操作叫 作实现调 色板 ,实现调 色板 包括两 个步骤 ,既首 先将调 色板选择到 设 备上下 文中, 然后 在设备 上下文 中实现它 。可以 通过 CDC::SelectPalette()、CDC::RealizePalette()或相 应的 API函数来实 现上述 的 两个步 骤。在 实现调 色板 的过程中 , 通过在 主框架 类中处理 Windows定义 的消息 WM_QUERYNEWPALETTE、WM_PALETTECHANGED及视图 类中 处理自 定义消 息 WM_DOREALIZE(该 消息在 主框架 窗口定 义如下 : #define WM_REALIZEPAL(WM_USER+101))来实 现调 色板的 操作。 当系统 需要处理调 色 板的 变化时 ,将向 程序的 主窗口 发送 WM_QUERYNEWPALETTE、WM_PALETTECHANGED,例 如当某 一窗口 即将激活时 , 主框 架窗 口将收到 WM_QUERYNEWPALETTE消息 , 通知 该窗 口将要 收到输入焦 点 , 给它 一次机会 实 现其自 身的逻辑调 色板 ; 当 系统调 色板改变后 , 主 框架 窗口 将收 到 WM_PALETTECHANGED消息 ,通知 其它窗 口系统 调色板 已经改变, 此时 每一 窗口都应该 实 现其逻辑调 色板 ,重 画客户 区。 由于 上述的 调色板 变更消 息是发往主 框架 窗口 的,所以 我们 只能在 主窗口 中响应这两 个消 息, 然后由 主框架 窗口通知 各个视窗 , 使得程序 激活时 能自 动装载自 己的 调色板 。我们 定义的 用户消息 WM_REALIZEPAL 用于 主框架 窗口通知 视窗 它已经收到 调 色板变更 消息 ,视窗 应该协 调其调 色板。 下面我们 给出 了各 个消息 的响应处理 函 数的具 体实现代 码和 注释: SetModifiedFlag(FALSE);// 将文档设为 “干净 ”标志,表示此后文档不需要存盘提示; return TRUE; } typedef struct tagLOGPALETTE { WORD palVersion;//调色板 的板 本号, 应该指 定该值为 0x300; WORD palNumEntries;//调色板 中的 表项数, 对于 灰度图 像该值为 256; PALETEENTRY palPalEntry[1];//调色板 中的 颜色表 项,由 于该表 项的数目 不一 定,所以 这里 数组长 度定义为 1,灰度 图像对应的 该 数组的 长度为 256; }LOGPALETTE; 颜色表 项结构 PALETTEENTRY定义 了调色板 中的 每一个颜色 表 项的颜色和 使 用方式, 定义 如下: typedef struct tagPALETTEENTRY { BYTE peRed; //R分量值; BYTE peGreen; //G分量值; BYTE peBlue; //B分量值; BYTE peFlags; // 该颜色被 使 用的方式, 一 般情况下 设为 “0”; }PALETTEENTRY; ////////////////////////////////////////////////////////// void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd) {//总实 现活动 视的调 色板 CMDIFrameWnd::OnPaletteChanged(pFocusWnd); CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到 活动的 子窗口 指针; if (pMDIChildWnd == NULL) return CView* pView = pMDIChildWnd->GetActiveView();//得到 视图的 指针; ASSERT(pView != NULL); SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd); //通知 所有子 窗口系 统调色板 已改变 注: 在调用 API函数显 示位图 时, 不要忘 记设置逻辑 调 色板, 即 "背景 "调色板 ,否 则位图 将无法正 确显 示,读 者可以 从后面的 显示部 分的 实现看 出我们 在显 示时实 现了逻辑调 色板 。上述的 处理相 对来说 比 较繁琐 复杂, 可能对于 初学 者来说也 比较难于 理解, 所以 如果 我们的 程序仅 仅限于 处理灰 度图 象,可以 采用 另外一 种相对简 单的 办法, 即在文 档类的 初始化阶段 定 义一个灰度 调 色板, 然后在 设备上下 文中 实现它 ,这样 作的好处是 在 度取灰 } //////////////////////////////////////////////// BOOL CMainFrame::OnQueryNewPalette()//提供实 现系 统调色板 的机会 { // 实现活动 视的 调色板 CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到 活动的 子窗口 指针; if (pMDIChildWnd == NULL) return FALSE;//no active MDI child frame (no new palette) CView* pView = pMDIChildWnd->GetActiveView();//得到 活动子 窗口的 视图指 针; ASSERT(pView != NULL); //通知 活动视图 实现系 统调 色板 pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd); return TRUE; } ///////////////////////////////////////////////// BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//实现系 统调 色板 { ASSERT(wParam != NULL); CDibDoc* pDoc = GetDocument(); if (pDoc->m_hDIB == NULL) return FALSE;// must be a new document CPalette* pPal = pDoc->m_palDIB; //调色板 的颜色表 数据在 InitDIBData()函数中 实现 if (pPal != NULL) { CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;//得到 程序的 主框架 指针; ASSERT_KINDOF(CMainFrame, pAppFrame); CClientDC appDC(pAppFrame);//获取 主框架 的设备 上下文 ; CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd); //只有 活动视才可 以 设为 "FALSE",即 根据活动 视的 调色板 设为 "前景 "调色板 ; if (oldPalette != NULL) { UINT nColorsChanged = appDC.RealizePalette();//实现系 统调 色板 if (nColorsChanged > 0) pDoc->UpdateAllViews(NULL);//更新视图 appDC.SelectPalette(oldPalette, TRUE); //将原 系统调 色板置为 背景 调色板 } else { TRACE0(“tSelectPalette failed in”); } return TRUE; } 度位图 时可以 不再考虑 文 件中的 颜色表 信息, 提高了 文件读 取速度 ,笔者 在开发一 个基于 机器视觉 的 项目时 采用的 就是这种 方法, 取的 了比较满 意的 效果 。首先 定义一 个指向 逻辑颜色表 结构 LOGPALETTE的指针 pPal,填 充该指 针,然 后将该 指针与 调色板 指针联系 起来, 该方法的 具 体实现如 下: 三、 图像的显示 显示 DIB 位图 数据可以 通过设 备上下文 CDC对象的 成员 函数 CDC::Bitblt()或CDC::StretchBlt()来实 现,也 可以通过 API函数 SetDIBBitsToDevice()或StretchDIBBits()来实 现,函 数中具 体所用 到的各 个参数的 意义 可以参考 MSDN。其中 StretchDIBBits()和CDC:: StretchBlt()可以 将图像进行 放大 和缩 小显示。 当 从文档 中装入位图 文 件时, CDIBView 类的 OnInitialUpdate 函数将 被调 用, 因此可以 在该 函数 中 实现对视 图 尺寸的 设 置, 用于正 确的显 示位图 ,然后 就可以 在视图 类的 OnDraw() 函数中 正确的 显示位图 了。 这两个函 数的 具体实 现代码 分别如 下 所示: ///////////////////////////////////////////////////////// CDibDoc::CDibDoc() { ………………………. LOGPALETTE*Pal; Pal=new LOGPALETTE; m_palDIB=new Cpalette; pPal->palVersion=0x300; pPal->palNumEntries=256; for(int i=0;i<256;i++) m_palDIB->CreatePalette(pPal); ………………….. } ///////////////////////////////////////////////////////////// void CDIBView::OnInitialUpdate() { CscrollView::OnInitalUpdate(); CDIBDoc *pDoc=GetDocument(); If(pDoc->m_hDIB==NULL)//如果 位图数据 为空 ,设置 m_sizeDoc 的默认 尺寸 ; pDoc->m_sizeDoc.cx=pDoc->m_sizeDoc.cy=100; SetScrollSizes(MM_TEXT,pDoc-> m_sizeDoc); } ///////////////////////////////////////////////////////////// void CDIBView::OnDraw(CDC *pDC) { BITMAPINFOHEADER*lpDIBHdr;//位图 信息头 结构指 针; BYTE*lpDIBBits;//指向 位图像素 灰度 值的指 针; BOOL bSuccess=FALSE; CPalette*OldPal=NULL;//调色板 指针; HDC hDC=pDC->GetSafeHdc();//获取 当前设 备上下 文的句 柄; CDIBDoc *pDoc=GetDocument();//获取 活动文 档的指 针; If(pDoc->m_hDIB ==NULL) lpDIBHdr=( BITMAPINFOHEADER*)GlobalLock(pDoc->m_hDIB);//得到 图像的 位图头 信息 lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取 保存图 像像素 值的缓冲 区的 指针; if(pDoc-> m_palDIB) {//如果 存在调 色板信 息,实 现逻辑调 色板 ; 四、 小结 在本 期讲座里 我们主要 介绍了如 何操作 灰度位图 ,它具有 较强的代 表性, 同时为后 续的图像处理编 程的 学习 作了必要 的准备工 作,经过学 习, 对 于如 何操作 其它类型的 BMP 格式的 图像文 件, 可以达到 举一 反三的 作用。 VC 数字图像处理编程讲座之四 BMP BMP BMP BMP 图像显示的特 效操作 上期 讲座中 我们主 要讲述了 BMP图像数据 的 存取、 图 像的显 示和调 色板的 操作等 内容, 在 上面的 学习基础 上, 我们 可以进一 步深 化, 学习 并掌 握 图像特效 显 示技术 。有了 这种技术 ,可以 用来在 今后 的项目 开发中 美化我们 的软 件界面, 提高软 件的 视觉效果 。在 如今的 商业软 件中, 几乎每一 幅图 像的 显示都采用 了 图像特效显 示, 例 如读者 比较熟 悉的 Windows 的屏 幕保护 程序就 采用了 各种各 样的图 像特效显 示, 使人感到 眼 花缭乱 和耳目 一新 。 专业图 像处理软 件更 是提供了 丰 富的显 示方式供用 户 使用, 可以方便 的在 程序中 实现图 像的特效显 示,如 PhotoShop 、Authorware 等。 本节主 要 介绍 如何实 现图像的 浮雕、 雕刻 、百页 窗、旋 转、扫 描、栅条、 马 赛克、 和渐显 渐隐显 示等效果 。通过这 期 讲座的 学习, 读者朋 友们也 可以自 己动手 制作 拥有特效显 示效果 的 软件了 。 图像的 显示我们 讲过主 要有 BitBlt()、SetDIBitsToDevice()和StretchDIBits() 等函数。 需要 读者注 意的是 ,在特效显 示时 ,并不 是每个显 示函 数都适宜 , BitBlt()函数主 要是 用来显 示设备 无关位图 ( DDB) , 后两个函 数用 来显示设 备无关 位图 ( DIB) 。 由于我们 讲座 里处理的 是设 备无 关 位图 ,所以 我们主 要关心 的是后 两个函 数的应用 ,其中 SetDIBitsToDevice()使用 起来较死板 , 远不如 StretchDIBits() 用的灵 活,并且 对大 多 数 的特效显 示无能 为 力,所以 为了 实现图 像的 特效显 示效果 ,需 要使用 StretchDIBits() 函数来显 示图 像,具 体什么 原因, 我想可能 是微软 在实 现这 些函 数时使 用的方法不 同 吧。这些 函数如 何使 用,各 个参数的 含义 ,可以 参考微软的 MSDN。 实现图 像的 特殊效果 的显 示的基本 思路是 要么 是操作 图像的 像素, 要么是 对图像分块 按 一定的 方向或次序 , 分阶段的 显示或擦 除 对应的 图像块 。 对于 第二种 显示的 思路, 其中的 要点是 : 1.划分图 像块; 2.确定 图像块的 操作 次序; 3.显示或清 除 对应的 图像块; 4.在两 个连续 显示的 图像块之间 插 入一 个固定 的延迟 。其中 图像块的 划分决定 了 图像的 显示方式, 图 像块的 显示顺序 决定 了显示的 方向 和细分的 依据 。不同 的效果 决定了 不同的 分块方 法和 显示次序 ,我们 将在 后面的 各种特效显 示中 介绍 如何分块和 决定 次序 。为了 使图像的 显示过程 明 显的表 现出来, 实现显 示的 特效, 就需 要在图 像 块的 依此显 示中插入固 定 的延迟 。 也 许读者 朋友会想 到利用 sleep()函数或用 Settime() 来 实 现延迟 , 由于 Windows是个基于 消 息的多 任务操 作 系 OldPal=pDC-> SelectPalette(pDoc-> m_palDIB,TRUE); PDC->RealizePalette(); } else { AfxMessageBox("图像的 调色板 数据 不能为 空,请 首先读 取调色板 信息 ! "); return ; } SetStretchBltMode(hDC,COLORONCOLOR); //显示图 像 BSuccess=StretchDIBBits(hDC,0,0,pDoc-> m_sizeDoc.cx, pDoc-> m_sizeDoc.cy, 0, pDoc-> m_sizeDoc.cy,0, pDoc-> m_sizeDoc.cy, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); GlobalUnlock(pDoc->m_hDIB); If(OldPal)//恢复 调色板 ; PDC->SelectPalette(OldPal,FALSE); retrun; } 统, 这些 方法所产 生 的延迟 时间对于 图像的 显示来说 是 不精确 的, 为了 实现与 机器无关 的更 精确的 时间延 迟, 可以 采用 timeGetTime()函数来产 生 微 秒级 的延迟 。 使用这个函 数时 为了 编译不 产生错误 , 要在 连接设 置中引 入 “Winmm.lib”库, 并要 包含 头文件 “Mmsystem.h”。 这里我们 首先 给出一 个 延迟 函数, 它用来实 现固 定时间 的延迟 : 一、操作位图 的像素 实现显示 的特效 我们 首先介 绍直接操 作图 像中的 像素的 灰度值来实 现图 像显 示的特效、 这里 我们 主要介 绍如何实 现图 像的浮 雕和雕刻 效果 。经常 看电视的 朋友 们 不知 注意到 没有, 有些电 视连续 剧在每集片 头 或片尾部 分都有 显示一 些特殊效 果 的图像, 比如 前一阵子 中央一 套放的 《长 征》和 《康熙 王朝》 ,这些 特效称 为 "图像的 浮雕效果 "和"图像的 雕刻 效果 ", 经过 这些 特效处理 后 的图像增 强了 观众们 的视觉 效果, 它 们看上去 仿 佛是使用 3D技术 作的, 这也 许 就是 为什么 这种技术 那么 流行的 原因吧 。其实 ,我们 完全可以 用一 些简单的 数字 图像处理算 法来实 现这些 看似复 杂 高深的 显示效果 。下 面以一 个标准 的Lena灰度 图像为 原图, 给出了 处理后 的效果 图,同 时给出了 VC开发平 台上的 部分实 现源代 码。 1."浮雕 "图像 "浮雕 "图象效果 是 指图像的 前景 前向凸 出背景 。 所 谓 的 "浮雕 "概念是 指标绘图 像上的 一 个像素 和它左 上方的 那个像素 之间 差值的 一种处理过 程 , 为 了使 图像保持一 定 的亮度 并呈现灰色 , 我在处理过 程中 为这个差 值加 了一 个数值为 128的常 量。需 要读者 注意的 是,当 设置一 个像素 值的时 候,它 和 它左 上方的 像素都要 被用 到,为 了避免用 到已 经设置过的 像素 ,应该 从图 像的右 下方的 像素开始 处理, 下面是 实现的 源代 码: void DelayTime(DWORD time) { DWORD BeginTime ,EndTime; BeginTime=timeGetTime();//得到 当前的 系统时 间、单位为 微秒 ; do while((EndTime-BeginTime) m_hDIB;//拷贝 存放已 经读取 的图像文 件数据 句柄 ; lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);//获取 图像信 息头 pData=(unsigned char*)FindDIBBits((LPSTR)lpBi); //FindDIBBits是我定 义的 一个函 数、根据 图像的 结构 得到位图 的灰度 值数据 、 pDoc->SetModifiedFlag(TRUE); //设置文 档修改标 志 为 “真”、为 后续的 修改存盘作 准 备; data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight); //声明 一个缓冲 区用 来暂存处理 后 的图像数 据; data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);//得到 该缓冲 区的指 针; AfxGetApp()->BeginWaitCursor(); int i,j,buf; for( i=lpBi->biHeight; i>=2; i--)//从图 像右下 角开始 对图像的 各个像素 进行 “浮雕 ”处理; for( j=lpBi->biWidth; j>=2; j--) { //浮雕处理 2."雕刻 "图像 上面讲述 了 通过求 一个像素 和它 左上方像素 之间 的差 值并加 上一个常 数的 方法生 成 "浮雕 "效果 的灰度 图像, "雕刻 "图像与 之相 反, 它 是通过取 一 个 像素 和它右 下方的 像素之间 的差 值并加 上一个常 数, 这里我也取 128,经过这 样 处理, 就可以 得到 "雕刻 "图 像 ,这 时候 图 像 的前 景凹 陷 进 背景 之 中。 同样 需要读 者注意 的是为 了避免重 复使 用处理过的 图 像像素 ,处理图 像时 要从图 像的左 上方的 像素开始 处理。 实现代 码如 下: buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i+1)*WIDTHBYTES(lpBi- >biWidth*8)+j-1)+128; if(buf>255) buf=255; if(buf<0)buf=0; *(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf; } for( j=0; jbiHeight; j++) for( i=0; ibiWidth; i++) //重新写 回原 始图像的 数据 缓冲区 ; *(pData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j); AfxGetApp()- >EndWaitCursor(); pDoc->m_hDIB =hdib//将处理过 的 图像数据 写回 pDoc中的 图像缓冲 区; GlobalUnlock((HGLOBAL)hdib);//解锁 、释放缓冲 区 ; GlobalUnlock((HGLOBAL)data1handle); GlobalFree((HGLOBAL)hdib); GlobalFree((HGLOBAL)data1handle); Invalidate(TRUE);//显示图 像 } void CDibView::OnDKImage() { //TODO: Add your command handler code here HANDLE data1handle;//这里 的内部 变量与 前面的 含义一 致、这里 不再赘述 ; LPBITMAPINFOHEADER lpBi; CDibDoc *pDoc=GetDocument(); HDIB hdib; unsigned char *pData; unsigned char *data; hdib=pDoc->m_hDIB;//拷贝 图像数据 的句 柄; lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib); pData=(unsigned char*)FindDIBBits((LPSTR)lpBi); pDoc->SetModifiedFlag(TRUE); data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);//申请 缓冲区 ; data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);//得到 新的缓冲 去的 指针; AfxGetApp()->BeginWaitCursor(); int i,j,buf; for( i=0;i<=lpBi->biHeight-2; i++)//对图 像的各 个像素 循环进行 "雕刻 "处理; for( j=0;j<=lpBi->biWidth-2; j++) { buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i-1)*WIDTHBYTES(lpBi- >biWidth*8)+j+1)+128;//“雕刻 ”处理; if(buf>255) buf=255; if(buf<0)buf=0; *(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf; } for( j=0; jbiHeight; j++) 3.图 像的旋 转 根据 图像像素 的位置来 调 节该位置的 灰度 可以 实现许多 显示的 特效, 例如 图像的 镜像、 翻转等 。灰度 图像旋 转就是 根据这一 个思 想实现的 ,它 是 指把定 义的 图像绕某 一点 以逆时 针或顺时 针方向 旋转一 定的 角度, 通常是 指绕图 像的中 心以逆时 针方向 旋转。 首先 根据旋 转的角 度、图 像对角 线的长 度计算 旋转后 的图 像的最 大宽度 、高度 ,根据 旋转后 图象最 大的宽 度、高度 生成新的 缓冲 区, 假设图 像的左 上角为 ( left, top), 右下角 为( right, bottom), 则图像上任 意 点( x, y)绕其中 心 ( xcenter, ycenter)逆时 针旋转 angle 角度 后,新的 坐标位置 ( x1, y1)的 计算公 式为: 与图 像的镜 像变换相 类似, 下一 步就是 把原图 中的 (x,y)处象素 的灰度 值读 入新缓冲 区的 (x1,y1)点处。 注意 在新缓冲 区中 与原图 没有对应的 象 素点 的值用 白色或指 定的 灰度代 替。 二、图像的分 块显示 和清除 1.图像的 扫描 显示和 清除 扫描 显示图像是 最基本 的特效显 示方法, 它表现为 图像一 行行(或一 列列 )地显示出 来或从屏 幕上清 除掉, 有种大戏院 种的拉幕效果 。 根据扫 描 的方向 的不同 ,可以分为 上、下 、左、 右、水平 平分和垂 直平分等 六种扫 描。这里 以向下移 动为例, 分别介 绍显示和 清除的实 现。其余的 扫描 效果可 以依次类推。 向下 扫描显示的 实现方法是 :从图像的 底部开始将 图像一行一 行的复制到 目标区域的 顶部。每复 制一行后, 复制的行数便 要增加一 行, 并加 上一些 延迟; 向下移 动清除 的实现方法 是 图像向 下移动 显示, 并在显 示区域的 上部 画不断增 高的 矩形。 1)扫 描显示的 代码 : for( i=0; ibiWidth; i++) //重新将 处理后 的图 像数据 写入原 始的图 像缓冲 区内; *(pData+i*WIDTHBYTES(lpBi- >biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j); pDoc->m_hDIB =hdib//将处理过 的 图像数据 写回 pDoc中的 图像缓冲 区; GlobalUnlock((HGLOBAL)hdib);//解锁 、释放缓冲 区 ; GlobalUnlock((HGLOBAL)data1handle); GlobalFree((HGLOBAL)hdib); GlobalFree((HGLOBAL)data1handle); Invalidate(TRUE);//显示图 像 } xcenter = (width+1)/2+left; ycenter = (height+1)/2+top; x1 = (x-xcenter) cosθ -(y - ycenter) sinθ+xcenter; y1 = (x-xcenter) sinθ+ (y- ycenter) cosθ+ ycenter; CdibView::OnImageDownScan() { CDibDoc *pDoc=GetDocument(); HDIB hdib; CClientDC pDC(this); hdib=pDoc->m_hDIB;//获取 图像数据 句柄 ; BITMAPINFOHEADER*lpDIBHdr;//位图 信息头 结构指 针; BYTE*lpDIBBits;//指向 位图像素 灰度 值的指 针; HDC hDC=pDC.GetSafeHdc();//获取 当前设 备上下 文的句 柄; lpDIBHdr=( BITMAPINFOHEADER*)GlobalLock(hdib);//得到 图像的 位图头 信息; lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取 指向图 像像素 值; SetStretchBltMode(hDC,COLORONCOLOR); //显示图 像; 2)清 除代码 : 2.百页 窗效果 所谓 百页窗 显示效果 ,就 如同关 闭和开启 百页 窗一样 ,图像被分 为 一条条或一 列 列地分别 显示或清 除 掉,根据 显示时 以行 或列为 单位可以 将该 效 果分为 垂直 或水平 两种方式。 以 垂直百 页窗为 例来说明 如何实 现这种 特效显 示。 实现垂 直百页 窗显示时 ,需 要将图 像垂直 等分为 n部分由 上向 下扫描 显示, 其中 每一 部分包括 m个条、 这个 n可以 根据具 体应用 时的需 要来决定 、 m既为 图像的 高度除 n。 扫描显 示时, 依照 差值进行 扫描 显示, 即第 k 次显示 k-1、k*m-1、…k*n-1条扫 描线。 同样, 垂直百 页窗清 除的实 现与垂 直百页 窗的显 示相似, 不同 的是将 绘制位图 换成 画矩 形而已 。在下 面的 例子 中,我将 图像的 分成 8份。 3.栅条显 示特效 栅条特效 是 移动特效的 复 杂组合, 可以 分为 垂直栅条和 水 平栅条两 类。 它的基本 思想 是将图 像分为 垂直或水 平的 的小条, 奇数条向 上或向 左 显示 / 清除 ,偶数条向 下 或向右 显示 /清除 。当然 也可以 规定进行 相反的 方向 显示 /清除 。下面的 代码 是实现垂 直栅条的 例 子: for(int i=0;ibiHeight;i++) {//每次循环 显 示图象的 “0”到“i”行数据 ; SetDIBitsToDevice (hDC,0,0,lpDIBHdr->biWidth, lpDIBHdr->biHeight, 0, 0,0, i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS ); DelayTime(50);//延迟 ; } GlobalUnlock(hdib); return; } …………………………………//由于 篇幅的 限制, 省略了 与上面的 相同 代码 Cbrush brush(crWhite);//定义 一个 “白色 ”的刷 子; Cbrush *oldbrush=pDC->SelectObject(&brush); for(int i=0;i < lpDIBHdr->biHeight ;i++) ………………………………… ………………………………… int m=8; int n=lpDIBHdr->biHeight/m;//图像的 高度 能够整除 8; for(int l=1;l<=m;l++) for(int k=0;kbiWidth,1, 0, lpDIBHdr->biHeight-4*k-l+1,lpDIBHdr->biWidth,1, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);//juanlianxiaoguo DelayTime(50); } ………………………………… 4.马赛克 效果 马赛克 显示是 指图 像被分成许 多 的小块, 它们 以随机的 次序 显示出 来,直 到图像显 示完 毕。实 现马赛克 的效果 主要 解决的 问题是 如何定 义显示随 机序 列的小 方块, 这个问 题的解决可 以 在定义 过小方块的 基础 上, 用一个数组 来记 录各 个方块的 左上角 的坐标的 位置。 显 示图像过程 中 ,产生 一个随 机数来挑 选即 将显 示的小 方块, 显示后 将该方块的 位置坐 标从数 组中 剔除 。清除 过程与 之相仿 。剔除 显示过的 方块的 位置坐标 的 方法是 将该数组中 的 最后 的一个点 的坐标拷 贝 到当前 位置, 然后删 除数组中 的最 后点的 坐标, 经过实 现发现这样 处理有 时 显示的 图像是 不完整 的,分析其 原 因 是生 成随机 数的 过程有 舍入溢出 误差 。读者 可以采用 其它 的办法解决 这个问 题 ,例如 可以生 成固定 的随机数组 或采用 一 个动态 的数组来跟 踪 未显示的 图像方块 的 坐标等 方法。 ………………………………… int m=8; for(int i=0;i<=lpDIBHdr->biHeight;i++) for(int j=0;j<=lpDIBHdr->biWidth;j+=m) {//向下 显示偶 数条; StretchDIBits (hDC,j,0,m,i,j,lpDIBHdr->biHeight-i, m,i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);//juanlianxiaoguo j=j+m; //向上显 示奇数条 ; StretchDIBits (hDC,j,lpDIBHdr->biHeight-i,m,i,j,0, m,i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);// DelayTime(20); }………………………………… ………………………………… int m,n; int RectSize=60;//方块的 宽、 高尺寸为 60个像素 ; if(lpDIBHdr->biWidth%RectSize!=0)//得到 图像水 平方块的 个数; m= lpDIBHdr->biWidth/RectSize+1; else m= lpDIBHdr->biWidth/RectSize; if(lpDIBHdr->biHeight%RectSize!=0)//得到 图像垂 直方块的 个数; n= lpDIBHdr->biHeight/RectSize+1; else n=lpDIBHdr->biHeight/RectSize; POINT*point=new POINT[n*m];//申请 一个数组用 来记 录各 个方块的 左上角 的坐标; POINT point1; for(int a=0;a=0;k--) { 5.图 像的淡 入淡出 效果 图像的 淡入淡 出的 显示效果 被广 泛的应用 在多 媒体娱乐 软件中 ,是 一种特别 重要 的特效显 示方法。 淡 入就是 将显示图 像的 目标区 域由本 色逐渐过 度的 图像中 的各个像素 点 的颜色; 淡出 就是由 显示的 图像逐渐过 度 到目标区 域的 本色。 实现图 像的淡 入淡出 有两种 办法: 一是均 匀的改变图 像的 调色 板中 的颜色索 引值; 另一 种方法是 改变图 像像素 的灰度 值。 第一种 方法实 现起来比 较繁 琐,第 二种方法就 比 较简单。 下面是 我们 采用第 二种方法实 现 图像淡 入效果 的代 码: int c=(int)((double)(m*n)*rand()/fMax); int mx=point[c].x; int my=point[c].y; //显示对应 的 图像的 小块; StretchDIBits (hDC,mx,my,RectSize,RectSize, mx,lpDIBHdr->biHeight-my,RectSize,RectSize, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); point[c].x=point[k].x; point[c].y=point[k].y; DelayTime(50); } ………………………………… ………………………………… //申请 一个与 图像缓冲 区相 同大小 的内存; hdibcopy=(HDIB)GlobalAlloc(GMEM_SHARE,lpDIBHdr->biWidth*lpDIBHdr->biHeight); lpbits=(BYTE*)GlobalLock(hdibcopy); //将缓冲 区的 数据初 始化; for(int k=0;kbiWidth*lpDIBHdr->biHeight;k++) //显示最 初的 图像为 “白色 ” StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0, lpDIBHdr->biWidth,lpDIBHdr->biHeight, lpbits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); //布尔 变量 end用来标志 何时 淡入处理 结 束; BOOL end=false; while(!end) { int a=0; for(int k=0;kbiWidth*lpDIBHdr->biHeight;k++) //显示处理 后 的图像数据 lpbits; StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0, lpDIBHdr->biWidth,lpDIBHdr->biHeight, lpbits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); //如果 所有的 点的灰度 值的 都小于 或等于 原始图 像的像素 点的 灰度值, 则认 为图像的 淡入处理 结 束。 if(a==lpDIBHdr->biWidth*lpDIBHdr->biHeight) 本文 上面的 内容介 绍了几种 图像的 特殊显 示效果 ,代 码在 Windows2000和Visual C++6.0编程环 境下 编译通过, 运行 正常 ,处理达到 了 预期 的效果 。读 者可以 将上面介 绍的 显示图 像的函 数和处理思 路结 合起来, 实 现更多 效果。 VC数字 图像处 理编程 讲座之 五 BMP BMP BMP BMP 图像操作的补 充篇 上一 讲中我们 介绍 了图像特效 显 示操作 的实现方法 , 如随 机显示效果 、 马赛克 效果、 拉幕显 示效果 等, 由于篇 幅的限 制, 还有许多 效果 没有介 绍 ; 本期 讲座将 接着上一 讲的 内容, 继续介 绍一些 图像特效显 示效果 。 另外 , 我们前 面的学 习都是 针对现成的 BMP图像 , 在实际工 作学 习中, 绝 大部分处理 图 像过程都是 在 一个系 统环境中 , 也就是 说需要 和图像数 据 的 获 取设 备直 接 打交 道 , 一般 情况 下, 计算机 图 像 处 理系统 从 系统 层 次 上 可分 为高 、 中、 低档 三 个 层次, 目前 比较 普 及的 是 低档 次 的系 统 , 该系 统 由 CCD (摄像头 ) 、图像采集 卡 、计算 机三个部 分组成, 其结 构简 单,应用 方便 ,效果 也比较不 错, 得到的 图像较清 晰, 所以目 前在工 程应用 中采用 的比较 多。 这 就 给开发人员 带 来一个现实 的 问题, 如 何使 用图 像采集卡 呢? 目前虽 然各种 编程资 源中基于 VC开发经验 的 文章不 少, 但 是关 于如何在 VC开发 平台 上使用 图像采集卡 的 文章的 确没发现, 笔 者借这期 讲座 的宝贵 机会, 补充介 绍一下 如何在 程序中 编写自 己的代 码来操 作图像采集 卡 ,从而 搭建一 个完 整的图 像处理系 统。 希望通过这 部 分内容 的学习 ,在读 者的脑 海里就 可以建立一 个完 整的 图像操 作系统 概念; 同时也 能够给目 前正 需要利 用图像 采集卡 开发自 己的 图像处理系 统 的朋友 有所帮 助。 1111.抖动图像 在上一 节讲座 中, 我 们 讲到 了如何实 现图 象的 "雕刻 "和"浮雕 "效 果, 它们的 实现 思 想是 通 过 求取 "没有 处理过的 相邻 两个像素 之间 的差值 "来实 现 的。 如果 没有限 制 "以前 没有处理过 的 两个像素 之间 的操作 ", 取而 代之 的 是 "处理以 前已 经操作 过的像素 ", 那 末就 可以将 一个像素 的灰度 值传 递到与 其相 邻 的若 干像素 。事 实上, 有时后 我们必 须通过上述 的 约定才能 实现一 些效果 , 如图像的 抖动 效果。 例如, 为了 使图象看起 来好象 从左 上角 向右下 角扫过 , 以产 生运动 的感觉 ,必须 要反复 拷贝左 上方的 那些像素 的灰度 值, 逐步把它 们融合在 一 起,看起来 好象图 像后 边有 一 些颜色在 逐渐的 消 失,这就 是我 们要 讲的图 象的抖动 效果 。下面给出 了 该效果 的实现代 码: end=true; DelayTime(50); ………………………………… void CDibView::OnDouDongImage() //产生 "抖动 "效果 图函数 { HANDLE data1handle;//用来存放 图 像数据 的句柄 ; LPBITMAPINFOHEADER lpBi;//图像的 信息 头结构 ; CDibDoc *pDoc=GetDocument();//得到 文挡指 针; HDIB hdib;//用来存放 图 像数据 的句柄 ; unsigned char *pData;//指向 原始图 像数据 的指针; unsigned char *data;//指向 处理后 图像数据 的指 针; hdib=pDoc->m_hDIB;//拷贝 存放已 经读取 的图像文 件数据 句柄 ; lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);//获取 图像信 息头; pData=(unsigned char*)FindDIBBits((LPSTR)lpBi); //FindDIBBits是我定 义的 一个函 数,根据 图像的 结构 得到位图 的灰度 值数据 ; pDoc->SetModifiedFlag(TRUE); //设置文 档修改标 志 为 "真",为 后续的 修改存盘作 准 备; data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight); //声明 一个缓冲 区用 来暂存处理 后 的图像数 据; data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);//得到 该缓冲 区的指 针; AfxGetApp()->BeginWaitCursor(); int i,j,buf; for( i=lpBi->biHeight; i>=2; i--)//从图 像右下 角开始 对图像的 各个像素 进行 "抖动 "处理; 对于 比较复杂 的图像, 计算当前 像素的 灰度和斜上方像 素 的均值产 生的抖动 效果可能 不明显 ,为了解决这 个问 题,笔 者的解决办 法是隔 行隔列 的 计算 ,比如 说计算 当前位置( i j)的 灰度值, 我取 ( i,j)和 ( i-2,j-2)两 个位置的 像素 的灰度 值的平 均。 2.2.2.2.图像合成技术 图像合成 技术 很重 要,其实 质是 操作将 两幅或两 幅以 上的图 像,将 它们的 信息融合在 一 起,产生 1+1>2 的效果 。我们 在进行 图像合成 的 时候可 以采用 Alpha值的 方法, 下面来看一 下 如何利用 Alpha值来合成 两 张图片 。 采用 Alpha图象合成 的 方法, 就是最 终合成的 图象的 各点 像素值是 由用 来制作 合成图 的两张图 片的 相应点 的像素 值按一 定比例 混合而 成的, 这个 比例由 Alpha值决定 ,具 体算式如 下: 上面的 算式中 , pixel1 代表 图像 1的当 前像素 点的灰度 值, pixel2 代表 图像 2的当 前像素 点的灰度 值, Alpha可以 看作两 个像素 在最终合成 的 结 果中 所占的 权重。 可以看出 ,只 要修改 Alpha的值, 就可以 改变合成 后 的图象中 用来合成 的 两张图 片各自 所占的 比值, 改变合成后 的 显示效果 。我们 可以 利用这个方 法, 按一 定的时 间间隔 修改 Alpha的值、 这样 就可以 很轻易 的制作 出生动 的淡入淡 出效果 、实 现两幅 图片间 的平滑 过度效果 。下 面给 出一 个制作 合成图 的具体源码 : for( j=lpBi->biWidth; j>=2; j--) { //抖动 处理、 从图像的 右下 角开始 计算图 像斜上方相 邻 像素的 均值; buf=(*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)+*(pData+(lpBi->biHeight-i+1)*WIDTHBYTES(lpBi- >biWidth*8)+j-1))/2; if(buf>255) buf=255;//限制 像素点 的灰度 范围为 0-255; if(buf<0)buf=0; *(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf; } for( j=0; jbiWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j); AfxGetApp()- >EndWaitCursor(); pDoc->m_hDIB =hdib//将处理过 的 图像数据 写回 pDoc中的 图像缓冲 区; GlobalUnlock((HGLOBAL)hdib);//解锁 、释放缓冲 区 ; GlobalUnlock((HGLOBAL)data1handle); GlobalFree((HGLOBAL)hdib); GlobalFree((HGLOBAL)data1handle); Invalidate(TRUE);//显示图 像 } resultPixe= (pixel1*(255-Alpha)+pixel2*Alpha)/255; // Alpha取值范 围从 0 到255 BOOL CompoundImage(HANDLE HDib1,HANDLE HDib2,int alpha) { BYTE lpData1,lpData2; // 源图象 2的信 息 //由于 待合成的 两个图 象的 格式、 大小是 一样的 ,所以 我只获取 一个图 像文 件的图 像信息 就可以 了。 LPBITMAPINFO lpBi=(LPBITMAPINFO)HDib2; // 计算 图象数据 偏移 量 lpData2=(LPVOID)((LPBYTE)lpBi->bmiColors+256*sizeof(RGBQUAD)); //获取 源图像 2的图 像数据 ; lpBi=(LPBITMAPINFO)HDib1; 以上内 容我们 主要 是讲述了 alpha图像混合 的 实现原 理和方法 , 其实 读 者大可不 必自 己写这么 多代 码, 微 软 给我们 提供了 一个名为 AlphaBlend() 的函 数,它 就可以 直接实 现图像合成 的 功能, 具体怎 么使用 ,还请 读者参考 MSDN。 3.3.3.3.采集卡的操作 图像处理 所涉及 的 应用领域有 军 事应用 、医学 诊断、 工业监控、 物 体的自 动分检识 别等 等,这些 应用 系统无不 需要 计算机提供 实 时动态 ,效果 逼 真的 图像。 目 前获取 实时 图像一 般都需 要在计算 机内 部安装 一个图 像采集卡 , 用 来实现 CCD端获取 的模拟 图像的 数字 化转换 。 笔者 结合自 己在项 目 开 发中 积累的 一些经验, 谈一 下如 何操作 图像采集卡 、 然后再此基 础 基础上再实 现一 些特殊处 理。 笔者 的摄像机采 用 台湾 BENTECHINDUSTRIAL 有限 公司生 产的 CV-155L 黑白 摄像机 。 该 摄像机分辨 率为 752x582。 图 象采集卡 采用 的是 北 京中 科院科技嘉 公 司开发的 基于 PCI 总线的 CA-MPE 1000 黑白 图象采集卡 。 一般情 况下, 使用图 像采集卡 分三步 ,首 先安装 采集卡 的驱动 程序, 并将 虚拟驱动 文件 VxD.vxd拷贝到 Windows 的SYSTEM目录 下;这时 候就 可以进入开 发状态 了 ,进入 VC开发平 台, 生成新的 项目 ,由于 生产厂 家为 图像采集卡 提供了以 mpew32.dll、mpew32.lib 命名 的库 文件, 库中 提供了 初始硬件、 采集图 像等 函数, 为使 用这些 函数, 需要在 新项目 上连 接该 动态库 ;最后 一步就 是采集图 像并显 示处理了 , 这一步 要设置系 统调 色板, 因为采集卡 提供的 是 裸图形 式,既 纯图像数据 , 没有图 像的规格和 调 色板 信息, 这些需 要开发者 自己 规定实 现,下 面是实 现的部 分代码 : lpData1=(LPVOID)((LPBYTE)lpBi->bmiColors+256*sizeof(RGBQUAD)); //通过 alpha值合并两 张图 象的 像素值 for ( int i=0;i< lpBi->biWidth; i++ ) for(int j=0;j< lpBi->biHeight;j++ ) { //套用 alpha图像混合 公 式; *(lpData1+i*WIDTHBYTES(lpBi->biWidth*8)+j)=(*(lpData1+i*WIDTHBYTES(lpBi->biWidth*8)+j)*(255-alpha)+ *(lpData2+i*WIDTHBYTES(lpBi->biWidth*8)+j)*alpha)/255; } return lpData1; } //////////////////////////////////////// CTestView::CTestView() { W32_Init_MPE1000();//初始 化采集卡 W32_Modify_Contrast(50);//下面的 函数是 为了 对采集卡 进行 预设置 W32_Modify_Brightness(45);//设置亮度 W32_Set_HP_Value(945);//设置水 平采集点 数 wCurrent_Frame = 1;//当前 帧为 1,获取 的图 像就是 从这帧 取得的 // 设置采集 信 号源, 仅对 MPE1000有效 W32_Set_Input_Source(1);//该图 像采集卡 支持三路 视频, 目 前采集的 图像来自 第 二路输入端 ; W32_Set_PAL_Range(1250, 1024);//设置水 平采集范 围 W32_Set_VGA_Mode ( 1 ); 采用 PAL制式; wGrabWinX1 = 0; // 采集窗 口的 左上角 的坐标 wGrabWinY1 = 0; firstTime=TRUE; //第一 次采集; bGrabMode = FRAME;//抓图 模式为 ?格式; bZipMode = ZIPPLE;//压缩 模式为 ZIPPLE; lpDib=NULL;//存放获取 的 图像数据 缓冲 区为空 ; } //////////////////////////////////////// CTestView::~CTestView() { W32_Close_MPE1000();//关闭 采集卡 } //////////////////////////////////////////// void CTestView::OnGraboneframe()//显示采集 的 图象, 双击鼠 标采集停 止 { //TODO: Add your command handler code here wCurrent_Frame = 1; // 设置采集 目 标为内 存 W32_CACardParam (AD_SETGRABDEST,CA_GRABMEM); // 启动 采集 if (lpDib != NULL)//如果 图像缓冲 区不 为空, 释放该 缓冲区 ; { GlobalUnlock( hglbDIB ); GlobalFree( hglbDIB ); } //为采集到 的 图像数据 分配内 存; hglbDIB=GlobalAlloc(GHND, (DWORD)wImgWidth*(DWORD)wImgHeight ); lpDib = (BYTE*)GlobalLock( hglbDIB );//得到 图像数据 的指 针; hdc = GetDC()->GetSafeHdc( );//获取 视图的 设备上下 文句 柄; if(lpDib != NULL) { cxDib = wImgWidth; cyDib = wImgHeight; SetLogicPal( hdc, cxDib, cyDib, 8 );//设置调 色板 ; SetStretchBltMode (hdc, COLORONCOLOR); bGrabMark = TRUE; while (bGrabMark == TRUE) { if(msg.message==WM_LBUTTONDBLCLK) //分析是 否为 鼠标双 击消息 ; bGrabMark = FALSE;//如为 鼠标双 击消息 ,停止 采集图 象; W32_ReadXMS2Buf (wCurrent_Frame,lpDib) ;//将图 象数据 读入到 图像数据 缓冲 区; SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0, 0, cyDib, (LPSTR) lpDib, bmi, DIB_RGB_COLORS);//显示图 像; } // 停止 采集 W32_CAStopCapture(); ::ReleaseDC( GetSafeHwnd(), hdc ); return ; } //将下 面这个函 数添加 在视图 类的 CTestView::OnSize() 函数中 ,就可以 对系 统的调 色板进行 设置。 void WINAPI InitLogicPal( HDC hdc , short width, short height, WORD bitCount ) { int j, i; short cxDib, cyDib; LOGPALETTE* pLogPal; j=256 ; if((pLogPal=(LOGPALETTE*)malloc(sizeof(LOGPALETTE)+ (j*sizeof(PALETTEENTRY)))) == NULL) return ; pLogPal->palVersion=0x300; //设置调 色版的 颜色信 息; pLogPal->palNumEntries=j; for (i=0;i pLogPal->palPalEntry[i].peRed = i ; { pLogPal->palPalEntry[i].peGreen = i ; pLogPal->palPalEntry[i].peBlue = i ; pLogPal->palPalEntry[i].peFlags = 0; } hPal = ::CreatePalette(pLogPal); //创建调 色板 ; delete pLogPal; ::SelectPalette(hdc,hPal,0);//系统 实现调 色板; ::RealizePalette(hdc); cxDib = width; cyDib = height; if ((bmi = (BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER) + j*sizeof(RGBQUAD))) == NULL) return ; //定义 图

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档