这是我第一个博客,一方面我把博客看作笔记,记录我在学习的过程中遇到的问题,写博客的过程也是加深理解的过程;另一方面也是为了与大家分享,共同进步。
双线性插值分为两步:1. 先做图像尺寸缩放;2. 求缩放后的图像像素灰度值。
- 图像尺度缩放变换的公式
其中,(x, y)是原图坐标,(x’, y’)是变换后的坐标,a,b分别是水平和垂直方向的缩放因子。这是前向变换,即从原图变换到目标图像。在双线性插值处理中,我们往往已知原图大小和缩放因子,求得缩放后的图像灰度值。目标图像灰度值就是根据目标像素在原图中的位置的灰度值双线性插值得到的。根据目标图像的像素求得对应于原图中的位置就是后向变换,公式如下:
根据公式,后向变换计算出来的坐标一般不是整数。最邻近插值就是把后向变换的坐标取整,获得原图坐标处的像素值作为目标图像的像素值。
上述变换公式都假定原图和目标图像的坐标原点在左上角,前向变换没有任何问题,但是后向变换就存在问题了。如下图所示,假设把5x5的图像缩小为3x3的,那么原图与目标图像之间的坐标对应关系如下图所示:这里只画出第一行!
其中,黑色的原图,红色的框是目标图像。变换后的图像的3个像素平均占据在原图的5个像素的前3个位置上,但是由于目标图像的3个像素起始点选在了原图的左上角,导致原图最后一个像素坐标没有以及最后一行的像素坐标都没有使用到。为了充分利用原图每一个像素坐标,应该保持两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,如下图所示。
所以,最终的后向变换公式为
- 双线性插值
在求得图像缩放后的坐标只之后,就需要对目标图像的每一个像素计算其像素值了。
双线性变换其实就是在水平和垂直方向分别作一次线性变换,如下图所示
假设目标图像P点坐标为P(x, y),现在利用P点在原图的位置的最邻近的4个点的像素值拟合P点的像素值。P点坐标在原图上最邻近的4点分别为左上、右上、左下、右下。
(1). 先对x方向插值
Q12,R2,Q22; Q11,R1,Q21三点共线,所以
(2). 再对y方向进行插值
R2,P,R1三点共线,有
其中,与P点最邻近的4点坐标分别为
Q11 = (x’, y’), Q21 = (x’+1, y’)
Q21 = (x’, y’+1), Q22 = (x’+1, y’ + 1)
其中,(x’, y’)是P点坐标在原图的整数部分。
所以,上述公式可以进一步变化为
其中,u,v分别是P点y,x坐标取整后的小数部分。
至此,双线性插值的原理部分讲完了。其实这个原理也不完全是我自己写的,我也是参考了很多网上的别人的播客,待会我会在下面把那些博客贴上来。
下面附上我的双线性插值代码实现:
这里写代码片
void Geometry_Transform::Interpolate(Mat src, Mat& mask)
{
int Rows = mask.rows;
int Cols = mask.cols;
uchar* mask_data = mask.data;
uchar* src_data = _src.data;
if (3 == src.channels())
{
for (int i = 0; i < Rows; ++i)
{
double r = (i + 0.5 ) * 1.0 / _sy - 0.5;//浮点行
for (int j = 0; j < Cols; ++j)
{
double c = (j + 0.5 ) * 1.0 / _sx - 0.5;//浮点列
//获得四个顶点
int lRow = static_cast<int>(r);//最邻近行,取整
int nRow = lRow + 1;
int lCol = static_cast<int>(c);//最邻近列
int nCol = lCol + 1;
double u = r - lRow;
double v = c - lCol;
if (lRow >= _src.rows - 1 && lCol >= _src.cols - 1)//右下角
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]]);//B通道
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] + 1);//G通道
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2]);//R通道
}
else if (lRow >= _src.rows - 1)//最后一行
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 2]);
}
else if (lCol >= _src.cols /** _src.channels()*/ - 1)//最后一列
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 2]);
}
else
{
mask_data[i * mask.step[0] + j * mask.step[1]] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 1] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 1] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 2] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 2] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1] + 2]);
}
}//end inner for
}//end outer for
}//end color
else
{
for (int i = 0; i < Rows; ++i)
{
double r = i * 1.0 / _sy;//浮点行
//uchar* mask_p = mask.ptr<uchar>(i);//当前行指针
for (int j = 0; j < Cols; ++j)
{
double c = j * 1.0 / _sx;//浮点列
//获得四个顶点
int lRow = static_cast<int>(r);//最邻近行,取整
int nRow = lRow + 1;
int lCol = static_cast<int>(c);//最邻近列
int nCol = lCol + 1;
double u = r - lRow;
double v = c - lCol;
if (lRow >= _src.rows - 1 && lCol >= _src.cols - 1)//右下角
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]]);
}
else if (lRow >= _src.rows - 1)//最后一行
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]]);
}
else if (lCol >= _src.cols - 1)//最后一列
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]]);
}
else
{
mask_data[i * mask.step[0] + j * mask.step[1]] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1]]);
}
}//end inner for
}//end outer for
}//end else
}
这里写代码片
Mat Geometry_Transform::Up_Down(const double sx, const double sy)
{
int sw = static_cast<int>(_src.rows * sy);
int sh = static_cast<int>(_src.cols * sx);
Mat mask = Mat::zeros(sw, sh, _src.type());
_sx = sx;
_sy = sy;
if (sx == 1 && sy == 1)//不做缩放运算,直接返回原图
return _src;
else
{
Interpolate(_src, mask);
return mask;
}
}
这里写代码片
int main()
{
const string impath = "D:/opera.png";
Mat src = imread(impath);
if (!src.data)
{
cerr << "读取图片失败..." << endl;
return 0;
}
Mat gray;
cvtColor(src, gray, CV_BGR2GRAY);
const double sx = 3;
const double sy = 3;
Geometry_Transform Geo_obj(src, sx, sy);
Mat mask = Geo_obj.Up_Down();
//调用OpenCV自带的缩放函数测试
Mat res;
resize(src, res, Size(0, 0), sx, sy, INTER_LINEAR);
Mat ss;
subtract(mask, rs, ss);//做差比较
return 0;
}
PS:
- 啊,我的第一个博客终于写完了。第一次写博客发现好难,文笔向来不好,不知道怎么写。对于数学公式插入进来不知道怎么编辑,不会latex语法,后面我会学一点latex语法,尽量把公式编辑的漂亮一点吧。
以下链接是我主要参考的文章,他们讲的已经很好了,真心佩服他们。
http://www.w2bc.com/article/228253
http://www.cnblogs.com/funny-world/p/3162003.html