基础知识
图像分辨率是指图像单位英寸中所包含的像素数,常用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));
}
}
然后进行高斯平滑(和下采样一样)
上采样效果图
补充
金字塔的层数是根据图像的原始大小和塔顶图像的大小共同决定,其计算公式如下: