OpenCV2:等间隔采样和局部均值的图像缩小
图像的缩小从物理意义上来说,就是将图像的每个像素的大小缩小相应的倍数。但是,改变像素的物理尺寸显然不是那么容易的,从数字图像处理的角度来看,图像的缩小实际就是通过减少像素个数来实现的。显而易见的,减少图像的像素会造成图像信息丢失。为了在缩小图像的同时,保持原图的概貌特征不丢失,从原图中选择的像素方法是非常重要的。本文主要介绍基于等间隔采样的图像缩小和基于局部均值的图像缩小以及其在OpenCV2的实现。
基于等间隔采样的图像缩小
这种图像缩小算法,通过对原图像像素进行均匀采样来保持所选择到的像素仍旧可以反映原图像的概貌特征。
算法描述
设原图的大小为W*H,宽度和长度的缩小因子分别为看k1和k2,那么采样间隔为:W/k1,W/k2.也就是说在原图的水平方向每隔W/k1,在垂直方向每隔W/k2取一个像素。长和宽的缩小因子k1和k2相等时,图像时等比例缩小,不等时是不等比例缩小,缩小图像的长和宽的比例会发生变化。
基于OpenCV的算法实现
void scaleIntervalSampling(const Mat &src, Mat &dst, double xRatio, double yRatio) { //只处理uchar型的像素 CV_Assert(src.depth() == CV_8U); // 计算缩小后图像的大小 //没有四舍五入,防止对原图像采样时越过图像边界 int rows = static_cast<int>(src.rows * xRatio); int cols = static_cast<int>(src.cols * yRatio); dst.create(rows, cols, src.type()); const int channesl = src.channels(); switch (channesl) { case 1: //单通道图像 { uchar *p; const uchar *origal; for (int i = 0; i < rows; i++){ p = dst.ptr<uchar>(i); //四舍五入 //+1 和 -1 是因为Mat中的像素是从0开始计数的 int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; origal = src.ptr<uchar>(row); for (int j = 0; j < cols; j++){ int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; p[j] = origal[col]; //取得采样像素 } } break; } case 3://三通道图像 { Vec3b *p; const Vec3b *origal; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; origal = src.ptr<Vec3b>(row); for (int j = 0; j < cols; j++){ int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; p[j] = origal[col]; //取得采样像素 } } break; } } }
代码实现还是比较简单的,需要注意的一点就是对于单通道和三通道图像用ptr取行地址时,使用的数据类型的不同。三通道图像中,一个像素有BGR三个分量,可以使用Vec3b来保存。
运行结果
基于局部均值的图像缩小
算法描述
等间隔采样的缩小方法实现简单,但是原图像中未被选中的像素信息会在缩小后的图像中丢失。局部均值的图像缩小方法对其进行了改进。在求缩小图像的像素时,不仅仅单纯的取在原图像中的采样点像素,而是以相邻的两个采样点为分割,将原图像分成一个个的子块。缩小图像的像素取相应子块像素的均值。
根据局部均值缩小的原理:g11 = (f11 + f12 + f21 + f22 ) / 4
基于OpenCV的算法实现
void scalePartAverage(const Mat &src, Mat &dst, double xRatio, double yRatio) { int rows = static_cast<int>(src.rows * xRatio); int cols = static_cast<int>(src.cols * yRatio); dst.create(rows, cols, src.type()); int lastRow = 0; int lastCol = 0; Vec3b *p; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; for (int j = 0; j < cols; j++) { int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; Vec3b pix; average(src, Point_<int>(lastRow, lastCol), Point_<int>(row, col), pix); p[j] = pix; lastCol = col + 1; //下一个子块左上角的列坐标,行坐标不变 } lastCol = 0; //子块的左上角列坐标,从0开始 lastRow = row + 1; //子块的左上角行坐标 } }
算法实现只考虑了三通道图像,单通道图像与之类似。
局部均值缩小图片实现的关键点在子块左上角行和列坐标的求取(子块右下角行和列坐标,就是间隔采样的采样点)。图像子块求出后,计算出子块像素的平均值即是缩小图像的像素。
子块平均像素的求解
void average(const Mat &img, Point_<int> a, Point_<int> b, Vec3b &p) { const Vec3b *pix; Vec3i temp; for (int i = a.x; i <= b.x; i++){ pix = img.ptr<Vec3b>(i); for (int j = a.y; j <= b.y; j++){ temp[0] += pix[j][0]; temp[1] += pix[j][1]; temp[2] += pix[j][2]; } } int count = (b.x - a.x + 1) * (b.y - a.y + 1); p[0] = temp[0] / count; p[1] = temp[1] / count; p[2] = temp[2] / count; }
求取局部平均值时,要注意数据类型。Vec3b实际就元素类型为uchar的Vector,也就是说Vec3b的每一个分量的最大值是255.而求均值时,需要累加像素值,使用uchar时会越界,为此,这里使用Vec3i作为中间值保存。Vec3i是int型的Vector。
运行结果
两种方法的比较
上面两幅图像都是512 * 512的lenna图片缩小到0.3,左边为使用局部均值的算法,右边是使用等间隔采样的算法。
下一章准备介绍下OpenCV下实现几种常见的图像放大方法。