图像金字塔

基础知识

图像分辨率是指图像单位英寸中所包含的像素数,常用DPI表示。图像分辨率和图像尺寸的值一起决定了图像的输出质量和文件大小,图像分辨率越大,图像质量越好,同时所占的内存和磁盘空间越大。文件大小与图像分辨率的平方成正比,如果图像的尺寸不变,将图像分辨率提高一倍,那么文件大小增加四倍。

图像金字塔

图像金字塔是图像多尺度表达的一种。一幅图像的图像金字塔是一系列以金字塔形状(自下而上)逐步降低,且来源于同一张原始图的图像分辨率集合。通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

图像金字塔如下图所示:
在这里插入图片描述

金字塔生成方式

生成图像金字塔的方式有两种:下采样上采样
在这里插入图片描述

下采样

下采样:图像分辨率不断降低的过程,对应于 level 0到 level 4的过程。图像下采样一般分为两步:先对图像进行高斯内核卷积 ,再将所有偶数行和列删除进行降采样。所以下采样后,得到的图像只会有原来四分之一的大小。

opencv库中可以直接调用pyrDown()函数来进行下采样

dst = pyrDown(src, dst, dstsize[, borderType]]]);
其中,
src 表示输入图像;
dst 表示输出图像,和输入图像具有一样的尺寸和类型;
dstsize 表示输出图像的大小,默认值为Size();
borderType 表示像素外推方法,详见cv::bordertypes 。

当然也可以借用opencv库自己写
首先进行高斯模糊


void GaussianSmooth(const Mat &src, Mat &dst, double sigma)
{
	GaussianBlur(src, dst, Size(0, 0), sigma);
	/*
		if(src.channels() != 1 && src.channels() != 3)
			return;

		//
		sigma = sigma > 0 ? sigma : -sigma;
		//高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
		//ksize为奇数
		int ksize = cvRound(sigma * 3) * 2 + 1;

		//cout << "ksize=" <<ksize<<endl;
		//	dst.create(src.size(), src.type());
		if(ksize == 1)
		{
			src.copyTo(dst);
			return;
		}

		//计算一维高斯核
		double *kernel = new double[ksize];

		double scale = -0.5/(sigma*sigma);
		const double PI = 3.141592653;
		double cons = 1/sqrt(-scale / PI);

		double sum = 0;
		int kcenter = ksize/2;
		int i = 0, j = 0;
		for(i = 0; i < ksize; i++)
		{
			int x = i - kcenter;
			*(kernel+i) = cons * exp(x * x * scale);//一维高斯函数
			sum += *(kernel+i);

	//		cout << " " << *(kernel+i);
		}
	//	cout << endl;
		//归一化,确保高斯权值在[0,1]之间
		for(i = 0; i < ksize; i++)
		{
			*(kernel+i) /= sum;
	//		cout << " " << *(kernel+i);
		}
	//	cout << endl;

		dst.create(src.size(), src.type());
		Mat temp;
		temp.create(src.size(), src.type());

		pixel_t* srcData = (pixel_t*)src.data;
		pixel_t* dstData = (pixel_t*)dst.data;
		pixel_t* tempData = (pixel_t*)temp.data;

		int srcStep = src.step/sizeof(srcData[0]);
		int dstStep = src.step/sizeof(dstData[0]);
		int tempStep = src.step/sizeof(tempData[0]);


		//x方向一维高斯模糊
		for(int y = 0; y < src.rows; y++)
		{
			for(int x = 0; x < src.cols; x++)
			{
				double mul = 0;
				sum = 0;
				double bmul = 0, gmul = 0, rmul = 0;
				for(i = -kcenter; i <= kcenter; i++)
				{
					if((x+i) >= 0 && (x+i) < src.cols)
					{
						if(src.channels() == 1)
						{
							mul += *(srcData+y*srcStep+(x+i))*(*(kernel+kcenter+i));
						}
						else
						{
							bmul += *(srcData+y*srcStep+(x+i)*src.channels() + 0)*(*(kernel+kcenter+i));
							gmul += *(srcData+y*srcStep+(x+i)*src.channels() + 1)*(*(kernel+kcenter+i));
							rmul += *(srcData+y*srcStep+(x+i)*src.channels() + 2)*(*(kernel+kcenter+i));
						}
						sum += (*(kernel+kcenter+i));
					}
				}
				if(src.channels() == 1)
				{
					*(tempData+y*tempStep+x) = mul/sum;
				}
				else
				{
					*(tempData+y*tempStep+x*temp.channels()+0) = bmul/sum;
					*(tempData+y*tempStep+x*temp.channels()+1) = gmul/sum;
					*(tempData+y*tempStep+x*temp.channels()+2) = rmul/sum;
				}
			}
		}


		//y方向一维高斯模糊
		for(int x = 0; x < temp.cols; x++)
		{
			for(int y = 0; y < temp.rows; y++)
			{
				double mul = 0;
				sum = 0;
				double bmul = 0, gmul = 0, rmul = 0;
				for(i = -kcenter; i <= kcenter; i++)
				{
					if((y+i) >= 0 && (y+i) < temp.rows)
					{
						if(temp.channels() == 1)
						{
							mul += *(tempData+(y+i)*tempStep+x)*(*(kernel+kcenter+i));
						}
						else
						{
							bmul += *(tempData+(y+i)*tempStep+x*temp.channels() + 0)*(*(kernel+kcenter+i));
							gmul += *(tempData+(y+i)*tempStep+x*temp.channels() + 1)*(*(kernel+kcenter+i));
							rmul += *(tempData+(y+i)*tempStep+x*temp.channels() + 2)*(*(kernel+kcenter+i));
						}
						sum += (*(kernel+kcenter+i));
					}
				}
				if(temp.channels() == 1)
				{
					*(dstData+y*dstStep+x) = mul/sum;
				}
				else
				{
					*(dstData+y*dstStep+x*dst.channels()+0) = bmul/sum;
					*(dstData+y*dstStep+x*dst.channels()+1) = gmul/sum;
					*(dstData+y*dstStep+x*dst.channels()+2) = rmul/sum;
				}

			}
		}

		delete[] kernel;
	*/
}

然后进行降采样

//降采样(隔点采样)
void DownSample(const Mat& src, Mat& dst)
{
	if (src.channels() != 1)
		return;

	if (src.cols <= 1 || src.rows <= 1)
	{
		src.copyTo(dst);
		return;
	}

	dst.create((int)(src.rows / 2), (int)(src.cols / 2), src.type());
	//cout<<"-- "<<dst.rows<<" " <<dst.cols << " --"<<endl;

	pixel_t* srcData = (pixel_t*)src.data;
	pixel_t* dstData = (pixel_t*)dst.data;

	int srcStep = src.step / sizeof(srcData[0]);
	int dstStep = dst.step / sizeof(dstData[0]);

	int m = 0, n = 0;
	for (int j = 0; j < src.cols; j += 2, n++)
	{
		m = 0;
		for (int i = 0; i < src.rows; i += 2, m++)
		{
			pixel_t sample = *(srcData + srcStep * i + src.channels() * j);

			//防止当图像长宽不一致时,长宽为奇数时,m,n越界
			if (m < dst.rows && n < dst.cols)
			{
				*(dstData + dstStep * m + dst.channels() * n) = sample;

			}
		}
	}

}

下采样效果图
在这里插入图片描述

上采样

上采样:图像分辨率不断增大的过程,对应于 level 4到 level 0的过程。图像上采样一般分为两步:先将图像在每个方向放大为原来的两倍,新增的行和列用0填充,再使用先前同样的内核与放大后的图像卷积,获得新增像素的近似值。所以上采样后,得到的图像会是原来的四倍。

opencv库中可以直接调用pyrUp()函数来进行下采样

dst = pyrUp(src[, dst[, dstsize[, borderType]]])

其中,
src 表示输入图像;
dst 表示输出图像,和输入图像具有一样的尺寸和类型;
dstsize 表示输出图像的大小,默认值为Size();
borderType 表示像素外推方法,详见cv::bordertypes 。

当然也可以借用opencv库自己写
首先进行线性插值放大

//线性插值放大
void UpSample(const Mat &src, Mat &dst)
{
	if (src.channels() != 1)
		return;
	dst.create(src.rows * 2, src.cols * 2, src.type());

	pixel_t* srcData = (pixel_t*)src.data;
	pixel_t* dstData = (pixel_t*)dst.data;
	
	int srcStep = src.step / sizeof(srcData[0]);
	int dstStep = dst.step / sizeof(dstData[0]);
	cout << dst.rows << " " << dst.cols << " " << srcStep << " " << dstStep << endl;

	int m = 0, n = 0;
	for (int j = 0; j < src.cols - 1; j++, n += 2)
	{
		m = 0;
		for (int i = 0; i < src.rows - 1; i++, m += 2)
		{
			double sample = *(srcData + srcStep * i + src.channels() * j);
			*(dstData + dstStep * m + dst.channels() * n) = sample;

			double rs = *(srcData + srcStep * (i)+src.channels()*j) + (*(srcData + srcStep * (i + 1) + src.channels()*j));
			*(dstData + dstStep * (m + 1) + dst.channels() * n) = rs / 2;
			double cs = *(srcData + srcStep * i + src.channels()*(j)) + (*(srcData + srcStep * i + src.channels()*(j + 1)));
			*(dstData + dstStep * m + dst.channels() * (n + 1)) = cs / 2;

			double center = (*(srcData + srcStep * (i + 1) + src.channels() * j))
				+ (*(srcData + srcStep * i + src.channels() * j))
				+ (*(srcData + srcStep * (i + 1) + src.channels() * (j + 1)))
				+ (*(srcData + srcStep * i + src.channels() * (j + 1)));

			*(dstData + dstStep * (m + 1) + dst.channels() * (n + 1)) = center / 4;
			//cout << i << " " << j <<" "<< sample <<" "<< rs << " " << cs << " " << center << endl;
		}

	}



	if (dst.rows < 3 || dst.cols < 3)
		return;

	//最后两行两列
	for (int k = dst.rows - 1; k >= 0; k--)
	{
		*(dstData + dstStep * (k)+dst.channels()*(dst.cols - 2)) = *(dstData + dstStep * (k)+dst.channels()*(dst.cols - 3));
		*(dstData + dstStep * (k)+dst.channels()*(dst.cols - 1)) = *(dstData + dstStep * (k)+dst.channels()*(dst.cols - 3));
	}
	for (int k = dst.cols - 1; k >= 0; k--)
	{
		*(dstData + dstStep * (dst.rows - 2) + dst.channels()*(k)) = *(dstData + dstStep * (dst.rows - 3) + dst.channels()*(k));
		*(dstData + dstStep * (dst.rows - 1) + dst.channels()*(k)) = *(dstData + dstStep * (dst.rows - 3) + dst.channels()*(k));
	}

}

然后进行高斯平滑(和下采样一样)

上采样效果图
在这里插入图片描述

补充

金字塔的层数是根据图像的原始大小和塔顶图像的大小共同决定,其计算公式如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值