图像缩放是指将图像的尺寸变小或变大的过程,也就是减少或增加源图像数据的像素个数。图像缩放一定程度上会造成信息的丢失,因此需要考虑适宜的方法进行操作。
下面介绍两种常用的图像缩放方法的原理及实现
1.基于等间隔提取图像缩放
等间隔提取图像缩放是通过对源图像进行均匀采样来完成的。对于源图像数据f(x,y),其分辨率为M*N,如果将其分辨率改变成m*n,对于等间隔采样而言,其宽度缩放因子k1=m/M,高度缩放因子k2=n/N,对于图像而言,图像缩放在其水平方向的等间隔采样为k1,垂直方向上的等间隔采样为k2。若满足k1=k2,源图像数据将等比例缩放,否则源图像数据的宽度和高度将发生不同程度的缩放,造成图像变形扭曲现象
2.基于区域子块图像缩放
区域子块提取图像缩放是通过对源图像进行区域子块划分,然后提取子块中像素值作为采样像素以构成新图像来实现的。提取子块像素值常用的方法有计算子块像素的中值与计算子块像素的均值。对源图像进行区域划分同样也有多种不同方法,常用方法是根据缩放因子等比例提取子块与自适应因子提取子块,假设源图像数据f(x,y)的分辨率为8*8,图像g(x,y)的分辨率为2*2,则区域子块提取方式如下:
子块区域提取后g(x,y)为下式
下面分别实现这两种方式对图像进行缩放
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
//基于等间隔提取图像缩放
Mat imgReduction1(Mat& src, float kx, float ky)
{
//获取输出图像分辨率
int nRows = cvRound(src.rows * kx);
int nCols = cvRound(src.cols * ky);
Mat result(nRows, nCols, src.type());
for (int i = 0; i < nRows; ++i)
{
for (int j = 0; j < nCols; ++j)
{
//根据水平因子计算坐标
int x = static_cast<int>((i + 1) / kx + 0.5) - 1;
//根据垂直因子计算坐标
int y = static_cast<int>((j + 1) / ky + 0.5) - 1;
result.at<Vec3b>(i, j) = src.at<Vec3b>(x, y);
}
}
return result;
}
Vec3b areaAverage(const Mat& src, Point_<int> leftPoint, Point_<int> rightPoint)
{
int temp1 = 0, temp2 = 0, temp3 = 0;
//计算区域子块像素点个数
int nPix = (rightPoint.x - leftPoint.x + 1) * (rightPoint.y - leftPoint.y + 1);
//对于区域子块各个通道对像素值求和
for (int i = leftPoint.x; i <= rightPoint.x; i++)
{
for (int j = leftPoint.y; j <= rightPoint.y; j++)
{
temp1 += src.at<Vec3b>(i, j)[0];
temp2 += src.at<Vec3b>(i, j)[1];
temp3 += src.at<Vec3b>(i, j)[2];
}
}
//对每个通道求均值
Vec3b vecTemp;
vecTemp[0] = temp1 / nPix;
vecTemp[1] = temp2 / nPix;
vecTemp[2] = temp3 / nPix;
return vecTemp;
}
//基于区域子块图像缩放
Mat imgReduction2(const Mat& src, double kx, double ky)
{
//获取输出图像分辨率
int nRows = cvRound(src.rows * kx);
int nCols = cvRound(src.cols * ky);
Mat result(nRows, nCols, src.type());
//区域子块的左上角行列坐标
int leftRowCoordinate = 0;
int leftColCoordinate = 0;
for (int i = 0; i < nRows; ++i)
{
//根据水平因子计算坐标
int x = static_cast<int>((i + 1) / kx + 0.5) - 1;
for (int j = 0; j < nCols; ++j)
{
//根据垂直因子计算坐标
int y = static_cast<int>((j + 1) / kx + 0.5) - 1;
//求解区域子块的均值
result.at<Vec3b>(i, j) = areaAverage(src,
Point_<int>(leftRowCoordinate, leftColCoordinate), Point_<int>(x, y));
//更新下子块左上角的列坐标,行坐标不变
leftColCoordinate = y + 1;
}
leftColCoordinate = 0;
//更新下子块左上角的行坐标
leftRowCoordinate = x + 1;
}
return result;
}
int main()
{
Mat src = imread("C:\\Users\\32498\\Pictures\\16.png");
if (!src.data)
{
return -1;
}
imshow("src", src);
Mat dst1 = imgReduction1(src, 0.5, 0.5);
imshow("dst1", dst1);
Mat dst2 = imgReduction2(src, 0.5, 0.5);
imshow("dst2", dst2);
waitKey();
return 0;
}
对代码进行一些注释
- cvRound():返回跟参数最接近的整数值,即四舍五入;
与之近似的函数还有
- cvFloor() :返回不大于参数的最大整数值,即向下取整;
- cvCeil() :返回不小于参数的最小整数值,即向上取整;
Point_<int>(x,y)
opencv中内置了三种二维平面点坐标类型,这就带来一个问题:即我们在编写图像处理算法的时候有时候并不确定调用者需要哪一种坐标点数据类型,只写一种吧可能无法满足需要,三种都分别实现又费时耗力,且没利用好c++多态泛型的特性。而opencv中的模板类cv::Point_
刚好可以解决这个问题,实际上以上三种类型的点都是cv::Point_
的具体实例化,在opencv里转到定义就可以查看到
typedef Point_<int> Point2i;
typedef Point_<int64> Point2l;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
typedef Point2i Point;
point_ 的定义如下:
template<typename _Tp> class Point_
{
public:
typedef _Tp value_type;
//! default constructor
Point_();
Point_(_Tp _x, _Tp _y);
Point_(const Point_& pt);
Point_(const Size_<_Tp>& sz);
Point_(const Vec<_Tp, 2>& v);
Point_& operator = (const Point_& pt);
//! conversion to another data type
template<typename _Tp2> operator Point_<_Tp2>() const;
//! conversion to the old-style C structures
operator Vec<_Tp, 2>() const;
//! dot product
_Tp dot(const Point_& pt) const;
//! dot product computed in double-precision arithmetics
double ddot(const Point_& pt) const;
//! cross-product
double cross(const Point_& pt) const;
//! checks whether the point is inside the specified rectangle
bool inside(const Rect_<_Tp>& r) const;
_Tp x; //!< x coordinate of the point
_Tp y; //!< y coordinate of the point
};