【学习图像处理】之实验四——图像编码之LZW编码

前言

这应该是本学期图像处理的最后一次实验内容了,虽然还有选做的算数编码,那个我还在研究中,目前的进度是卡在如何解决运算量过大导致的运行时间过长问题。
LZW编码看起来简单,实际上遇到的问题还是不少,踩了很多坑之后才解决,记录一下我的经验,希望对大家有帮助。(由于一些优化理论还没有学习,因此后续可能还会进行优化)

LZW编码

1、简介

LZW编码和上一次我们实现的赫夫曼编码一样属于无损编码的类别。不同于赫夫曼编码根据频率的不同使用变长编码,使得高概率短编码,低概率长编码,最终达到总长度变短的目的;LZW编码使用的是固定长编码,通过将几个值合并在一起使用一个编码表示来降低总长度。举个例子:原本120个像素,每个像素8bit;现在通过将其中重复出现的像素序列(比如在灰度图中可能有大量连续的0,那么我就可以把类似00赋予一个新的编码,只要编码的位数少于 2 ∗ 8 = 16 b i t 2*8=16bit 28=16bit,就可以达到压缩的目的)用一个合适长度的新编码表示,最终达到,比方说, 100 ∗ 9 b i t = 900 b i t 100*9bit=900bit 1009bit=900bit的总长,这样相比原本的960bit就实现了压缩的目的。

2、原理

上面说的是一个实现压缩的思路,然而实际上LZW编码的实现原理更加简单,这篇博客给出了非常完整的例子和词典的扩充演示,并且还特意指出了解码时可能出现的一种特例情况,建议完整的看一遍,不然只靠空口说原理,理解起来总还是有难度的。

不过大家也发现了,网上几乎所有的LZW编码原理讲解,包括演示用的都是字母,“ABAAD”和“01003”这样的像素值组合总还是不一样的。像素值具有一个非常不好的特点,就是歧义性;“100”可以解释成一个像素,也可以解释成多个像素的组合。

因此即便我只用了一个小时就参照另一篇博客实现了对于输入字符串的LZW编解码,也通过了老师给出的演示样例。但是当真正应用于图像编解码时,出现了两个问题:一)解码歧义性;二)反向压缩(变得更大了)。

3、两个问题

歧义性的问题在盖哥的帮助下得到了解决。我们用char(你可以理解为ASCII码)来代替int型数值表示像素值。这样一来在解码得到的字符串的每一位(每一个char)就恰好对应一个像素值,歧义性的问题也就不存在了。(解释的更清楚一些,就是因为char恰好是一个字节的大小,不考虑符号位的话恰好可以表示0-255之间的数,而我们的像素数据本质上也是Byte型的。)

反向压缩的问题实际上才是本次实验的重点,在谷歌上查阅到了一本参考书籍为我们提供了思路。

首先我们说一下反向压缩的原因:主要原因是我们使用了int来表示编码,而一个int本身占用4个字节,根据编码的结果看来,最高只有9万多,这意味着有一整个字节被完全浪费,还有一个字节处于极少使用的情况。

我们可以使用更小的变量类型,例如两个字节的short,但是如果不改变实现方法,那显然又会有不够用的情况。这里就用到了书中给到的第一个思路:定长码表。我们最初实现的时候码表是无限扩增的,因此需要足够大的位数来表示编码;假设我们给定码表的长度是65536( 2 16 2^{16} 216),那么我们的编码就不会超过short可以表示的范围。

你可能会问码表满了图像还没编码完怎么办呢?重置初始值(只有0-255的编码)。我们应该明确一点,LZW的编码和解码都是按照顺序动态完成的,因此不会存在清空之后对应不上的问题。

当然至此问题还是没有解决。实际上,图片的不同将会导致LZW编码的最佳码表长度发生变化,使用short可以实现部分图像的压缩(我们常用的《柿子椒》可以达到最佳压缩),但是对于另一部分效果不明显,甚至依然会出现反向压缩的问题(例如《照相的男人》)。

对此,参考书中提供的第二个思路,也就是最开头我举的那个例子,给了我想法:使用bitset这样一个模板类型,它可以帮你建立一个位数自定义的变量类型,从而实现一个更灵活的LZW编码。不过也不是没有问题,bitset是一个固定长的变量,也就是说你只可以定义为类似bitset<10>,而不可以定义为bitset<n>,这样带来的坏处就是我们必须重新编译才可以得到用不同位数进行LZW编码的结果。你可以尝试用vector<bool>来解决这个问题。

一、实验内容

将⼀幅给定的灰度图象进行编解码。编解码方法可以是算术编解码、LZW 编解码方法中的⼀种。

又是一个字数很短的要求,往往越是这样越难切入。这一次,我将LZW编解码的相关内容写成了一个类主要之前写高精度习惯了,并且在编解码过程中调用了部分实验三中编写的函数,如果你对那些部分有疑问可以看我对于赫夫曼编码的博客。

二、代码实现与分析

1、LZW类定义
class LZW	//利用LZW算法进行编码或解码
{
public:
	LZW();								//构造函数,初始化私有成员
	~LZW();
	void printDict();					//输出字典
	void outputDict(const char*);		//将字典写到文件里
	void decoding(vector<bitset<16>>);	//解码函数
	void encoding(string);				//编码函数
	vector<bitset<16>> get_encode_result();
	string get_decode_result();		//获取解码结果
	void dict_reset();				//当字典满了之后需要重置编码表(清空)
	bool dict_is_full();			//判断字典满了
	void decode_table_reset(map<bitset<16>, string, bitset_comparer<16>>&);			//解码表重置

private:
	map<string, bitset<16>> dictionary;		//编码用的字典
	string last_code;				//用来记录上次编解码的字符
	int code_value;					//新增加编码的值
	int dic_size;					//字典最大容量512,满了之后清空
	vector<bitset<16>> encode_result;	//用来保存编码结果
	string decode_result;				//解码结果
};
1.1 私有成员

一个类的定义过程应该是先明确私有成员,再不断添加相关方法。我们编码解码需要一个字典,因此用一个map来实现。根据之前给过的参考博客中的写法,我们需要last_code来保存上一次的编解码内容。根据我们改进后的算法,需要给字典一个长度计数dic_size,方便判断何时重置。当然还有每次加入的新编码的值code_value
至于把编解码结果加入到私有成员中,这只是我的编码喜好,方便debug的时候检查。实际上在函数中返回一个变量就可以了,也许还有助于减小内存开销。
如果你仔细观察,会发现解码的时候用的map有三个参数,即map<bitset<16>, string, bitset_comparer<16>>。这是因为bitset本身是没有对operator<进行重载的,这导致它不能作为map的key值(因为map是一个红黑树结构)。所以第三个参数是我自己写的对于小于运算的重载。

1.2 公有成员

下面对构造函数、编码、解码函数进行解释,其他的辅助功能很容易实现。

1.2.1 构造函数
LZW::LZW()
{
	int i;
	char c;	//这里一定要用一个char来表示,因为char的一字节大小恰好可以表示0-255,而to_string(int)生成的是字符串,将使我们无法还原编码
	string key = "";
	bitset<16> value;
	for (i = 0; i < 256; i++)	//初始化0-255像素值的编码
	{
		key = "";
		c = i;
		key += c;	//这里一定要使用空string拼接c,如果用c+""就会错误
		value = i;
		dictionary.insert(make_pair(key, value));
	}
	last_code = "";
	code_value = 256;
	dic_size = 256;		//初始大小为256
}

从这里开始我就疯狂踩坑了,首先是key值的赋予。最初的时候我使用to_string()这个函数,但是这样是得到一个字符串,例如to_string(128)的结果就是"128",这样就会最终造成我们无法解码的问题。
用一个char来转换解决了这个问题,之前也说过了。但是由于我们后需要合并像素值,所以肯定还是要用string类型来做,所以就涉及到一个charstring的转换问题。
我想当然的认为可以用如下的表达式进行转换:c+"",然后正如下图给出的测试,是不行的。。。

char2string测试
所以还是乖乖地用一个临时变量吧!

1.2.2 LZW编码
void LZW::encoding(string s)
{
	string::iterator s_it;	//*s_it是一个char
	last_code = "";			//初始状态,上一次编码置空
	string null = "";
	bitset<16> value;
	for (s_it = s.begin(); s_it != s.end(); s_it++)
	{
		if (dict_is_full())		//字典满了就重置
		{
			dict_reset();
		}

		if (dictionary.find(last_code + *s_it) != dictionary.end())
			last_code = last_code + *s_it;		//直到找到最长前缀,进入else
		else
		{
			encode_result.push_back(dictionary[last_code]);
			value = code_value++;
			dictionary.insert(make_pair(last_code + *s_it, value));
			dic_size++;
			last_code = null + *s_it;
		}
	}

	if (last_code != "")	//最后一次的输出
	{
		encode_result.push_back(dictionary[last_code]);
	}
}

再次放上参考博客的链接,我这里只不过是把博客中的java代码改写成了c++而已,整个处理的过程没有任何变动。说一下dict_reset(),这个函数是将我们的码表重置到构造函数中新建的那样,包括code_valuedic_size都要重置,但是last_code不重置,不重置,不重置!

1.2.3 LZW解码
void LZW::decoding(vector<bitset<16>> s)
{
	map<bitset<16>, string, bitset_comparer<16>> decode_table;	//建立一个键值互换的表用来加速解码
	decode_table_reset(decode_table);

	last_code = "";
	string c = "";	//c for current,当前解码字符
	bitset<16> key;
	vector<bitset<16>>::iterator it;
	for (it = s.begin(); it != s.end(); it++)
	{
		if (dic_size % 65536 == 0)	//注意这个数值是pow(2,n),n是bitset位数
		{
			decode_table_reset(decode_table);
		}
		if (decode_table.find(*it) != decode_table.end())
			c = decode_table[*it];
		else
		{
			if ((*it).to_ulong() == code_value)	//当出现连续的同字符编码,可能会超过字典已有范围
				c += c[0];
			else
			{
				cout << "当前编码:" << (*it).to_ulong() << endl;
				cout << "code_value:" << code_value << endl;
				cout << "编码错误" << endl;
				exit(1);
			}
		}
		if (last_code != "")
		{
			key = code_value++;
			decode_table.insert(make_pair(key, last_code + c[0]));
			dic_size++;
		}
		decode_result += c;
		last_code = c;
	}
}

解码的时候我们需要一个键值互换的map来实现,这里我开头也提到了,bitset本身是不能作为key值构造字典的,需要重载小于运算符,具体实现见文末的传送门。

2、编码调用函数

上面类定义中我们完成了编解码的运算部分,但我们还需要解决那两个函数的形参获取问题,以及文件写入、读取问题。因此下面分别写出编码调用函数和解码调用函数

bool LZWEncode(BMPFILE &src, const char* cFilename)
{
	LZW encode_lzw;

	string encode_data = "";
	string null = "";
	char c;
	int i, j;
	for (i = 0; i < src.imagew; i++)
		for (j = 0; j < src.imageh; j++)
		{
			null = "";
			c = src.pDataAt(i)[j];
			encode_data += null + c;
		}

	encode_lzw.encoding(encode_data);

	vector<bitset<16>> encode_result = encode_lzw.get_encode_result();

	if (!writeHeader(src, cFilename))
	{
		cout << "bmp头部写入失败!" << endl;
		return false;
	}
	else
	{
		cout << "bmp头部写入成功!" << endl;
	}

	string result = "";	//由于bitset直接写入文件是按照ASCII码,因此我们要调用writeBit才行

	vector<bitset<16>>::iterator it;
	for (it = encode_result.begin(); it != encode_result.end(); it++)
	{
		result += (*it).to_string();
	}

	if (!writeBit(result, cFilename))
	{
		cout << "像素数据写入异常!" << endl;
		return false;
	}
	else
	{
		cout << "像素数据写入成功!" << endl;
		return true;
	}
}

编码的数据还是比较容易获得的,我们遍历一次图像,将像素值转换成char后合并成一个大字符串即可。
文件头、信息头、调色板写入文件和上一次实验中一致,直接调用函数即可。但是这一次我们的像素数据编码结果是一个bitset动态数组,对于32位及以下的bitset变量,其占用空间为4字节,而不是我们定义的bit数。因此我们需要将这些bitset再转换成'1' '0'字符串,然后调用writeBit将其按位写入文件。

3、解码调用函数
bool LZWDecode(BMPFILE &des, const char* cFilename)
{
	LZW decode_lzw;

	if (!readHeader(des, cFilename))	//首先读取头部
	{
		cout << "bmp头部读取失败!" << endl;
		return false;
	}
	else
		cout << "bmp头部读取成功" << endl;

	FILE *f;
	if ((f = fopen(cFilename, "r+b")) == NULL)
	{
		cout << "无法打开文件!" << endl;
		return false;
	}
	fseek(f, sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD), SEEK_SET);	//指向像素数据包

	char buffer;
	int flag;
	string read_string = "";
	while ((flag = fread(&buffer, sizeof(char), 1, f) != 0))
	{
		read_string += buffer;	//我们先将编码数据读入一个字符串
	}
	fclose(f);

	string::iterator it;
	string binary_data = "";
	for (it = read_string.begin(); it != read_string.end(); it++)
	{
		binary_data += Byte2Binary(*it);	//然后把每一位(char)转换成二进制,再合并成一个字符串
	}
	read_string.clear();

	int i, j = 15;		//这里j的取值需要根据bitset<>的大小决定
	string code = "";
	vector<bitset<16>> decode_data;
	for (i = 0; i < binary_data.size(); i++)
	{
		code += binary_data[i];
		j--;

		if (j < 0)
		{
			bitset<16> temp(code);
			decode_data.push_back(temp);	//把二进制组成的字符串,按位数转成bitset类型,构成待解码数据
			code = "";
			j = 15;
		}
	}
	binary_data.clear();

	decode_lzw.decoding(decode_data);

	string decode_result = decode_lzw.get_decode_result();
	string::iterator result_it = decode_result.begin();

	for (int i = 0; i < des.imagew; i++)
		for (int j = 0; j < des.imageh; j++)
		{
			des.pDataAt(i)[j] = *result_it;	//将像素值写入
			result_it++;
		}

	des.SaveBMPFILE("result.bmp");
	return true;
}

解码的形参变量获取稍微需要一点转换:
考虑到我们定义的bitset变量位数不一定恰好能够按字节读取出来(比如bitset<9>,第一个字节有8bit,下一个字节还有1个bit,直接fread是读取不了的),因此我们首先一个字节一个字节的将全部像素数据读取出来,并合并到一个字符串中,read_string
然后,将这个字符串中的每个字符,调用Byte2Binary这个上次实验中编写的函数,转换为二进制后,再次合并到一个字符串中,binary_data
最后对这个字符串进行遍历,每次取出你定义位数的子串构造一个bitset对象,添加到解码数据组中。

解码结束之后,只需要一个char对应一个像素值的赋值即可。

结语

LZW编码的原理简单易懂,实现并不复杂。再加上,上一次实验中编写了许多有关输入输出编码的函数,因此总体来说本次实验的编程负担并没有上次重。
但是这不意味着很轻易就能够实现,我花费了三天从一个又一个坑中跳出来,这种debug的经验是一种收获。
本次实验完整的项目文件与代码可见我的gitee

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书是关于计算机科学与工程领域的基础性研究科目之一——数据结构与算法的专著。 本书在简要回顾了基本的C++ 程序设计概念的基础上,全面系统地介绍了队列、堆栈、树、图等基本数据结构,以及贪婪算法、分而治之算法、分枝定界算法等多种算法设计方法,为数据结构与算法的继续学习和研究奠定了一个坚实的基础。更为可贵的是,本书不仅仅介绍了理论知识,还提供了50多个应用实例及600多道练习题。 本书内容广博权威,结构清晰合理,是一本全新的有关数据结构与算法的教材,对于计算机科学与工程领域的从业人员也是一本很好的参考书。 目 录 译者序 前言 第一部分 预备知识 第1章 C++程序设计 1 1.1 引言 1 1.2 函数与参数 2 1.2.1 传值参数 2 1.2.2 模板函数 3 1.2.3 引用参数 3 1.2.4 常量引用参数 4 1.2.5 返回值 4 1.2.6 递归函数 5 1.3 动态存储分配 9 1.3.1 操作符new 9 1.3.2 一维数组 9 1.3.3 异常处理 10 1.3.4 操作符delete 10 1.3.5 二维数组 10 1.4 类 13 1.4.1 类Currency 13 1.4.2 使用不同的描述方法 18 1.4.3 操作符重载 20 1.4.4 引发异常 22 1.4.5 友元和保护类成员 23 1.4.6 增加#ifndef, #define和#endif语句 24 1.5 测试与调试 24 1.5.1 什么是测试 24 1.5.2 设计测试数据 26 1.5.3 调试 28 1.6 参考及推荐读物 29 第2章 程序性能 30 2.1 引言 30 2.2 空间复杂性 31 2.2.1 空间复杂性的组成 31 2.2.2 举例 35 2.3 时间复杂性 37 2.3.1 时间复杂性的组成 37 2.3.2 操作计数 37 2.3.3 执行步数 44 2.4 渐进符号(O、 健?、 o) 55 2.4.1 大写O符号 56 2.4.2 椒?58 2.4.3 符号 59 2.4.4 小写o符号 60 2.4.5 特性 60 2.4.6 复杂性分析举例 61 2.5 实际复杂性 66 2.6 性能测量 68 2.6.1 选择实例的大小 69 2.6.2 设计测试数据 69 2.6.3 进行实验 69 2.7 参考及推荐读物 74 第二部分 数据结构 第3章 数据描述 75 3.1 引言 75 3.2 线性表 76 3.3 公式化描述 77 3.3.1 基本概念 77 3.3.2 异常类NoMem 79 3.3.3 操作 79 3.3.4 评价 83 3.4 链表描述 86 3.4.1 类ChainNode 和Chain 86 3.4.2 操作 88 3.4.3 扩充类Chain 91 3.4.4 链表遍历器类 92 3.4.5 循环链表 93 3.4.6 与公式化描述方法的比较 94 3.4.7 双向链表 95 3.4.8 小结 96 3.5 间接寻址 99 3.5.1 基本概念 99 3.5.2 操作 100 3.6 模拟指针 102 3.6.1 SimSpace的操作 103 3.6.2 采用模拟指针的链表 106 3.7 描述方法的比较 110 3.8 应用 111 3.8.1 箱子排序 111 3.8.2 基数排序 116 3.8.3 等价类 117 3.8.4 凸包 122 3.9 参考及推荐读物 127 第4章 数组和矩阵 128 4.1 数组 128 4.1.1 抽象数据类型 128 4.1.2 C++数组 129 4.1.3 行主映射和列主映射 129 4.1.4 类Array1D 131 4.1.5 类Array2D 133 4.2 矩阵 137 4.2.1 定义和操作 137 4.2.2 类Matrix 138 4.3 特殊矩阵 141 4.3.1 定义和应用 141 4.3.2 对角矩阵 143 4.3.3 三对角矩阵 144 4.3.4 三角矩阵 145 4.3.5 对称矩阵 146 4.4 稀疏矩阵 149 4.4.1 基本概念 149 4.4.2 数组描述 149 4.4.3 链表描述 154 第5章 堆栈 161 5.1 抽象数据类型 161 5.2 派生类和继承 162 5.3 公式化描述 163 5.3.1 Stack的效率 164 5.3.2 自定义Stack 164 5.4 链表描述 166 5.5 应用 169 5.5.1 括号匹配 169 5.5.2 汉诺塔 170 5.5.3 火车车厢重排 172 5.5.4 开关盒布线 176 5.5.5 离线等价类问题 178 5.5.6 迷宫老鼠 180 5.6 参考及推荐读物 188 第6章 队列 189 6.1 抽象数据类型 189 6.2 公式化描述 190 6.3 链表描述 194 6.4 应用 197 6.4.1 火车车厢重排 197 6.4.2 电路布线 201 6.4.3 识别图元 204 6.4.4 工厂仿真 206 6.5 参考及推荐读物 217 第7章 跳表和散列 218 7.1 字典 218 7.2 线性表描述 219 7.3 跳表描述 222 7.3.1 理想情况 222 7.3.2 插入和删除 223 7.3.3 级的分配 224 7.3.4 类SkipNode 224 7.3.5 类SkipList 225 7.3.6 复杂性 229 7.4 散列表描述 229 7.4.1 理想散列 229 7.4.2 线性开型寻址散列 230 7.4.3 链表散列 234 7.5 应用——文本压缩 238 7.5.1 LZW压缩 239 7.5.2 LZW压缩的实现 239 7.5.3 LZW解压缩 243 7.5.4 LZW解压缩的实现 243 7.6 参考及推荐读物 247 第8章 二叉树和其他树 248 8.1 树 248 8.2 二叉树 251 8.3 二叉树的特性 252 8.4 二叉树描述 253 8.4.1 公式化描述 253 8.4.2 链表描述 254 8.5 二叉树常用操作 256 8.6 二叉树遍历 256 8.7 抽象数据类型BinaryTree 259 8.8 类BinaryTree 260 8.9 抽象数据类型及类的扩充 263 8.9.1 输出 263 8.9.2 删除 264 8.9.3 计算高度 264 8.9.4 统计节点数 265 8.10 应用 265 8.10.1 设置信号放大器 265 8.10.2 在线等价类 268 8.11 参考及推荐读物 275 第9章 优先队列 276 9.1 引言 276 9.2 线性表 277 9.3 堆 278 9.3.1 定义 278 9.3.2 最大堆的插入 279 9.3.3 最大堆的删除 279 9.3.4 最大堆的初始化 280 9.3.5 类MaxHeap 281 9.4 左高树 285 9.4.1 高度与宽度优先的最大及最小 左高树 285 9.4.2 最大HBLT的插入 287 9.4.3 最大HBLT的删除 287 9.4.4 合并两棵最大HBLT 287 9.4.5 初始化最大HBLT 289 9.4.6 类MaxHBLT 289 9.5 应用 293 9.5.1 堆排序 293 9.5.2 机器调度 294 9.5.3 霍夫曼编码 297 9.6 参考及推荐读物 302 第10章 竞?303 10.1 引言 303 10.2 抽象数据类型WinnerTree 306 10.3 类WinnerTree 307 10.3.1 定义 307 10.3.2 类定义 307 10.3.3 构造函数、析构函数及Winner 函数 308 10.3.4 初始化赢者树 308 10.3.5 重新组织比赛 310 10.4 输者树 311 10.5 应用 312 10.5.1 用最先匹配法求解箱子装载 问题 312 10.5.2 用相邻匹配法求解箱子装载 问题 316 第11章 搜索树 319 11.1 二叉搜索树 320 11.1.1 基本概念 320 11.1.2 抽象数据类型BSTree和 IndexedBSTree 321 11.1.3 类BSTree 322 11.1.4 搜索 322 11.1.5 插入 323 11.1.6 删除 324 11.1.7 类DBSTree 326 11.1.8 二叉搜索树的高度 327 11.2 AVL树 328 11.2.1 基本概念 328 11.2.2 AVL树的高度 328 11.2.3 AVL树的描述 329 11.2.4 AVL搜索树的搜索 329 11.2.5 AVL搜索树的插入 329 11.2.6 AVL搜索树的删除 332 11.3 红-黑树 334 11.3.1 基本概念 334 11.3.2 红-黑树的描述 336 11.3.3 红-黑树的搜索 336 11.3.4 红-黑树的插入 336 11.3.5 红-黑树的删除 339 11.3.6 实现细节的考虑及复杂性分析 343 11.4 B-树 344 11.4.1 索引顺序访问方法 344 11.4.2 m 叉搜索树 345 11.4.3 m 序B-树 346 11.4.4 B-树的高度 347 11.4.5 B-树的搜索 348 11.4.6 B-树的插入 348 11.4.7 B-树的删除 350 11.4.8 节点结构 353 11.5 应用 354 11.5.1 直方图 354 11.5.2 用最优匹配法求解箱子装载 问题 357 11.5.3 交叉分布 359 11.6 参考及推荐读物 363 第12章 图 365 12.1 基本概念 365 12.2 应用 366 12.3 特性 368 12.4 抽象数据类型Graph和Digraph 370 12.5 无向图和有向图的描述 371 12.5.1 邻接矩阵 371 12.5.2 邻接压缩表 373 12.5.3 邻接链表 374 12.6 网络描述 375 12.7 类定义 376 12.7.1 不同的类 376 12.7.2 邻接矩阵类 377 12.7.3 扩充Chain类 380 12.7.4 类LinkedBase 381 12.7.5 链接类 382 12.8 图的遍历 386 12.8.1 基本概念 386 12.8.2 邻接矩阵的遍历函数 387 12.8.3 邻接链表的遍历函数 388 12.9 语言特性 389 12.9.1 虚函数和多态性 389 12.9.2 纯虚函数和抽象类 391 12.9.3 虚基类 391 12.9.4 抽象类和抽象数据类型 393 12.10 图的搜索算法 394 12.10.1 宽度优先搜索 394 12.10.2 类Network 395 12.10.3 BFS的实现 395 12.10.4 BFS的复杂性分析 396 12.10.5 深度优先搜索 397 12.11 应用 399 12.11.1 寻找路径 399 12.11.2 连通图及其构件 400 12.11.3 生成树 402 第三部分 算法设计方法 第13章 贪婪算法 405 13.1 最优化问题 405 13.2 算法思想 406 13.3 应用 409 13.3.1 货箱装船 409 13.3.2 0/1背包问题 410 13.3.3 拓扑排序 412 13.3.4 二分覆盖 415 13.3.5 单源最短路径 421 13.3.6 最小耗费生成树 424 13.4 参考及推荐读物 433 第14章 分而治之算法 434 14.1 算法思想 434 14.2 应用 440 14.2.1 残缺棋盘 440 14.2.2 归并排序 443 14.2.3 快速排序 447 14.2.4 选择 452 14.2.5 距离最近的点对 454 14.3 解递归方程 462 14.4 复杂性的下限 463 14.4.1 最小最大问题的下限 464 14.4.2 排序算法的下限 465 第15章 动态规划 467 15.1 算法思想 467 15.2 应用 469 15.2.1 0/1背包问题 469 15.2.2 图像压缩 471 15.2.3 矩阵乘法链 476 15.2.4 最短路径 480 15.2.5 网络的无交叉子集 483 15.2.6 元件折叠 486 15.3 参考及推荐读物 491 第16章 回溯 492 16.1 算法思想 492 16.2 应用 496 16.2.1 货箱装船 496 16.2.2 0/1背包问题 503 16.2.3 最大完备子图 506 16.2.4 旅行商问题 508 16.2.5 电路板排列 510 第17章 分枝定界 516 17.1 算法思想 516 17.2 应用 519 17.2.1 货箱装船 519 17.2.2 0/1背包问题 526 17.2.3 最大完备子图 528 17.2.4 旅行商问题 529 17.2.5 电路板排列 532
本书是关于计算机科学与工程领域的基础性研究科目之一——数据结构与算法的专著。 本书在简要回顾了基本的C++ 程序设计概念的基础上,全面系统地介绍了队列、堆栈、树、图等基本数据结构,以及贪婪算法、分而治之算法、分枝定界算法等多种算法设计方法,为数据结构与算法的继续学习和研究奠定了一个坚实的基础。更为可贵的是,本书不仅仅介绍了理论知识,还提供了50多个应用实例及600多道练习题。 本书内容广博权威,结构清晰合理,是一本全新的有关数据结构与算法的教材,对于计算机科学与工程领域的从业人员也是一本很好的参考书 目 录 译者序 前言 第一部分 预备知识 第1章 C++程序设计 1 1.1 引言 1 1.2 函数与参数 2 1.2.1 传值参数 2 1.2.2 模板函数 3 1.2.3 引用参数 3 1.2.4 常量引用参数 4 1.2.5 返回值 4 1.2.6 递归函数 5 1.3 动态存储分配 9 1.3.1 操作符new 9 1.3.2 一维数组 9 1.3.3 异常处理 10 1.3.4 操作符delete 10 1.3.5 二维数组 10 1.4 类 13 1.4.1 类Currency 13 1.4.2 使用不同的描述方法 18 1.4.3 操作符重载 20 1.4.4 引发异常 22 1.4.5 友元和保护类成员 23 1.4.6 增加#ifndef, #define和#endif语句 24 1.5 测试与调试 24 1.5.1 什么是测试 24 1.5.2 设计测试数据 26 1.5.3 调试 28 1.6 参考及推荐读物 29 第2章 程序性能 30 2.1 引言 30 2.2 空间复杂性 31 2.2.1 空间复杂性的组成 31 2.2.2 举例 35 2.3 时间复杂性 37 2.3.1 时间复杂性的组成 37 2.3.2 操作计数 37 2.3.3 执行步数 44 2.4 渐进符号(O、 健?、 o) 55 2.4.1 大写O符号 56 2.4.2 椒?58 2.4.3 符号 59 2.4.4 小写o符号 60 2.4.5 特性 60 2.4.6 复杂性分析举例 61 2.5 实际复杂性 66 2.6 性能测量 68 2.6.1 选择实例的大小 69 2.6.2 设计测试数据 69 2.6.3 进行实验 69 2.7 参考及推荐读物 74 第二部分 数据结构 第3章 数据描述 75 3.1 引言 75 3.2 线性表 76 3.3 公式化描述 77 3.3.1 基本概念 77 3.3.2 异常类NoMem 79 3.3.3 操作 79 3.3.4 评价 83 3.4 链表描述 86 3.4.1 类ChainNode 和Chain 86 3.4.2 操作 88 3.4.3 扩充类Chain 91 3.4.4 链表遍历器类 92 3.4.5 循环链表 93 3.4.6 与公式化描述方法的比较 94 3.4.7 双向链表 95 3.4.8 小结 96 3.5 间接寻址 99 3.5.1 基本概念 99 3.5.2 操作 100 3.6 模拟指针 102 3.6.1 SimSpace的操作 103 3.6.2 采用模拟指针的链表 106 3.7 描述方法的比较 110 3.8 应用 111 3.8.1 箱子排序 111 3.8.2 基数排序 116 3.8.3 等价类 117 3.8.4 凸包 122 3.9 参考及推荐读物 127 第4章 数组和矩阵 128 4.1 数组 128 4.1.1 抽象数据类型 128 4.1.2 C++数组 129 4.1.3 行主映射和列主映射 129 4.1.4 类Array1D 131 4.1.5 类Array2D 133 4.2 矩阵 137 4.2.1 定义和操作 137 4.2.2 类Matrix 138 4.3 特殊矩阵 141 4.3.1 定义和应用 141 4.3.2 对角矩阵 143 4.3.3 三对角矩阵 144 4.3.4 三角矩阵 145 4.3.5 对称矩阵 146 4.4 稀疏矩阵 149 4.4.1 基本概念 149 4.4.2 数组描述 149 4.4.3 链表描述 154 第5章 堆栈 161 5.1 抽象数据类型 161 5.2 派生类和继承 162 5.3 公式化描述 163 5.3.1 Stack的效率 164 5.3.2 自定义Stack 164 5.4 链表描述 166 5.5 应用 169 5.5.1 括号匹配 169 5.5.2 汉诺塔 170 5.5.3 火车车厢重排 172 5.5.4 开关盒布线 176 5.5.5 离线等价类问题 178 5.5.6 迷宫老鼠 180 5.6 参考及推荐读物 188 第6章 队列 189 6.1 抽象数据类型 189 6.2 公式化描述 190 6.3 链表描述 194 6.4 应用 197 6.4.1 火车车厢重排 197 6.4.2 电路布线 201 6.4.3 识别图元 204 6.4.4 工厂仿真 206 6.5 参考及推荐读物 217 第7章 跳表和散列 218 7.1 字典 218 7.2 线性表描述 219 7.3 跳表描述 222 7.3.1 理想情况 222 7.3.2 插入和删除 223 7.3.3 级的分配 224 7.3.4 类SkipNode 224 7.3.5 类SkipList 225 7.3.6 复杂性 229 7.4 散列表描述 229 7.4.1 理想散列 229 7.4.2 线性开型寻址散列 230 7.4.3 链表散列 234 7.5 应用——文本压缩 238 7.5.1 LZW压缩 239 7.5.2 LZW压缩的实现 239 7.5.3 LZW解压缩 243 7.5.4 LZW解压缩的实现 243 7.6 参考及推荐读物 247 第8章 二叉树和其他树 248 8.1 树 248 8.2 二叉树 251 8.3 二叉树的特性 252 8.4 二叉树描述 253 8.4.1 公式化描述 253 8.4.2 链表描述 254 8.5 二叉树常用操作 256 8.6 二叉树遍历 256 8.7 抽象数据类型BinaryTree 259 8.8 类BinaryTree 260 8.9 抽象数据类型及类的扩充 263 8.9.1 输出 263 8.9.2 删除 264 8.9.3 计算高度 264 8.9.4 统计节点数 265 8.10 应用 265 8.10.1 设置信号放大器 265 8.10.2 在线等价类 268 8.11 参考及推荐读物 275 第9章 优先队列 276 9.1 引言 276 9.2 线性表 277 9.3 堆 278 9.3.1 定义 278 9.3.2 最大堆的插入 279 9.3.3 最大堆的删除 279 9.3.4 最大堆的初始化 280 9.3.5 类MaxHeap 281 9.4 左高树 285 9.4.1 高度与宽度优先的最大及最小 左高树 285 9.4.2 最大HBLT的插入 287 9.4.3 最大HBLT的删除 287 9.4.4 合并两棵最大HBLT 287 9.4.5 初始化最大HBLT 289 9.4.6 类MaxHBLT 289 9.5 应用 293 9.5.1 堆排序 293 9.5.2 机器调度 294 9.5.3 霍夫曼编码 297 9.6 参考及推荐读物 302 第10章 竞?303 10.1 引言 303 10.2 抽象数据类型WinnerTree 306 10.3 类WinnerTree 307 10.3.1 定义 307 10.3.2 类定义 307 10.3.3 构造函数、析构函数及Winner 函数 308 10.3.4 初始化赢者树 308 10.3.5 重新组织比赛 310 10.4 输者树 311 10.5 应用 312 10.5.1 用最先匹配法求解箱子装载 问题 312 10.5.2 用相邻匹配法求解箱子装载 问题 316 第11章 搜索树 319 11.1 二叉搜索树 320 11.1.1 基本概念 320 11.1.2 抽象数据类型BSTree和 IndexedBSTree 321 11.1.3 类BSTree 322 11.1.4 搜索 322 11.1.5 插入 323 11.1.6 删除 324 11.1.7 类DBSTree 326 11.1.8 二叉搜索树的高度 327 11.2 AVL树 328 11.2.1 基本概念 328 11.2.2 AVL树的高度 328 11.2.3 AVL树的描述 329 11.2.4 AVL搜索树的搜索 329 11.2.5 AVL搜索树的插入 329 11.2.6 AVL搜索树的删除 332 11.3 红-黑树 334 11.3.1 基本概念 334 11.3.2 红-黑树的描述 336 11.3.3 红-黑树的搜索 336 11.3.4 红-黑树的插入 336 11.3.5 红-黑树的删除 339 11.3.6 实现细节的考虑及复杂性分析 343 11.4 B-树 344 11.4.1 索引顺序访问方法 344 11.4.2 m 叉搜索树 345 11.4.3 m 序B-树 346 11.4.4 B-树的高度 347 11.4.5 B-树的搜索 348 11.4.6 B-树的插入 348 11.4.7 B-树的删除 350 11.4.8 节点结构 353 11.5 应用 354 11.5.1 直方图 354 11.5.2 用最优匹配法求解箱子装载 问题 357 11.5.3 交叉分布 359 11.6 参考及推荐读物 363 第12章 图 365 12.1 基本概念 365 12.2 应用 366 12.3 特性 368 12.4 抽象数据类型Graph和Digraph 370 12.5 无向图和有向图的描述 371 12.5.1 邻接矩阵 371 12.5.2 邻接压缩表 373 12.5.3 邻接链表 374 12.6 网络描述 375 12.7 类定义 376 12.7.1 不同的类 376 12.7.2 邻接矩阵类 377 12.7.3 扩充Chain类 380 12.7.4 类LinkedBase 381 12.7.5 链接类 382 12.8 图的遍历 386 12.8.1 基本概念 386 12.8.2 邻接矩阵的遍历函数 387 12.8.3 邻接链表的遍历函数 388 12.9 语言特性 389 12.9.1 虚函数和多态性 389 12.9.2 纯虚函数和抽象类 391 12.9.3 虚基类 391 12.9.4 抽象类和抽象数据类型 393 12.10 图的搜索算法 394 12.10.1 宽度优先搜索 394 12.10.2 类Network 395 12.10.3 BFS的实现 395 12.10.4 BFS的复杂性分析 396 12.10.5 深度优先搜索 397 12.11 应用 399 12.11.1 寻找路径 399 12.11.2 连通图及其构件 400 12.11.3 生成树 402 第三部分 算法设计方法 第13章 贪婪算法 405 13.1 最优化问题 405 13.2 算法思想 406 13.3 应用 409 13.3.1 货箱装船 409 13.3.2 0/1背包问题 410 13.3.3 拓扑排序 412 13.3.4 二分覆盖 415 13.3.5 单源最短路径 421 13.3.6 最小耗费生成树 424 13.4 参考及推荐读物 433 第14章 分而治之算法 434 14.1 算法思想 434 14.2 应用 440 14.2.1 残缺棋盘 440 14.2.2 归并排序 443 14.2.3 快速排序 447 14.2.4 选择 452 14.2.5 距离最近的点对 454 14.3 解递归方程 462 14.4 复杂性的下限 463 14.4.1 最小最大问题的下限 464 14.4.2 排序算法的下限 465 第15章 动态规划 467 15.1 算法思想 467 15.2 应用 469 15.2.1 0/1背包问题 469 15.2.2 图像压缩 471 15.2.3 矩阵乘法链 476 15.2.4 最短路径 480 15.2.5 网络的无交叉子集 483 15.2.6 元件折叠 486 15.3 参考及推荐读物 491 第16章 回溯 492 16.1 算法思想 492 16.2 应用 496 16.2.1 货箱装船 496 16.2.2 0/1背包问题 503 16.2.3 最大完备子图 506 16.2.4 旅行商问题 508 16.2.5 电路板排列 510 第17章 分枝定界 516 17.1 算法思想 516 17.2 应用 519 17.2.1 货箱装船 519 17.2.2 0/1背包问题 526 17.2.3 最大完备子图 528 17.2.4 旅行商问题 529 17.2.5 电路板排列 532
第1章 Visual C++与数字图像处理 1 1.1 数字图像处理概述 2 1.1.1 图像与数字图像 2 1.1.2 数字图像处理研究的内容 4 1.1.3 数字图像处理的应用 6 1.2 Visual C++概述 8 1.2.1 C++语言简介 8 1.2.2 Visual C++简介 16 1.2.3 Visual C++ 2005 集成开发环境 19 1.3 在Visual C++中处理数字图像 22 1.3.1 位图和调色板 22 1.3.2 图形设备接口 23 1.3.3 OpenCV 26 1.4 本章小结 26 第2章 Visual C++ 2005基础知识 27 2.1 利用向导生成应用程序 28 2.1.1 创建新项目 28 2.1.2 编译并运行工程 35 2.2 添加资源 36 2.2.1 新建资源 36 2.2.2 导入资源 38 2.3 MFC编程基础 38 2.3.1 MFC应用程序框架 39 2.3.2 Windows消息和事件驱动 40 2.3.3 常用消息 41 2.3.4 MFC的消息映射 42 2.4 消息与事件响应 44 2.4.1 添加类 44 2.4.2 添加类成员 45 2.4.3 添加消息响应 46 2.4.4 添加事件 47 2.4.5 添加函数重写 48 2.4.6 手动添加消息响应 50 2.5 对话框的使用 51 2.5.1 创建并编辑对话框资源 51 2.5.2 模式对话框和无模式对话框 55 2.5.3 消息对话框 57 2.5.4 共用对话框 59 2.6 常用控件的使用 64 2.6.1 按钮类控件 64 2.6.2 文本框 67 2.6.3 列表框 70 2.6.4 组合框 74 2.6.5 静态类控件 76 2.7 菜单栏和工具栏 77 2.7.1 菜单栏的使用 77 2.7.2 工具栏的使用 83 2.8 本章实例:简单的画图程序 87 2.8.1 实例预览 88 2.8.2 概要设计 88 2.8.3 完成实例编码 91 2.9 本章小结 98 第3章 认识色彩空间 99 3.1 颜色的基本知识 100 3.1.1 颜色的定义 100 3.1.2 颜色的属性 102 3.2 常用色彩空间简介 103 3.2.1 RGB颜色空间 103 3.2.2 CMY/CMYK颜色空间 105 3.2.3 HSV/HSB(HSI/HCI/HSL) 颜色空间 106 3.2.4 CIE系列颜色空间 109 3.2.5 YUV/YCbCr颜色空间 111 3.3 色彩空间的转换方法 112 3.3.1 RGB转换到HSV的方法 113 3.3.2 RGB转换到HSI的方法 114 3.3.3 RGB转换到YUV的方法 115 3.3.4 RGB转换到YCbCr的方法 116 3.4 本章实例:Photoshop 色彩编辑器 118 3.4.1 需求分析 118 3.4.2 概要设计 119 3.4.3 完成实例编码 121 3.5 本章小结 130 第4章 图像文件格式 131 4.1 图像文件概述 132 4.1.1 图像文件 132 4.1.2 图像文件的一般结构 132 4.1.3 图像文件的常用参数 133 4.2 BMP文件格式 134 4.2.1 文件结构 135 4.2.2 文件头和信息头 135 4.2.3 主要参数 136 4.3 GIF文件格式 136 4.3.1 GIF格式简介 137 4.3.2 GIF文件结构 137 4.3.3 GIF文件块的结构 138 4.4 PNG文件格式 142 4.4.1 PNG格式简介 142 4.4.2 PNG文件结构 143 4.4.3 PNG中的关键数据块 144 4.5 图像的压缩编码 146 4.5.1 Huffman编码 147 4.5.2 LZW编码 148 4.5.3 行程编码 151 4.5.4 离散余弦变换 151 4.6 JPEG文件格式 153 4.6.1 JPEG文件概述 153 4.6.2 JPEG编码/解码的理论基础 153 4.6.3 JPEG文件的格式 160 4.7 本章实例:JPEG解码程序 163 4.7.1 概要设计 163 4.7.2 完成实例编码 169 4.8 本章小结 188 第5章 使用DIB处理数字图像 189 5.1 设备相关位图和设备 无关位图 190 5.1.1 设备相关位图(DDB) 190 5.1.2 设备无关位图(DIB) 190 5.2 CBitmap类 190 5.2.1 创建DDB 191 5.2.2 CBitmap中的成员函数 193 5.2.3 应用DDB显示图像 193 5.2.4 应用DDB显示大图像 195 5.3 进一步了解DIB 203 5.3.1 DIB的结构 203 5.3.2 DIB信息段 203 5.3.3 位图数据 205 5.3.4 与DIB有关的函数 206 5.4 本章实例:DIB类的封装 208 5.4.1 设计 208 5.4.2 构造函数 210 5.4.3 DIB位图的显示 214 5.4.4 BMP文件的存储 215 5.5 本章小结 216 第6章 使用GDI+处理数字图像 217 6.1 GDI+简介 218 6.1.1 GDI+概述 218 6.1.2 GDI+的结构 218 6.2 在Visual C++中应用GDI+ 219 6.2.1 GDI+ 在Visual C++ 2005 中的配置方法 219 6.2.2 在Visual 6.0中使用GDI+ 221 6.3 GDI+基础 222 6.3.1 Graphics类 222 6.3.2 GDI+的基本数据类型 225 6.3.3 GDI+中的颜色 226 6.4 GDI+处理图像的基本方法 228 6.4.1 GDI+的图像类 228 6.4.2 创建图像对象 229 6.4.3 图像的显示和缩放 232 6.4.4 图像的基本处理方法 237 6.5 处理图像的色彩 244 6.5.1 ColorMatrix结构体 244 6.5.2 改变图像的透明度 245 6.5.3 将图像转换为灰度图 249 6.5.4 改变图像的亮度 251 6.5.5 改变图像的对比度 253 6.6 本章实例:播放GIF动画 255 6.6.1 播放原理分析 255 6.6.2 处理过程 256 6.6.3 具体实现 257 6.7 本章小结 260 第7章 使用OpenCV处理 数字图像 261 7.1 OpenCV简介 262 7.1.1 OpenCV概述 262 7.1.2 OpenCV的特点 263 7.1.3 OpenCV的命名规则 263 7.1.4 OpenCV的应用举例 264 7.2 OpenCV的安装与配置 266 7.2.1 OpenCV 在Visual C++ 6.0 下的安装与配置 266 7.2.2 OpenCV 在Visual C++ 2005 下的安装与配置 268 7.3 OpenCV的结构 271 7.3.1 OpenCV的体系结构 271 7.3.2 OpenCV的函数结构 271 7.3.3 OpenCV的功能结构 273 7.3.4 OpenCV的数据结构 274 7.4 本章实例:利用OpenCV 显示图像 275 7.4.1 图像文件的载入与显示 275 7.4.2 图像文件的创建、 保存和复制 277 7.5 本章小结 282 第8章 常见图像显示特效 283 8.1 显示特效概述 284 8.1.1 显示特效基础 284 8.1.2 显示特效过程 286 8.1.3 显示特效类 287 8.2 扫描显示特效 289 8.2.1 特效预览 289 8.2.2 基本原理和实现方法 289 8.2.3 编程实现 290 8.3 移动显示特效 292 8.3.1 特效预览 292 8.3.2 基本原理和实现方法 292 8.3.3 编程实现 293 8.4 百叶窗显示特效 295 8.4.1 特效预览 295 8.4.2 基本原理和实现方法 295 8.4.3 编程实现 297 8.5 栅条显示特效 298 8.5.1 特效预览 298 8.5.2 基本原理和实现方法 299 8.5.3 编程实现 300 8.6 马赛克显示特效 301 8.6.1 特效预览 301 8.6.2 基本原理和实现方法 302 8.6.3 编程实现 303 8.7 雨滴显示特效 304 8.7.1 特效预览 304 8.7.2 基本原理和实现方法 304 8.7.3 编程实现 305 8.8 本章实例:类似ACDSee 的图像浏览工具 306 8.8.1 实例预览 306 8.8.2 概要设计 307 8.8.3 完成实例编码 311 8.9 本章小结 324 第9章 图像的点运算 325 9.1 灰度直方图 326 9.1.1 灰度直方图 326 9.1.2 基本原理 328 9.1.3 编程实现 328 9.2 灰度线性变换 338 9.2.1 基本原理 338 9.2.2 编程实现 341 9.3 灰度非线性变换 344 9.3.1 灰度对数变换 344 9.3.2 灰度幂次变换 350 9.3.3 灰度指数变换 353 9.4 灰度阈值变换 354 9.4.1 基本原理 355 9.4.2 编程实现 355 9.5 灰度拉伸 357 9.5.1 基本原理 358 9.5.2 编程实现 360 9.6 灰度均衡 364 9.6.1 基本原理 364 9.6.2 编程实现 365 9.7 本章小结 366 第10章 对图像进行几何变换 367 10.1 图像几何变换的基本理论 368 10.1.1 图像几何变换概述 368 10.1.2 图像几何变换的数学描述 370 10.2 图像的平移变换 371 10.2.1 效果预览 371 10.2.2 基本原理 371 10.2.3 编程实现 373 10.3 图像的镜像变换 377 10.3.1 效果预览 377 10.3.2 基本原理 378 10.3.3 编程实现 379 10.4 图像的转置 383 10.4.1 效果预览 383 10.4.2 基本原理 384 10.4.3 编程实现 385 10.5 图像的缩放 386 10.5.1 效果预览 387 10.5.2 基本原理 387 10.5.3 插值算法介绍 388 10.5.4 编程实现 392 10.6 图像的旋转 398 10.6.1 效果预览 398 10.6.2 基本原理 398 10.6.3 编程实现 403 10.7 使用GDI+实现图像的 几何变换 409 10.7.1 GDI+的变换操作 409 10.7.2 平移 410 10.7.3 缩放 412 10.7.4 旋转 413 10.7.5 变换的组合 417 10.7.6 利用矩阵进行其他 几何变化 419 10.8 本章小结 422 第11章 图像的增强处理 423 11.1 图像的简单平滑 424 11.1.1 邻域处理的基本概念 424 11.1.2 图像的简单平滑原理 427 11.1.3 图像简单平滑的算法实现 427 11.2 图像的高斯平滑 431 11.2.1 平滑线性滤波器 432 11.2.2 高斯平滑的原理 432 11.2.3 高斯平滑的算法实现 433 11.3 图像的中值滤波 436 11.3.1 统计排序滤波器 437 11.3.2 图像中值滤波的原理 437 11.3.3 图像中值滤波的算法实现 439 11.4 应用OpenCV对图像 进行平滑处理 445 11.4.1 函数描述 445 11.4.2 概要设计 446 11.4.3 编码实现 446 11.5 拉普拉斯边缘增强 452 11.5.1 图像的锐化 452 11.5.2 图像拉普拉斯锐化的原理 452 11.5.3 图像拉普拉斯锐化的 算法实现 453 11.6 Sobel边缘细化 457 11.6.1 Sobel边缘细化的原理 457 11.6.2 Sobel边缘细化的 编程实现 459 11.7 本章小节 464 第12章 常见滤镜效果 465 12.1 图像的反色效果 466 12.1.1 底片效果 467 12.1.2 实现方法及原理 467 12.1.3 编程实现 467 12.2 图像的雕刻效果 469 12.2.1 雕刻效果 469 12.2.2 实现方法及原理 469 12.2.3 编程实现 470 12.3 图像的黑白效果 472 12.3.1 黑白效果 472 12.3.2 实现方法及原理 473 12.3.3 编程实现 473 12.4 图像的雾化效果 475 12.4.1 雾化效果 475 12.4.2 图像点阵的随机化处理 476 12.4.3 编程实现 476 12.5 图像的马赛克效果 483 12.5.1 马赛克效果 483 12.5.2 实现方法及原理 483 12.5.3 编程实现 484 12.6 图像的素描效果 487 12.6.1 素描效果 487 12.6.2 实现方法及原理 487 12.6.3 编程实现 487 12.7 本章小结 490 第13章 边缘检测和轮廓跟踪 491 13.1 边缘检测 492 13.1.1 边缘检测的基本概念 492 13.1.2 常规边缘检测 493 13.1.3 带方向的边缘检测 498 13.1.4 拉普拉斯算子 503 13.2 Hough变换 509 13.2.1 Hough变换的原理 509 13.2.2 编程实现 515 13.3 种子算法 520 13.3.1 算法介绍 520 13.3.2 编程实现 523 13.4 轮廓跟踪 526 13.4.1 区域表示方法 526 13.4.2 单区域跟踪 536 13.4.3 多区域跟踪 539 13.5 本章实例:应用OpenCV 进行边缘检测 541 13.5.1 Canny准则 541 13.5.2 Canny算法 542 13.5.3 在OpenCV中使用Canny 算法 543 13.6 本章小结 548 第14章 图像的形态学处理 549 14.1 数学形态学 550 14.2 一些必要的概念和 符号约定 550 14.3 图像的腐蚀 554 14.3.1 腐蚀原理 554 14.3.2 编程实现 557 14.4 图像的膨胀 562 14.4.1 膨胀原理 562 14.4.2 编程实现 565 14.5 腐蚀和膨胀的性质及应用 568 14.5.1 腐蚀和膨胀的代数性质 568 14.5.2 腐蚀和膨胀的应用 571 14.6 开运算和闭运算 577 14.6.1 开运算 578 14.6.2 闭运算 579 14.6.3 编程实现 580 14.6.4 开运算和闭运算的 代数性质 582 14.7 图像形态学的其他运算 584 14.7.1 击中/不击中运算 584 14.7.2 细化处理 588 14.8 本章实例:应用OpenCV 进行形态学处理 592 14.8.1 函数描述 592 14.8.2 概要设计 592 14.8.3 编码实现 593 14.9 本章小结 598 第15章 图像分割与目标识别 599 15.1 图像的分割 601 15.1.1 基于幅度的图像分割 601 15.1.2 基于区域的图像分割 606 15.1.3 基于形态学分水岭的 图像分割 611 15.2 图像的匹配 614 15.2.1 基本概念 614 15.2.2 模板匹配算法 615 15.2.3 序贯相似性检测算法 616 15.2.4 幅度排序算法 618 15.3 模式的识别 620 15.3.1 基本概念 620 15.3.2 统计模式识别 621 15.3.3 其他模式识别方法简介 627 15.4 本章实例:静态人脸 检测程序 628 15.4.1 人脸检测概述 628 15.4.2 算法分析 629 15.4.3 应用OpenCV进行 人脸检测 633 15.5 本章小结 640
VC6下数字图像处理系统-DIP_system,包含以下功能函数: ============================================================================== 第3章 ============================================================================== 相关函数: PaintDIB() - 绘制DIB对象 CreateDIBPalette() - 创建DIB对象调色板 FindDIBBits() - 返回DIB图像象素起始位置 DIBWidth() - 返回DIB宽度 DIBHeight() - 返回DIB高度 PaletteSize() - 返回DIB调色板大小 DIBNumColors() - 计算DIB调色板颜色数目 CopyHandle() - 拷贝内存块 DIBBitCount() - 该函数返回DIBBitCount SaveDIB() - 将DIB保存到指定文件中 ReadDIBFile() - 重指定文件中读取DIB对象 SetRgnColor() - 用指定的颜色填充指定的区域 SetCircleColor() - 用指定的颜色填充指定的圆形区域 以上函数为类CDIB的成员函数,CDIB类存在于DIB.h、DIB.cpp文件中。 ============================================================================== 第4章 ============================================================================== 4.1 图象的灰度变换 相关函数: ToGray() - 彩色位图转化为灰度位图 PointInvert() - 对图像进行反色变换 GetIntensity() - 对图像各颜色分量的灰度分布(数目、密度)进行统计 PointEqua() - 对图像进行灰度分布均衡化处理 GrayStretch() - 对图像进行灰度折线变换 WindowTrans() - 对图像进行灰度窗口变换 PointDZ() - 对图像进行灰度带阻变换 PointDT() - 对图像进行灰度带通变换 PointSML() - 对图像进行单映射规则直方图规定化变换 PointGML() - 对图像进行组映射规则直方图规定化变换 DynamicCompress()- 对图像进行灰度动态范围压缩处理 CutWave() - 对图像进行灰度削波处理 以上函数为类CGrayProc的成员函数,CGrayProc类存在于GrayProc.h、GrayProc.cpp文件中。 4.2 图象的正交变换 相关函数: FFT() - 一维快速付立叶变换 IFFT() - 一维快速付立叶逆变换 Fourier() - 二维快速傅立叶变换 IFourier() - 二维快速傅立叶逆变换 DCT() - 一维快速离散余弦变换 IDCT() - 一维快速离散余弦逆变换 FreqDCT() - 二维快速离散余弦变换 IFreqDCT() - 二维快速离散余弦逆变换 WALSH() - 一维沃尔什-哈达玛变换 IWALSH() - 一维沃尔什-哈达玛逆变换 FreqWALSH() - 二维沃尔什-哈达玛变换 IFreqWALSH()- 二维沃尔什-哈达玛逆变换 DIBFourier()- 图像的付立叶变换 DIBDCT() - 图像的离散余弦变换 DIBWalsh() - 图像的沃尔什-哈达玛变换 以上函数为类CFreqCalculate的成员函数,CFreqCalculate类存在于FreqCalculate.h、FreqCalculate.cpp文件中。 ============================================================================== 第5章 ============================================================================== 5.1 图像的空域滤波 相关函数: MakeEmboss() -

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值