部分彩色图片处理方式的C++实现

简述

基本实现了如目录所示的功能,其中HSI变换针对24位RGB图像,其它理论上可以对色深为8位的图片进行操作(包括8位灰度、24位RGB等),仅在RGB和灰度图上测试过。

bmp图片的色彩简单介绍

在这里插入图片描述
图片来自 文大侠的CSDN文章Windows DIB文件操作详解-5.DIB和调色板,很详细的介绍了bmp图片调色板和储存之间的关系。
本文中用到的就是其中的8位位图和24位位图,分别使用调色板和R,G,B各八位的方式来指定颜色。

代码中将要涉及到的变量和函数

unsigned char* m_pDibBits; //图片数据区的首地址
int m_nWidth; //图片宽度
int m_nHeight; // 图片高度
Destory() or IsNull() or Create() //都是DIB库自带函数,看名字就懂干啥的了,不懂自己查一下。
Get_Distance() //获取两点之间距离
double CDib::Get_Distance(int x1, int y1, int x2 = 256, int y2 = 256)
{
	double result = sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2));
	return result;
}

案例及其C++实现

生成三 色 图

创建新的图像

具体内容写在注释里了。

void CDib::createCircle()
{
	//如果已经有数据了,那么久销毁掉
	if (!IsNull())
	{
		Destroy();
	}

	// 创建新的数据区
	int Width = 512;
	int Height = 512;
	int BPP = 24;

	Create(Width, Height, BPP, 0);
	m_nWidth = Width;
	m_nHeight = Height;
	
	// 初始化各个变量
	m_nWidthBytes = abs(GetPitch());
	m_nBitCount = GetBPP();
	m_pDibBits = (unsigned char*)GetBits() + (m_nHeight - 1)*GetPitch();
	memset(m_pDibBits, 0, m_nWidthBytes*m_nHeight);

	//给数据区赋值,三种颜色坐标分别是 170,170 ;340,255 ;170,340
	RGBTRIPLE* RGBtemp; // 用来指向每一位数据,实现赋值
	for (int i = 0; i < m_nHeight; i++)
	{
		for (int j = 0; j < m_nWidth; j++)
		{
			RGBtemp = (RGBTRIPLE*) (m_pDibBits + i * m_nWidthBytes + j * 3);

			if (Get_Distance(i, j, 170, 170) < 150)
			{
				RGBtemp->rgbtBlue = 255;
			}
			if (Get_Distance(i, j, 340, 255) < 150)
			{
				RGBtemp->rgbtGreen = 255;
			}
			if (Get_Distance(i, j, 170, 340) < 150)
			{
				RGBtemp->rgbtRed = 255;
			}
		}
	}
}

对图片做HSI变换(仅针对RGB图片)

HSI简介

HSI是数字图像的一种表现形式,具体定义参照百度百科
计算方式为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然了,其中可能会涉及到一些分子全为零、整形浮点型之间转换数据丢失的问题,自己考虑一下。

代码实现

代码基本就是公式,不懂的对着公式看一下就懂了,部分地方做出了一小小的改变。

void CDib::HSI(HSIChannel Channel)
{
	double theta = 0;
	RGBTRIPLE* RGBtemp;
	double R, G, B, value;
	for (int lineX = 0; lineX < m_nHeight; lineX++)
	{
		for (int lineY = 0; lineY < m_nWidth; lineY++)
		{
			RGBtemp = (RGBTRIPLE*)(m_pDibBits + lineX * m_nWidthBytes + lineY * 3);
			B = RGBtemp->rgbtBlue;
			R = RGBtemp->rgbtRed;
			G = RGBtemp->rgbtGreen;
			switch (Channel)
			{
			case HUE: 
				{
					theta = acos(((R - G) + (R - B)) / 2 / \
					pow(pow((R - G), 2) + (R - B)*(G - B), 0.5)) * 180 / Pi;
					value = B > G ? 360 - theta : theta;
					break;
				}
			case SATURATION:
				{
				value = 1 - 3 / (R + G + B + 0.01)* min(min(R, G), B);
				if (!(R || G || B)) value = 0;
				value *= 255;
				break;
				}
			case INTENSITY:
			{
				value = (R+G+B)/3;
				break;
			}
			}

			RGBtemp->rgbtBlue = value;
			RGBtemp->rgbtRed = value;
			RGBtemp->rgbtGreen = value;
		}
	}
}

缩放

中值滤波

作用

用来去掉椒盐噪声,能有如下图所示的效果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

void CDib::midfilter()
{
	//开新空间
	//1~(width-1)
	int pixelSize = m_nWidthBytes / m_nWidth;

	for (int i = pixelSize; i < m_nHeight - 1; i++)
	{
		for (int j = pixelSize; j < (m_nWidth - 1) * pixelSize; j++)
		{
			double *mid = new double[9];
			mid[0] = *(m_pDibBits + (i - 1) * m_nWidthBytes + (j - pixelSize));
			mid[1] = *(m_pDibBits + (i - 1) * m_nWidthBytes + (j));
			mid[2] = *(m_pDibBits + (i - 1) * m_nWidthBytes + (j + pixelSize));
			mid[3] = *(m_pDibBits + (i)* m_nWidthBytes + (j - pixelSize));
			mid[4] = *(m_pDibBits + (i)* m_nWidthBytes + (j));
			mid[5] = *(m_pDibBits + (i)* m_nWidthBytes + (j + pixelSize));
			mid[6] = *(m_pDibBits + (i + 1)* m_nWidthBytes + (j - pixelSize));
			mid[7] = *(m_pDibBits + (i + 1)* m_nWidthBytes + (j));
			mid[8] = *(m_pDibBits + (i + 1)* m_nWidthBytes + (j + pixelSize));
			for (int x = 0; x < 8; x++) { //控制n-1趟冒泡
				for (int y = 0; y < 8 - x; y++)
				{
					if (mid[y] > mid[y + 1]) { //比较相邻的两个元素
						double tmp; //临时变量
						tmp = mid[y]; //交换
						mid[y] = mid[y + 1];
						mid[y + 1] = tmp;
					}
				}
			}
			*(m_pDibBits + (i)* m_nWidthBytes + (j)) = mid[4];
			delete[]mid;
		}
	}
}

平滑&拉普拉斯增强&sobel算子卷积

简述

本质上就是对图像与一个3*3的卷积核进行卷积,其中几个卷积核的定义如下,其中,Sobel算子需要对原图卷积两次:

const int Smooth_Kernel[9] = {1,1,1,1,1,1,1,1,1};
const int Laplacian_Kernel[9] = { 1, 1, 1, 1, -7, 1, 1, 1, 1 };
const int Sobel_Kernel_x[9] = { -1, -2, -1, 0, 0, 0, 1, 2, 1 };
const int Sobel_Kernel_y[9] = { -1, 0, 1, -2, 0, 2, -1, 0, 1 };

前两个卷积核分别是
1 1 1 1 1 1 1 1 1 \begin{matrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{matrix} 111111111

1 1 1 1 − 7 1 1 1 1 \begin{matrix} 1 & 1 & 1 \\ 1 & -7 & 1 \\ 1 & 1 & 1 \end{matrix} 111171111
有如下效果:
原图 ↓
在这里插入图片描述

平滑 ↓(因为卷积核小,只是变糊了一点点,不太明显)
在这里插入图片描述
拉普拉斯增强 ↓
(就像对图片做了描边操作,这里如果把卷积核中间的-7调成-8,就可以只看到描出来的边)
在这里插入图片描述
sobel卷积 ↓(这边实际上就是一个描边的效果了)
在这里插入图片描述

代码实现

void CDib::Conv(int kernel)
{
	if (!this) return;
	const int* k1 = 0;
	const int* k2 = 0;
	float sum = 0;
	switch (kernel)
	{
	case Smooth: k1 = Smooth_Kernel; sum = 9; break;
	case Sobel: k1 = Sobel_Kernel_x; k2 = Sobel_Kernel_y; sum = 2; break;
	case Laplacian: k1 = Laplacian_Kernel; sum = 1; break;
	default: assert(0);
	}

	int pixelSize = m_nWidthBytes / m_nWidth;

	int* bitTemp = new int[m_nWidthBytes * m_nHeight];
	memset(bitTemp, 0, m_nWidthBytes * m_nHeight * sizeof(int));
	//所有都需要经过的卷积
	for (int i = 0; i < m_nHeight; i++)
	{
		for (int j = 0; j < m_nWidth; j++)
		{
			for (int ch = 0; ch < pixelSize ; ch++)
			{
				//ch指不同的通道
				bitTemp[i*m_nWidthBytes + j * pixelSize+ ch] += *getPos(j, i, ch)*k1[4]; //·
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i - 1, ch)*k1[0];//↖
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i, ch)*k1[1];//↑
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i + 1, ch)*k1[2];//↗
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j, i - 1, ch)*k1[3];//←
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j, i + 1, ch)*k1[5];//→
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i - 1, ch)*k1[6];//↙
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i, ch)*k1[7];//↓
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i + 1, ch)*k1[8];//↘
				bitTemp[i*m_nWidthBytes + j * pixelSize + ch] = abs(bitTemp[i*m_nWidthBytes + j * pixelSize + ch]);
			}
		}
	}

	//Sobel滤波器需要过两次卷积
	if (k2)
	{
		k1 = k2;//上面复制下来的 懒得改了
		for (int i = 0; i < m_nHeight ; i++)
		{
			for (int j = 0; j < m_nWidth; j++)
			{
				for (int ch = 0; ch < pixelSize; ch++)
				{
					//ch指不同的通道
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j, i, ch)*k1[4]; //·
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i - 1, ch)*k1[0];//↖
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i, ch)*k1[1];//↑
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j - 1, i + 1, ch)*k1[2];//↗
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j, i - 1, ch)*k1[3];//←
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j, i + 1, ch)*k1[5];//→
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i - 1, ch)*k1[6];//↙
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i, ch)*k1[7];//↓
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] += *getPos(j + 1, i + 1, ch)*k1[8];//↘
					bitTemp[i*m_nWidthBytes + j * pixelSize + ch] = abs(bitTemp[i*m_nWidthBytes + j * pixelSize + ch]);
				}
			}
		}
	}
	//卷积后结果放回原图数据区
	int tempByte = 0;
	for (int i = 0; i < m_nHeight; i++)
	{
		for (int j = 0; j < m_nWidth; j++)
		{
			for (int ch = 0; ch < pixelSize; ch++)
			{

				tempByte = bitTemp[i*m_nWidthBytes + j * pixelSize + ch] / sum;
				//防止超过上下限
				if (tempByte > 255) tempByte = 255;
				else if (tempByte < 0) tempByte = 0;
				//tempByte = 100;
				*getPos(j, i, ch) = tempByte;
			}
		}
	}
	delete bitTemp;
}

对图片做直方图均衡

直方图均衡效果

直方图均衡,简而言之就是根据概率分布,将原数据的分布拉伸到从下限到上限的区间(表现在灰度图中为0-255),具体解释百度都知道。
对于彩色图像来讲,有很多种不同的方法,可以对RGB三个通道分别均衡,也可以根据HSI图中的I属性进行数据的均衡。
本代码统计颜色分布的方式如下(大体就是统计I值):
n u m = R [ i ] + G [ i ] + B [ i ] c o u n t [ n u m ] + + num = R[i] + G[i] + B[i]\\ count[num] ++ num=R[i]+G[i]+B[i]count[num]++
统计后根据概率分布进行映射。
但由于我有点懒,仍存在一定的问题

映射后,将数据超过255的数据直接置为255,这样可能会导致对应位置的H和S值的改变

有想法的朋友希望可以评论区讨论下如何改进这个问题。
下面放一下效果:
原图 ↓
在这里插入图片描述

直方图均衡 ↓
在这里插入图片描述

代码

long* ValueCount(unsigned char* begin,int width, int height,int pixelSize = 1)
{
	//统计不同的像素强度的和的数量
	//pixelSize是看一个像素占几位(一个通道八位哦)
	long* count = new long[256*pixelSize];
	memset(count, 0, 256 * pixelSize * sizeof(long));
	for (int i = 0; i < width*height; i++)
	{
		int num = 0;
		for (int ch = 0; ch < pixelSize; ch++)
			num += begin[i * pixelSize + ch];
		 count[num]++;
	}
	return count;
}

void CDib::Equalization()
{
	int pixelSize = m_nWidthBytes / m_nWidth;
	long * count = ValueCount(m_pDibBits, m_nWidth, m_nHeight, pixelSize);
	double *tableFunc = new double[256 * pixelSize];
	memset(tableFunc, 0, 256 * pixelSize * sizeof(double));

	int counter = 0;
	int pixels = m_nHeight * m_nWidth;
	
	for(int i = 0; i < 256 * pixelSize; i++ )
	{
		counter += count[i];
		tableFunc[i] = counter * (256 * pixelSize - 1) / pixels;
	}
	int pixeltemp;
	for (int i = 0; i < m_nHeight; i++)
	{
		for (int j = 0; j < m_nWidth; j++)
		{
			unsigned char *temp = m_pDibBits + i * m_nWidthBytes + j * pixelSize;
			int t = 0;
			for (int ch = 0; ch < pixelSize; ch++)
				t += temp[ch];//计算一下原来的总值,做个比较
			for (int ch = 0; ch < pixelSize; ch++)
			{
				pixeltemp = temp[ch] * tableFunc[t] * 1.0 / t;
				pixeltemp = pixeltemp > 255 ? 255 : pixeltemp;
				temp[ch] = pixeltemp;//映射过去
			}	
		}
	}
	delete tableFunc;
	delete count;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值