灰度图像压缩 DP算法 位运算详解

【matlab】灰度图压缩 由直方图可以看出,当像素强度的扩散在整个图像中是平坦的,那么哈夫曼难以有很大的压缩节省。而分布越不均匀,哈夫曼压缩的压缩比越高。而又因为哈夫曼压缩是无损压缩,由解压缩后的图片可以看出,哈夫曼压缩的压缩比大约在1.1左右。另外,使用matlab读取图片时要注意图片格式,本次测试图片要先转换为灰度图,在进行编码,否则将会输出三张灰度图片,分别对应原图片的三个通道信息。由此可知,该代码程序也可对彩色图片进行压缩。灰度图在多方面具有运用,比如灰度图可以减少图像的存储空间和计算量,提高图像处理的效率; 阅读详情

作者码字不易,白天敲代码,晚上熬夜赶报告,要转载请注明出处哦,程序猿的辛酸泪

目录

位运算回顾

压缩过程

解压过程

关于一个莫得感情的小bug

实用小工具的下载地址

完整版代码


位运算回顾

若 a = 250    // 二进制为1111 1010

b = ((a << 5) >> 5)    // b = 2,即0000 0010

若 c = 15     // 即0000 1111

d = ((c << 3) | b)    // d = 0111 1010

讲解:当我们将a左移5位,在右移5位的时候(逻辑右移,即高位补0),就相当于将a的高5位抹零了,所以b就获得了a的低3位,即010,等于2;当我们将c左移3位,再和b进行或运算的时候,就相当于将a的低3位衔接到了c变量的后面。

注意:这里有一个小bug,文末揭晓。

 

压缩过程

为了方便打印数据,我们自定义一个myWrite()函数,代码如下图

unsigned int num = 1;

void myWrite(ofstream &fout, unsigned long long &value)
{
	fout.write(reinterpret_cast<char*>(&value), sizeof(value));
	cout << "向输出文件中写入的第 " << num++ << " 个value的值是:" << value << endl;
}

接下来我们需要定义一些数据,来演示是如何把灰度图像的像素进行压缩的,数据以及变量的详情如下

	// 这是像素点的数量,数量为12个
	unsigned int count = 12;
	// 这是我们的像素数据,下标从1开始
	unsigned char data[] = { 0, 10, 12, 15, 255, 1, 2, 1, 1, 2, 2, 1, 1};
	// 这是经过dp算法后,计算出来的分段数量
	unsigned int segNum = 3;
	// 这是经过dp算法后,计算出来的(分段长度-1),下标从1开始
	// l[1] = 2,表示第一段有3个元素,l[2] = 0,表示第二段有1个元素,分段长度最长256
	unsigned char l[] = { 0, 2, 0, 7 };
	// 这是每一段各像素的最大bit位数
	unsigned char b[] = { 0, 4, 8, 2 };
	// 压缩结束标志
	bool isEnd = false;

	unsigned long long value = 0;           // 可写64bit位,当位操作满8字节时向文件中写入value
	unsigned char index = 0;                // 记录已经被操作了的bit数目
	unsigned int dataNum = 1;               // data数组的下标

接下来开始我们的压缩第一步,写入长度为8bit的第一段的段长,代码如下

		// 存段长,即该段元素的数量,最多256个,占8bit
		if (index + 8 < 64)
		{
			value <<= 8;
			value |= l[i];
			index += 8;
		}
		else if (index + 8 == 64)
		{
			value <<= 8;
			value |= l[i];
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
			value = 0;
		}
		else    // index + 8 > 64
		{
			unsigned char t = 64 - index;			// 8位先存t位
			value <<= t;
			value |= (l[i] >> (8 - t));				// 存前t位
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			value = 0;
			value |= (static_cast<unsigned char>(l[i] << t) >> t);		// 8 - (8 - t)
			index = 8 - t;
		}

讲解:这里分为了三种情况:

1,当我们写入8bit的段长信息后,未操作满64bit,这是我们的第一种情况,不调用的myWrite()函数;

2,当我们写入8bit的段长信息后,正好满64bit,将这个写满数据的value输出到文件中,然后变量重置;

3,当我们写入8bit的段长信息后,超过了64bit,就需要分两步存入。比如,我们此时的index = 62,我们第一步只能先存入2个bit位的数据,若l[i] = 1101 1111(二进制),我们先将l[i]高位的11追加到value的末尾,然后将value写入文件中,然后第二步,再将l[i]低位的0001 1111存入value,这里就用到了我们前面回顾的位操作哦。最后index = 6,表示新的value被操作了6个bit位。

 

好,开始我们的压缩第二步,写入长度为3bit的第一段中各元素的最大长度,最长最8,即8 - 1 = 7(二进制111,3bit)

		// 存段中各元素的统一长度,最长8位,占3bit
		if (index + 3 < 64)
		{
			value <<= 3;
			value |= (b[i] - 1);
			index += 3;
		}
		else if (index + 3 == 64)
		{
			value <<= 3;
			value |= (b[i] - 1);
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
			value = 0;
		}
		else    // index + 3 > 64
		{
			unsigned char t = 64 - index;				// 3位先存t位
			value <<= t;
			value |= ((b[i] - 1) >> (3 - t));			// 存前t位
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			value = 0;
			value |= (static_cast<unsigned char>((b[i] - 1) << (5 + t)) >> (5 + t));     // 8 - (3 - t)
			index = 3 - t;
		}

讲解:这里就不详讲啦,原理和上面一模一样。不过这里有一个小地方要注意哦,在第三种情况中,(b[i] - 1)需要左移(5 + t)位,而不是 t 位,想想为什么(ฅ>ω<*ฅ)

 

嘿嘿,接下来就是第三步啦,我们前面已经写入了11bit的header,接下来就开始压入我们的像素数据了,代码如下,仔细看哦

		// 存段中元素的像素数据,注意这里是l[i] + 1,才是我们的段长
		for (unsigned char j = 0; j < l[i] + 1; j++)
		{
			if (index + b[i] < 64)
			{
				value <<= b[i];
				value |= data[dataNum++];
				index += b[i];
			}
			else if (index + b[i] == 64)
			{
				value <<= b[i];
				value |= data[dataNum++];
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				index = 0;
				value = 0;
			}
			else    // index + b[i] > 64
			{
				unsigned char t = 64 - index;					// b[i]位先存t位
				value <<= t;
				value |= (data[dataNum] >> (b[i] - t));			// 存前t位
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				value = 0;
				value = (static_cast<unsigned char>(data[dataNum] << (8 - (b[i] - t))) >> (8 - (b[i] - t)));
				dataNum++;
				index = b[i] - t;
			}
			if (dataNum == count + 1)			// 最后一个数据
			{
				value <<= (64 - index);
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				isEnd = true;
				break;
			}

好啦,写了这么长的一段代码,一共有9个条件判断呢,是不是心里没底,不知道自己写的对不对,那我们就来打印一下存入的value的值,看看我们的算法有没有问题。我们前面自定义的myWrite()函数就能派上用场啦。请看输出信息

有人可能会疑惑,咦,不对呀,第一个value的值怎么感觉有点小呀,是不是长度没有达到64bit位呀。真的是我们的算法出错了吗?其实不是哒,因为如果高位有多个0的话,转化成10进制数,就体现不出来了呀。比如十进制2,它对应的unsigned char类型,其二进制可是0000 0010,前面的6个0也没有体现出来呀。那怎么检验呢。

这里安利一款好用的小工具,可以将一个超大的10进制数,转换成其对应的二进制数,一些在线的转换工具可达不到这个目的。

界面如下,下载链接文末给出(๑´ㅂ`๑)

这里是我们第一个value的值,数数看(一行3个字节),上面一共显示了58位,可见最前面是有6个0的。为了方便检验,我把二进制信息写到下面

0000 0010(前面补了6个0)

011 1010 1100 1111

0000 0000

111 1111 1111

0000 0111

001 01 10 01 01 10 1 (未完,被分成了两段)

检验一下看看,第一段元素有3(2+1)个,每个元素长度为4(3+1),分别是10, 12, 15

第二段元素有1(0+1)个,每个元素长度为8(7+1),分别是255

第三段元素有8(7+1)个,每个元素长度为2(1+1),分别是1,2,1,1,2,......

第二个value的值我就不检验了,感兴趣的小伙伴可以自己去检验看看哦,肯定是没问题的。

 

解压过程

哇,好了,费了不小的劲,终于把压缩过程讲解完啦,接下来的解压过程就是一个逆过程哦,也是很好理解的

首先,我们同样也需要先读取一些数据,还需要定义一些变量用来记录,代码如下图

	// 读取我们的分段数量。
	// 在实际情况中,我们可以在压缩的时候,将分段的数量最先写入文件中,在解压的时候,就可以直接读取出来了
	unsigned int segNum = 3;

	// 这是像素点的数量,数量为12个。
	// 这里直接给出了,其实在实际情况中,我们可以通过bitMap的信息头的biWidth和biHeight的乘积来获取到
	unsigned int count = 12;

	// 存储我们解压出来的像素数据
	unsigned char *data = new unsigned char[count + 1];
	data[0] = 0;

	// 解压结束标志
	bool isEnd = false;

	unsigned long long value = 0;			        // 含义同压缩
	unsigned char index = 0;				// 含义同压缩
	unsigned int dataNum = 1;				// 含义同压缩
	unsigned char x = 0;					// 段长
	unsigned char y = 0;					// 每段各像素的最大长度

	// 初始化,先读入第一个value
	fin.read(reinterpret_cast<char*>(&value), sizeof(value));

接下来开始我们解压的第一步,读取长度为8bit的第一段的段长,代码如下

		// 读取段元素的数量,最多256个,占8bit
		if (index + 8 < 64)
		{
			x = ((value << index) >> 56);
			index += 8;
		}
		else if (index + 8 == 64)
		{
			x = ((value << 56) >> 56);
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
		}
		else		// index + 8 > 64
		{
			unsigned char t = 64 - index;					// 先读t位
			x = static_cast<unsigned char>((value << index) >> index);
			index = 8 - t;							// 再读8-t位
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			x <<= index;
			x |= (value >> (64 - index));
		}

讲解:解压缩的过程就是一个逆过程啦,我们要从高位向低位读取数据,所以先左移,消除掉之前已读取到的数据,再右移的过程就必不可少了。

 

解压第二步,读取长度为3bit的段中元素的最大长度,代码如下

		// 读取段中各元素的统一长度,最长8位,占3bit
		if (index + 3 < 64)
		{
			y = ((value << index) >> 61) + 1;
			index += 3;
		}
		else if (index + 3 == 64)
		{
			y = ((value << 61) >> 61) + 1;
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
		}
		else		// index + 3 > 64
		{
			unsigned char t = 64 - index;					// 先读t位
			y = static_cast<unsigned char>((value << index) >> index);
			index = 3 - t;							// 再读3-t位
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			y <<= index;
			y |= (value >> (64 - index));
			y++;
		}

讲解:注意第三种情况的那个y++是因为最大长度等于读取到的数据 + 1,注意一下就好啦。

 

解压第三步,开始解压我们的像素数据啦,仔细看哦

		// 读取段中元素的像素数据
		for (unsigned char j = 0; j < x + 1; j++)
		{
			if (index + y < 64)
			{
				data[dataNum++] = static_cast<unsigned char>((value << index) >> (64 - y));
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
				index += y;
			}
			else if (index + y == 64)
			{
				data[dataNum++] = static_cast<unsigned char>((value << index) >> index);
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
				fin.read(reinterpret_cast<char*>(&value), sizeof(value));
				index = 0;
			}
			else		// index + y > 64
			{
				unsigned char t = 64 - index;						// 先读t位
				data[dataNum] = static_cast<unsigned char>((value << index) >> index);
				index = y - t;								// 再读y-t位
				fin.read(reinterpret_cast<char*>(&value), sizeof(value));
				data[dataNum] <<= index;
				data[dataNum] |= (value >> (64 - index));
				dataNum++;
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
			}

讲解:已经没有什么可以讲解的了,原理都差不多呢(。・ω・。)ノ♡

 

最后的最后,让我们来接验一下我们解压的成果吧,只需要把data数组里的输出一下就可以了,请看下图

大功告成,啦啦啦 (*/ω\*)

 

关于一个莫得感情的小bug

最后,来解答一下最开头所提到的小bug吧。细心的小伙伴们可能会发现,在压缩和解压的函数里,有些地方我们使用了强制类型转换(static_cast<unsigned char>()),有些地方确没有使用。一方面,我们在解压过程中,将64bit位的value强行赋值给我们的x,y,或data[dataNum],编辑器会警告我们这样的转换可能会丢失精度,额为了消除这样的警告(强迫症啦这是),所以我们就使用一个强制类型转换啦。但是,另一方面,不知道小伙伴们有没有做过这样的尝试

	unsigned char a = 250;					// 11111010
	cout << "a:" << int(a) << endl;
	unsigned char b = ((a << 5) >> 5);
	cout << "b:" << int(b) << endl;
	unsigned char c = (static_cast<unsigned char>(a << 5) >> 5);
	cout << "c:" << int(c) << endl;

有人可能会疑惑,诶,b和c的值是一样的吧,肯定是2啊,先左移再右移嘛,但结果真的是这样吗,请看运行结果

我的天,为什么先左移再右移没有起作用呢,这里经过尝试后发现,对于unsigned char类型的变量,在同一条执行语句中,先左移再右移,编辑器会貌似会进行一个不必要的优化,即它发现,诶你既左移了5位,又右移了5位,不就相当于没移嘛,那我就不用给你执行这条语句了。同理,经测试后发现,如果对于unsigned char类型的变量,在同一条执行语句中,先左移5位,在右移4位,编辑器会优化成最终只用向左移1位。所以啊,如果我们希望通过先左移再右移来达到消除高位的效果,要么将左移和右移分两步进行,要么在左移结束后要加一个强制类型转换,告诉编辑器,我就要先左移,你必须得给我执行 o(一︿一+)o

博主又经过了多轮测试,发现,这个左移右移的优化功能,貌似只对unsigned char类型和unsigned short类型起作用,对于unsigned int 和unsigned long long类型,就算你把左移和右移放在同一条执行语句中,就算你没有加强制类型转换,它也不会给你进行优化了。

好吧,这也算是,课外的一个算有趣也不算有趣的小知识点吧 o(╯□╰)o

 

实用小工具的下载地址

最后的最后的最后,放出我们那个超好用的小工具的下载链接,说实话,这个小工具在我找bug的时候,帮了我不少的忙,虽然,这个灰度图像压缩的bug让我找得猿生绝望。

大数进制转换工具下载地址

 

完整版代码

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

unsigned int num = 1;

void myWrite(ofstream &fout, unsigned long long &value)
{
	fout.write(reinterpret_cast<char*>(&value), sizeof(value));
	cout << "向输出文件中写入的第 " << num++ << " 个value的值是:" << value << endl;
}

bool Compress(string fileName)
{
	// 关联我们要输出的文件
	ofstream fout(&fileName[0], ios::binary);
	if (!fout) return false;

	///
	/// 此处省略了我们写文件头的操作
	///

	// 这是像素点的数量,数量为12个
	unsigned int count = 12;
	// 这是我们的像素数据,下标从1开始
	unsigned char data[] = { 0, 10, 12, 15, 255, 1, 2, 1, 1, 2, 2, 1, 1};
	// 这是经过dp算法后,计算出来的分段数量
	unsigned int segNum = 3;
	// 这是经过dp算法后,计算出来的(分段长度-1),下标从1开始
	// l[1] = 2,表示第一段有3个元素,l[2] = 0,表示第二段有1个元素,分段长度最长256
	unsigned char l[] = { 0, 2, 0, 7 };
	// 这是每一段各像素的最大bit位数
	unsigned char b[] = { 0, 4, 8, 2 };
	// 压缩结束标志
	bool isEnd = false;

	// 开始压缩像素,并写入文件
	unsigned long long value = 0;           // 可写64bit位,当位操作满8字节时向文件中写入value
	unsigned char index = 0;                // 记录已经被操作了的bit数目
	unsigned int dataNum = 1;               // data数组的下标
	for (unsigned int i = 1; i <= segNum && !isEnd; i++)
	{
		// 存段长,即该段元素的数量,最多256个,占8bit
		if (index + 8 < 64)
		{
			value <<= 8;
			value |= l[i];
			index += 8;
		}
		else if (index + 8 == 64)
		{
			value <<= 8;
			value |= l[i];
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
			value = 0;
		}
		else    // index + 8 > 64
		{
			unsigned char t = 64 - index;			// 8位先存t位
			value <<= t;
			value |= (l[i] >> (8 - t));				// 存前t位
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			value = 0;
			value |= (static_cast<unsigned char>(l[i] << t) >> t);		// 8 - (8 - t)
			index = 8 - t;
		}

		// 存段中各元素的统一长度,最长8位,占3bit
		if (index + 3 < 64)
		{
			value <<= 3;
			value |= (b[i] - 1);
			index += 3;
		}
		else if (index + 3 == 64)
		{
			value <<= 3;
			value |= (b[i] - 1);
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
			value = 0;
		}
		else    // index + 3 > 64
		{
			unsigned char t = 64 - index;				// 3位先存t位
			value <<= t;
			value |= ((b[i] - 1) >> (3 - t));			// 存前t位
			myWrite(fout, value);
			//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
			value = 0;
			value |= (static_cast<unsigned char>((b[i] - 1) << (5 + t)) >> (5 + t));     // 8 - (3 - t)
			index = 3 - t;
		}

		// 存段中元素的像素数据,注意这里是l[i] + 1,才是我们的段长
		for (unsigned char j = 0; j < l[i] + 1; j++)
		{
			if (index + b[i] < 64)
			{
				value <<= b[i];
				value |= data[dataNum++];
				index += b[i];
			}
			else if (index + b[i] == 64)
			{
				value <<= b[i];
				value |= data[dataNum++];
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				index = 0;
				value = 0;
			}
			else    // index + b[i] > 64
			{
				unsigned char t = 64 - index;					// b[i]位先存t位
				value <<= t;
				value |= (data[dataNum] >> (b[i] - t));			// 存前t位
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				value = 0;
				value = (static_cast<unsigned char>(data[dataNum] << (8 - (b[i] - t))) >> (8 - (b[i] - t)));
				dataNum++;
				index = b[i] - t;
			}
			if (dataNum == count + 1)			// 最后一个数据
			{
				value <<= (64 - index);
				myWrite(fout, value);
				//fout.write(reinterpret_cast<char*>(&value), sizeof(value));
				isEnd = true;
				break;
			}
		}
	}
	fout.close();
	return true;
}

bool UnCompress(string fileName)
{
	// 打开指定文件
	ifstream fin(&fileName[0], ios::binary);
	if (!fin) return false;

	///
	/// 此处省略了我们读文件头的操作
	///

	// 读取我们的分段数量。
	// 在实际情况中,我们可以在压缩的时候,将分段的数量最先写入文件中,在解压的时候,就可以直接读取出来了
	unsigned int segNum = 3;

	// 这是像素点的数量,数量为12个。
	// 这里直接给出了,其实在实际情况中,我们可以通过bitMap的信息头的biWidth和biHeight的乘积来获取到
	unsigned int count = 12;

	// 存储我们解压出来的像素数据
	unsigned char *data = new unsigned char[count + 1];
	data[0] = 0;

	// 解压结束标志
	bool isEnd = false;

	unsigned long long value = 0;			// 含义同压缩
	unsigned char index = 0;				// 含义同压缩
	unsigned int dataNum = 1;				// 含义同压缩
	unsigned char x = 0;					// 段长
	unsigned char y = 0;					// 每段各像素的最大长度

	// 初始化,先读入第一个value
	fin.read(reinterpret_cast<char*>(&value), sizeof(value));

	for (unsigned int i = 1; i <= segNum && !isEnd; i++)
	{
		// 读取段元素的数量,最多256个,占8bit
		if (index + 8 < 64)
		{
			x = ((value << index) >> 56);
			index += 8;
		}
		else if (index + 8 == 64)
		{
			x = ((value << 56) >> 56);
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
		}
		else		// index + 8 > 64
		{
			unsigned char t = 64 - index;									// 先读t位
			x = static_cast<unsigned char>((value << index) >> index);
			index = 8 - t;													// 再读8-t位
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			x <<= index;
			x |= (value >> (64 - index));
		}

		// 读取段中各元素的统一长度,最长8位,占3bit
		if (index + 3 < 64)
		{
			y = ((value << index) >> 61) + 1;
			index += 3;
		}
		else if (index + 3 == 64)
		{
			y = ((value << 61) >> 61) + 1;
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			index = 0;
		}
		else		// index + 3 > 64
		{
			unsigned char t = 64 - index;									// 先读t位
			y = static_cast<unsigned char>((value << index) >> index);
			index = 3 - t;													// 再读3-t位
			fin.read(reinterpret_cast<char*>(&value), sizeof(value));
			y <<= index;
			y |= (value >> (64 - index));
			y++;
		}

		// 读取段中元素的像素数据
		for (unsigned char j = 0; j < x + 1; j++)
		{
			if (index + y < 64)
			{
				data[dataNum++] = static_cast<unsigned char>((value << index) >> (64 - y));
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
				index += y;
			}
			else if (index + y == 64)
			{
				data[dataNum++] = static_cast<unsigned char>((value << index) >> index);
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
				fin.read(reinterpret_cast<char*>(&value), sizeof(value));
				index = 0;
			}
			else		// index + y > 64
			{
				unsigned char t = 64 - index;											// 先读t位
				data[dataNum] = static_cast<unsigned char>((value << index) >> index);
				index = y - t;															// 再读y-t位
				fin.read(reinterpret_cast<char*>(&value), sizeof(value));
				data[dataNum] <<= index;
				data[dataNum] |= (value >> (64 - index));
				dataNum++;
				if (dataNum == count + 1) {
					isEnd = true;
					break;
				}
			}
		}
	}
	fin.close();

	// 输出一下我们解压出来的像素信息
	for (int i = 0; i <= 12; i++) {
		cout << int(data[i]) << " ";
	}
	cout << endl;

	///
	/// 此处省略了将data数组按蛇形写入输出文件,即还原成2维数组的过程
	/// 
	delete[] data;
	return true;
}

int main() 
{
	if (Compress("output.img"))
	{
		cout << "压缩成功" << endl;
	}
	else
	{
		cout << "压缩失败" << endl;
	}
	if (UnCompress("output.img"))
	{
		cout << "解压成功" << endl;
	}
	else
	{
		cout << "解压失败" << endl;
	}
	return 0;
}

欢迎可爱的小伙伴给我留言呀,Mum~

HDMI 2.2与DP2.1b线缆技术解析与应用指南 视频接口技术正经历从HDMI 2.1到2.2、DisplayPort 2.1b的跨越式发展,其核心在于带宽提升与信号完整性优化。新一代标准采用PAM4编码和固定速率链路技术(FRIL),显著降低信号抖动,支持8K@240Hz无损传输。在医疗影像、工业机器视觉等专业领域,这些技术解决了多路4K同步采集和DICOM图像传输延迟等痛点。线缆工艺方面,镀银铜导体和三层屏蔽结构使高频衰减降低62%。对于游戏玩家,延迟指示协议(LIP)可将输入延迟减半。选购时需注意设备芯片型号和线缆认证标识,专业应用场景才需要升级到最 阅读详情

相关推荐

网易数据挖掘工程师笔试复盘:机器学习考点与编程题全解析

从概念到原理,数据挖掘岗位笔试通常考察机器学习基础、特征工程、算法与编程能力。掌握L1/L2正则化、决策树、聚类等模型原理,理解评估指标如AUC、F1的含义,是应对大厂笔试的关键。通过网易校招真题分析,展示从理论到工程落地的完整路径,包括特征标准化、缺失值处理、过拟合应对等实用技能。在编程方面,TopK问题、差分数组、状态压缩DP等经典算法是高频考点。结合业务场景建模与模型评估,全面提升技术深度与实战能力,为数据挖掘算法工程师岗位做好准备。

weixin_34174322的博客 592

灰度图像压缩,包括读取、压缩、解压(中国地质大学软件工程系实习)

参考灰度图像压缩 DP算法 位运算详解_Apple_Coco的博客-优快云博客_灰度图像压缩算法

r1970147020的博客 1378

最大子矩阵和:从暴力到O(n²m)降维与Kadane算法实战

最大子段和是数组中的经典问题,而最大子矩阵和将其扩展到二维矩阵,寻找和最大的连续矩形区域。核心原理是通过固定上下边界,将二维问题压缩为一维数组,再应用Kadane算法求最大子段和,从而将复杂度从O(n²m²)降至O(n²m)。这一降维思想在图像处理、金融分析等场景中价值显著,例如定位热力图峰值区域或最优连续区间。工程实践中需注意转置优化、边界处理及负数陷阱,结合前缀和与动态规划可进一步提升效率。

weixin_42116058的博客 151

【图像压缩】基于霍夫曼+行程+算术编码多种算法得灰色图像无损+有损压缩附Matlab代码

图像压缩是一项重要的技术,它可以将图像文件的大小减小,以便更好地存储和传输。在图像压缩领域,有许多不同的算法被用来实现无损和有损压缩。本文将介绍基于霍夫曼编码、行程编码和算术编码的多种算法,用于实现灰色图像的压缩。首先,让我们来了解一下图像压缩的基本原理。在图像中,每个像素都由一定数量的位表示。灰度图像中的每个像素通常由8位表示,范围从0到255。通过压缩算法,我们可以减少每个像素所需的位数,从而减小图像文件的大小。霍夫曼编码是一种常用的无损压缩算法。

qq_59747472的博客 719

Kadane算法与前缀和:二维矩阵最大子矩阵和的高效解法

在数据处理和算法设计中,寻找连续子数组的最大和是一个经典问题,通常通过Kadane算法高效解决。其核心原理是动态规划,通过维护局部最优解来避免重复计算,从而将时间复杂度从O(n²)降至O(n)。这一思想在工程实践中价值显著,尤其在处理多维数据时,常需进行降维处理。前缀和作为一种基础预处理技术,能快速计算任意区间和,是优化多维查询的关键。结合Kadane算法与前缀和思想,可以将二维矩阵的最大子矩阵和问题转化为多个一维问题,通过枚举上下边界并压缩列和,将时间复杂度优化至O(n²m),广泛应用于图像处理、金融分析

weixin_34200628的博客 706

图像灰度级压缩算法的实现(C/C++)

图像灰度级压缩的基本思想是通过减少图像中的灰度级别来减少图像的信息量。通过降低灰度级别的数量,我们可以将图像从8位灰度图像转换为更低位数的图像,从而减小图像所占用的存储空间。在函数内部,我们遍历图像的每个像素点,并根据灰度级别的间隔将像素点的灰度值映射到新的灰度级别上。通过运行上述代码,我们可以将原始图像和压缩后的图像进行对比,观察压缩算法的效果。图像灰度级压缩是一种常见的图像处理技术,它可以减少图像的灰度级别,从而降低图像的存储空间和传输带宽。函数对图像进行灰度级压缩处理,并将压缩后的图像显示出来。

PixelDude的博客 775

动态规划算法压缩BMP位图

一文搞懂BMP图像动态规划(dp)压缩算法!

你的问题在于,读书太少而想得太多。 1617

图像灰度级压缩

图像灰度级压缩

zhaoguanhua的博客 8920

【算法小记】DP之图像压缩

问题描述:图片在计算机存储的是图片中的像素序列{p1,p2,p3......pn},也就是像素的灰度值(pi)。灰度值的范围是0~255。因此需要8位二进制表示一个像素。能否用更少的位数表示灰度值呢?即是图像压缩的问题。 问题分析: 可要求像素序列进行分段,使得最后所需要的位数最少(储存空间)。引入两个固定位数的值来表示: 1. 3位数表示当前段的每位像素的位数(000----表示像...

火锅底料_大伯的博客 2328

4、图像分形压缩与降维方法详解

本博文详细介绍了图像分形压缩的基本理论与实现方法,涵盖编码与解码的核心流程,并通过Python和Mathematica代码展示了具体实现。文章还讨论了加速分形码书计算的策略,如聚类方法,并引入MSE和PSNR作为图像质量衡量标准。此外,对比了主成分分析(PCA)和线性判别分析(LDA)等降维技术,分析了它们的优缺点及适用场景。最后,通过卫星图像压缩和医学图像存储的实际案例,展示了分形压缩的应用价值。

73

基于Hough变换的圆形检测代码实现与详解

对于点 $(x_j, y_j)$ 和圆 $(a, b, r)$,其到圆周的最短距离(沿径向)为:优化目标为:该问题是非线性的,适合使用Levenberg-Marquardt算法求解。

weixin_42584586的博客 1157

实验04:图像压缩(DP算法)

本实验中采用bin文件格式保存中间编码(压缩)文件以直观显示压缩完成后文件大小,本实验所有代码保存于CODE文件夹,所有结果保存于RESULT文件夹以便老师查阅。将像素值均大于271282^7=12827128的分段进行取反操作,保存像素值与256256256之差,段长最大值减一,需多加一位符号位表示是否取反,对于像素值较大的图像压缩率较大。对于一段像素值用高斯分布等概率模型拟合,保存参数后解压时用概率分布函数生成像素值。

m0_52065936的博客 972

S7-200 SMART Modbus RTU从站配置与调试实战指南

本文提供了一份详细的S7-200 SMART PLC配置为Modbus RTU从站的实战指南。内容涵盖从硬件基础、软件准备、指令库调用、核心编程到地址映射与调试排错的完整流程。重点解析了MBUS_INIT与MBUS_SLAVE指令的关键参数配置,并深入探讨了Modbus地址与PLC内部V存储区的对应关系,帮助工程师快速打通通讯链路,实现与上位机、触摸屏等主站设备的稳定数据交互。

potato的博客 753

动态规划——图像压缩

对于 dp[1][1],最优编码值等于当前灰度值减去上方像素点和左方像素点最小的编码值:dp[1][1] = 132 - min(5, -5) = 137。对于 dp[2][1],最优编码值等于当前灰度值减去上方像素点和左方像素点最小的编码值:dp[2][1] = 138 - min(-5, 4) = 143。对于 dp[2][2],最优编码值等于当前灰度值减去上方像素点和左方像素点最小的编码值:dp[2][2] = 142 - min(143, 4) = -1。这个矩阵代表了每个像素点的最优编码值。

qq_73981243的博客 1023

一文带你弄懂thermal governor IPA

当current temp < target temp时,使用k_pu,当current temp >= target temp时,使用k_po。如果current_temperature = switch_on_temp时,则k_p = k_pu,这时Pmax。如果current_temperature = target_temp时,则k_p = k_po,这时Pmax。2、解析dts,获得switch_on_temp和target_temp。3、初始化k_pu/k_po/k_i/k_d的常数。

yxw0609131056的博客 1245

Windows下Maven环境变量配置避坑指南:解决‘mvn‘不被识别问题

本文详细解析了Windows系统中配置Maven环境变量时常见的'mvn'不被识别问题,特别是当系统提示“无法将‘mvn’项识别为cmdlet、函数、脚本文件或可运行程序的名称”时的解决方案。文章从问题根源入手,提供了清晰的配置步骤、深入的排查方法以及针对PowerShell的特殊处理,旨在帮助开发者彻底解决环境变量配置难题,确保Maven命令正常使用。

weixin_28826961的博客 389

图像压缩算法避坑指南:动态规划中的段头开销与最优分组策略

本文深入解析图像压缩算法中动态规划的优化策略,重点探讨段头开销与分组策略的关键问题。通过分析11位设计的数学原理和工程实现陷阱,提供C++代码示例和性能优化技巧,帮助开发者避免常见错误并提升压缩效率。适用于计算机视觉、医疗影像处理等领域。

weixin_33733810的博客 413

OpenCV实战:5分钟搞定图像拼接(附Python代码)

本文通过OpenCV实战教程,详细讲解如何使用Python在5分钟内完成图像拼接。重点介绍`findHomography`函数和单应性矩阵的应用,提供完整的代码示例和常见问题解决方案,帮助开发者快速掌握图像拼接技术。

weixin_26833503的博客 120

动态规划之‘图像压缩’

问题描述:图像压缩的问题我们是这样理解的:大家都知道计算机的图像是用灰度值序列来表示的{P1,P2......Pn},其中Pi表示像素点i的灰度值。而通常灰度值的范围是0~255,因此需要8位二进制数来表示一个像素。这个时候大家应该有了一些小的疑问:我能不能用更少的位数来表示灰度值?(因为有的灰度值并没有达到255这么大)所以我们引入了图像压缩算法来解决这个问题。 不过在引入问题之前,我要在这里...

Sevexw的博客 1011

Python实战:5分钟搞定高德地图API批量地址转经纬度(附完整代码)

本文提供了一套完整的Python解决方案,利用高德地图Web服务API,实现批量地址到经纬度坐标的自动化转换。文章包含从API申请、核心代码编写到错误处理与性能优化的全流程实战指南,并附赠数据可视化方法,帮助数据分析师和开发者快速提升地理信息处理效率。

weixin_29047919的博客 582

使用Gromacs进行蛋白和配体小分子的动力学模拟+结果分析全流程教程

先选protein,选择non-water,使用pymol打开delWater.pdb,输入util.ss('all')展示cartoon,点击file-Export Moive as-MPEG。这一步完成后,文件夹中会出现以下三个文件,其中gro 文件用于记录蛋白的初始坐标,top 文件记录蛋白的拓扑信息和力场信息,itp 与top文件类似。接下来使用Spdbv软件打开蛋白,如果蛋白中存在原子缺失情况,就会出现以下的提示,如果不存在就不会显示,同时保存为rep.pdb。

weixin_46587777的博客 2854

浅谈稀疏性与压缩感知(一)

由于学习需要对这一部分的内容进行了简单了解,在社区里也有很多人写了相关的内容,我想记录自己在学习过程中的收获或者说问题,计划来写这篇博客,打算通过三部分来结束稀疏性和压缩感知的内容,参考的书目为数据驱动的科学和工程,当然B站上提供了这本书的讲解,链接如下数据驱动的科学和工程,在评论区中给出了此书的PDF,书中的代码链接如下代码,当然我也会在文中给出并且适当注释,文中就用Python来说明。

weixin_54729407的博客 648

华为面试手撕真题【判断是ipv4还是ipv6地址】

编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址。

MISAYAONE的博客 1万+

数据压缩实验之DPCM压缩系统的实现和分析

一、实验目的 掌握DPCM编解码系统的基本原理。初步掌握实验用C/C++/Python等语言编程实现DPCM编码器,并分析其压缩效率。 二、主要设备 安装 Windows 和 Visual Studio 软件的个人计算机 三、实验内容 1.DPCM编解码原理 DPCM是差分预测编码调制的缩写,是比较典型的预测编码系统。在DPCM系统中, 需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解

weixin_46393626的博客 252

【蒸汽幻想台服新版商业端】2025最新整理单机一键即玩镜像端+WIN系半手工端+PC客户端+网页注册+网页管理后台+教程【站长亲测】

MMORPG端游【蒸汽幻想台服新版商业端】2025最新整理单机一键即玩镜像端+WIN系半手工端+PC客户端+网页注册+网页管理后台+教程。

百创科技 531

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

二维最大子矩阵和:前缀和与Kadane算法的降维应用

前缀和是一种基础的数据预处理技术,通过构建累积和数组,能够在常数时间内查询任意区间的和,其核心原理是以空间换时间。Kadane算法则是动态规划的经典应用,通过贪心决策在线性时间内解决一维最大子数组和问题,体现了局部最优解如何导向全局最优解。这两种技术的结合,为解决二维及更高维度的区间最值问题提供了高效思路,其技术价值在于将O(n²m²)的暴力复杂度优化至O(min(m,n)²*max(m,n)),实现了从不可行到可行的质变。在图像处理、金融数据分析、生物信息学等场景中,快速定位矩阵中元素和最大的连续子区域是

weixin_34055787的博客 412

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

上一篇: 如何用Qt绘制一颗好看的二叉树
下一篇: OpenCV4(1)-读取和显示图像(C++,Python,JS)
Apple_Coco
博客等级 码龄9年 325粉丝 117原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Coco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值