4.1.1 最近邻插值
最简单图像缩放方法,原理:提取源数据图像中与其邻域最近像素值来作为目标图像相对应点的像素值。
目标各像素点的灰度值-->源图像中与其最邻近像素的灰度值。
OpenCV中提供3个将浮点型数转换成整数的函数:cvRound/cvFloor/cvCeil
1 ////////https://blog.csdn.net/linqianbi/article/details/78593724 2 ////////https://blog.csdn.net/qq_22424571/article/details/80918549 3 #include<opencv2corecore.hpp> 4 #include<opencv2highguihighgui.hpp> 5 #include<opencv2imgprocimgproc.hpp> 6 #include<iostream> 7 using namespace cv; 8 //最近邻插值 9 //基于等间隔提取图像的缩放 10 Mat imageReduction1(Mat &srcImage, float kx, float ky)//原始图形以及缩放比例 11 { 12 //获取输出图像分辨率 13 int nRows = cvRound(srcImage.rows * kx);//cvRound这个函数返回的是和参数最接近的整数 14 int nCols = cvRound(srcImage.cols * ky); 15 Mat resultImage(nRows, nCols, srcImage.type());//创建一张输出的图像 16 for (int i = 0; i < nRows; i++) 17 { 18 //根据水平因子计算在原图中的坐标 19 int x = static_cast<int>((i + 1) / kx + 0.5) - 1; 20 for (int j = 0; j < nCols; j++) 21 { 22 //根据垂直因子计算在原图中的坐标 23 int y = static_cast<int>((j + 1) / ky + 0.5) - 1; 24 resultImage.at<Vec3b>(i, j) = srcImage.at<Vec3b>(x, y); 25 } 26 } 27 return resultImage; 28 } 29 30 //基于区域子块提取图像缩放 31 //求每个区域子块中的像素的通道的平均值 32 Vec3b areaAverage(Mat &srcImage, Point_<int> leftPoint, Point_<int> rightPoint) 33 { 34 int temp1 = 0, temp2 = 0, temp3 = 0;//用来保存区域块中的每个通道像素的和 35 //计算区域块中的像素点的个数 36 int nPix = (rightPoint.x - leftPoint.x + 1)*(rightPoint.y - leftPoint.y + 1); 37 //计算区域子块中的各个通道的像素和 38 for (int i = leftPoint.x; i <= rightPoint.x; i++) 39 { 40 for (int j = leftPoint.y; j <= rightPoint.y; j++) 41 { 42 temp1 += srcImage.at<Vec3b>(i, j)[0];//求和区域块中的蓝绿红通道的像素和 43 temp2 += srcImage.at<Vec3b>(i, j)[1]; 44 temp3 += srcImage.at<Vec3b>(i, j)[2]; 45 } 46 } 47 //对区域块中的每个通道求平均值 48 Vec3b vecTemp; 49 vecTemp[0] = temp1 / nPix; 50 vecTemp[1] = temp2 / nPix; 51 vecTemp[2] = temp3 / nPix; 52 return vecTemp; 53 } 54 55 Mat imageReduction2(Mat &srcImage, float kx, float ky) 56 { 57 //获取输出图像分辨率 58 int nRows = cvRound(srcImage.rows * kx);//cvRound这个函数返回的是和参数最接近的整数 59 int nCols = cvRound(srcImage.cols * ky); 60 Mat resultImage(nRows, nCols, srcImage.type());//创建一张输出的图像 61 //区域子块的左上角行列坐标 62 int leftRowcoordinate = 0; 63 int leftColcoordinate = 0; 64 65 for (int i = 0; i < nRows; i++) 66 { 67 //根据水平因子计算在原图中的坐标 68 int x = static_cast<int>((i + 1) / kx + 0.5) - 1; 69 for (int j = 0; j < nCols; j++) 70 { 71 //根据垂直因子计算在原图中的坐标 72 int y = static_cast<int>((j + 1) / ky + 0.5) - 1; 73 //求区域子块的均值 74 resultImage.at<Vec3b>(i, j) = areaAverage(srcImage, Point_<int>(leftRowcoordinate, leftColcoordinate), Point_<int>(x, y)); 75 //更新下子块左上角的列坐标,行坐标不变 76 leftColcoordinate = y + 1; 77 } 78 //一列循环完毕重新将列坐标置零 79 leftColcoordinate = 0; 80 //更新下子块左上角的行坐标 81 leftRowcoordinate = x + 1; 82 } 83 return resultImage; 84 85 } 86 87 88 int main() 89 { 90 Mat srcIamge = imread("D:\大海.jpg"); 91 if (!srcIamge.data) 92 { 93 printf("image could not load... "); 94 return -1; 95 } 96 imshow("srcIamge", srcIamge); 97 Mat resultImage1 = imageReduction1(srcIamge, 0.5, 0.5); 98 imshow("res1", resultImage1); 99 100 Mat resultImage2 = imageReduction2(srcIamge, 0.5, 0.5); 101 imshow("res2", resultImage2); 102 waitKey(0); 103 return 0; 104 105 }
参考:
https://blog.csdn.net/linqianbi/article/details/78593724
https://blog.csdn.net/qq_22424571/article/details/80918549
https://blog.csdn.net/gbyy42299/article/details/80406509
这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。
双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
4.1.2 双线性插值
1 ////////https://blog.csdn.net/Gone_HuiLin/article/details/53223222 2 #include <opencv2/imgproc/imgproc.hpp> 3 #include <opencv2/core/core.hpp> 4 #include <opencv2/highgui/highgui.hpp> 5 #include <iostream> 6 // 实现双线性插值图像缩放 7 cv::Mat BilinearInterpolation(cv::Mat srcImage) 8 { 9 CV_Assert(srcImage.data != NULL); 10 int srcRows = srcImage.rows; 11 int srcCols = srcImage.cols; 12 int srcStep = srcImage.step; 13 // 构建目标图像 14 cv::Mat dstImage = cv::Mat( 15 cv::Size(640, 480), srcImage.type(), 16 cv::Scalar::all(0)); 17 int dstRows = dstImage.rows; 18 int dstCols = dstImage.cols; 19 int dstStep = dstImage.step; 20 // 数据定义及转换 21 IplImage src = srcImage; 22 IplImage dst = dstImage; 23 std::cout << "srcCols:" << srcCols << " srcRows:" << 24 srcRows << "srcStep:" << srcStep << std::endl; 25 std::cout << "dstCols:" << dstCols << " dstRows:" << 26 dstRows << "dstStep:" << dstStep << std::endl; 27 // 坐标定义 28 float srcX = 0, srcY = 0; 29 float t1X = 0, t1Y = 0, t1Z = 0; 30 float t2X = 0, t2Y = 0, t2Z = 0; 31 for (int j = 0; j < dstRows - 1; j++) 32 { 33 for (int i = 0; i < dstCols - 1; i++) 34 { 35 // 缩放映射关系 36 srcX = (i + 0.5)*((float)srcCols) / (dstCols)-0.5; 37 srcY = (j + 0.5)*((float)srcRows) / (dstRows)-0.5; 38 int iSrcX = (int)srcX; 39 int iSrcY = (int)srcY; 40 // 三通道求邻域加权值1 41 t1X = ((uchar*)(src.imageData + srcStep*iSrcY))[ 42 iSrcX * 3 + 0] * (1 - std::abs(srcX - iSrcX)) + 43 ((uchar*)(src.imageData + srcStep*iSrcY))[ 44 (iSrcX + 1) * 3 + 0] * (srcX - iSrcX); 45 t1Y = ((uchar*)(src.imageData + srcStep*iSrcY))[ 46 iSrcX * 3 + 1] * (1 - std::abs(srcX - iSrcX)) + 47 ((uchar*)(src.imageData + srcStep*iSrcY))[ 48 (iSrcX + 1) * 3 + 1] * (srcX - iSrcX); 49 t1Z = ((uchar*)(src.imageData + srcStep*iSrcY))[ 50 iSrcX * 3 + 2] * (1 - std::abs(srcX - iSrcX)) + 51 ((uchar*)(src.imageData + srcStep*iSrcY))[ 52 (iSrcX + 1) * 3 + 2] * (srcX - iSrcX); 53 // 三通道求邻域加权值2 54 t2X = ((uchar*)(src.imageData + srcStep*( 55 iSrcY + 1)))[iSrcX * 3] * (1 - std::abs(srcX - iSrcX)) 56 + ((uchar*)(src.imageData + srcStep*( 57 iSrcY + 1)))[(iSrcX + 1) * 3] * (srcX - iSrcX); 58 t2Y = ((uchar*)(src.imageData + srcStep*( 59 iSrcY + 1)))[iSrcX * 3 + 1] * (1 - std::abs(srcX - iSrcX)) 60 + ((uchar*)(src.imageData + srcStep*( 61 iSrcY + 1)))[(iSrcX + 1) * 3 + 1] * (srcX - iSrcX); 62 t2Z = ((uchar*)(src.imageData + srcStep*( 63 iSrcY + 1)))[iSrcX * 3 + 2] * (1 - std::abs(srcX - iSrcX)) 64 + ((uchar*)(src.imageData + srcStep*(iSrcY + 1)))[( 65 iSrcX + 1) * 3 + 2] * (srcX - iSrcX); 66 // 根据公式求解目标图像加权 67 ((uchar*)(dst.imageData + dstStep*j))[i * 3] = 68 t1X*(1 - std::abs(srcY - iSrcY)) + t2X*( 69 std::abs(srcY - iSrcY)); 70 ((uchar*)(dst.imageData + dstStep*j))[i * 3 + 1] = 71 t1Y*(1 - std::abs(srcY - iSrcY)) + t2Y*( 72 std::abs(srcY - iSrcY)); 73 ((uchar*)(dst.imageData + dstStep*j))[i * 3 + 2] = 74 t1Z*(1 - std::abs(srcY - iSrcY)) + t2Z*( 75 std::abs(srcY - iSrcY)); 76 } 77 // 列操作 78 ((uchar*)(dst.imageData + dstStep*j))[(dstCols - 1) * 3] = 79 ((uchar*)(dst.imageData + dstStep*j))[(dstCols - 2) * 3]; 80 ((uchar*)(dst.imageData + dstStep*j))[(dstCols - 1) * 3 + 81 1] = ((uchar*)(dst.imageData + dstStep*j))[( 82 dstCols - 2) * 3 + 1]; 83 ((uchar*)(dst.imageData + dstStep*j))[(dstCols - 1) * 3 84 + 2] = ((uchar*)(dst.imageData + dstStep*j))[( 85 dstCols - 2) * 3 + 2]; 86 } 87 // 行操作 88 for (int i = 0; i < dstCols * 3; i++) 89 { 90 ((uchar*)(dst.imageData + dstStep*(dstRows - 1)))[i] = 91 ((uchar*)(dst.imageData + dstStep*(dstRows - 2)))[i]; 92 } 93 return dstImage; 94 } 95 96 int main() 97 { 98 cv::Mat srcImage = cv::imread("D:\大海.jpg"); 99 if (!srcImage.data) 100 return -1; 101 cv::Mat dstImage = BilinearInterpolation(srcImage); 102 cv::imshow("srcImage", srcImage); 103 cv::imshow("dstImage", dstImage); 104 cv::waitKey(0); 105 return 0; 106 }
参考:
https://www.cnblogs.com/yssongest/p/5303151.html
4.1.3 插值操作性能对比
1 //////////https://blog.csdn.net/spw_1201/article/details/53544014 2 //////////最邻近、双线性、基于像素区域、立方插值及兰索斯插值 3 #include <opencv2/imgproc/imgproc.hpp> 4 #include <opencv2/core/core.hpp> 5 #include <opencv2/highgui/highgui.hpp> 6 #include <iostream> 7 using namespace cv; 8 using namespace std; 9 void ResizeExample(Mat srcImage) 10 { 11 //判断输入有效性 12 CV_Assert(srcImage.data != NULL); 13 imshow("srcImage", srcImage); 14 Mat dstImage(256, 256, CV_8UC3); 15 //测试1:默认参数为双线性插值 16 double tTime; 17 tTime = (double)getTickCount(); 18 const int nTimes = 100; 19 for (int i = 0; i < nTimes; i++) 20 { 21 resize(srcImage, dstImage, dstImage.size(), 0, 0); 22 } 23 tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); 24 tTime /= nTimes; 25 cout << "text1: " << tTime << endl; 26 imshow("1 default parameters:dstImage", dstImage); 27 //测试2:最邻近插值 28 tTime = (double)getTickCount(); 29 30 for (int i = 0; i < nTimes; i++) 31 { 32 resize(srcImage, dstImage, Size(256, 256), 0, 0, INTER_NEAREST); 33 } 34 tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); 35 tTime /= nTimes; 36 cout << "text2: " << tTime << endl; 37 imshow("2 INTER_NEAREST:dstImage", dstImage); 38 //测试3:像素区域插值 39 tTime = (double)getTickCount(); 40 41 for (int i = 0; i < nTimes; i++) 42 { 43 resize(srcImage, dstImage, Size(256, 256), 0.5, 0.5, INTER_AREA); 44 } 45 tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); 46 tTime /= nTimes; 47 cout << "text3: " << tTime << endl; 48 imshow("3 INTER_AREA : dstImage", dstImage); 49 //测试4:三次插值 50 tTime = (double)getTickCount(); 51 52 for (int i = 0; i < nTimes; i++) 53 { 54 resize(srcImage, dstImage, Size(), 0.5, 0.5, INTER_CUBIC); 55 } 56 tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); 57 tTime /= nTimes; 58 cout << "text4: " << tTime << endl; 59 imshow("4 INTER_CUBIC : dstImage", dstImage); 60 //测试5:三次插值 61 tTime = (double)getTickCount(); 62 63 for (int i = 0; i < nTimes; i++) 64 { 65 resize(srcImage, dstImage, Size(), 0.5, 0.5, INTER_LANCZOS4); 66 } 67 tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); 68 tTime /= nTimes; 69 cout << "text5: " << tTime << endl; 70 imshow("5 INTER_LANCZOS4 : dstImage", dstImage); 71 } 72 int main() 73 { 74 Mat srcImage = imread("D:\大海.jpg"); 75 if (!srcImage.data) 76 return -1; 77 ResizeExample(srcImage); 78 waitKey(0); 79 return 0; 80 }
4.1.4图像金字塔
其实非常好理解,如上图所示,我们将一层层的图像比喻为金字塔,层级越高,则图像尺寸越小,分辨率越低。
两种类型的金字塔:
- 高斯金字塔:用于下采样,主要的图像金字塔;
- 拉普拉斯金字塔:用于重建图像,也就是预测残差(我的理解是,因为小图像放大,必须插入一些像素值,那这些像素值是什么才合适呢,那就得进行根据周围像素进行预测),对图像进行最大程度的还原。比如一幅小图像重建为一幅大图像,
图像金字塔有两个高频出现的名词:上采样和下采样。现在说说他们俩。
- 上采样:就是图片放大(所谓上嘛,就是变大),使用PryUp函数
- 下采样:就是图片缩小(所谓下嘛,就是变小),使用PryDown函数
下采样将步骤:
- 对图像进行高斯内核卷积
- 将所有偶数行和列去除
下采样就是图像压缩,会丢失图像信息。
上采样步骤:
- 将图像在每个方向放大为原来的两倍,新增的行和列用0填充;
- 使用先前同样的内核(乘以4)与放大后的图像卷积,获得新增像素的近似值。
函数 cv2.pyrDown() 从一个高分辨率大尺寸的图像向上构建一个金字塔(尺寸变小,分辨率降低)。
函数 cv2.pyrUp() 从一个低分辨率小尺寸的图像向下构建一个金子塔(尺寸变大,但分辨率不会)
图像金字塔的一个应用是图像融合。例如,在图像缝合中,你需要将两幅图叠在一起,但是由于连接区域图像像素的不连续性,整幅图的效果看起来会很差,这时图像金字塔就可以排上用场了,可以实现无缝连接。
1 #include <iostream> 2 #include <opencv2/core.hpp> 3 #include <opencv2/highgui.hpp> 4 #include <opencv2/imgproc.hpp> 5 6 using namespace std; 7 using namespace cv; 8 9 int main() 10 { 11 Mat srcImage = imread("D:\大海.jpg"); 12 13 //判断图像是否加载成功 14 if (srcImage.empty()) 15 { 16 cout << "图像加载失败!" << endl; 17 return -1; 18 } 19 else 20 cout << "图像加载成功!" << endl << endl; 21 22 namedWindow("原图像", WINDOW_AUTOSIZE); 23 imshow("原图像", srcImage); 24 25 //两次向下采样操作分别输出 26 Mat pyrDownImage_1, pyrDownImage_2; 27 28 pyrDown(srcImage, pyrDownImage_1); 29 namedWindow("向下采样-1", WINDOW_AUTOSIZE); 30 imshow("向下采样-1", pyrDownImage_1); 31 32 pyrDown(pyrDownImage_1, pyrDownImage_2); 33 namedWindow("向下采样-2", WINDOW_AUTOSIZE); 34 imshow("向下采样-2", pyrDownImage_2); 35 36 //利用向下采样的结果进行向上采样操作 37 Mat pyrUpImage_1, pyrUpImage_2; 38 39 pyrUp(pyrDownImage_2, pyrUpImage_1); 40 namedWindow("向上采样-1", WINDOW_AUTOSIZE); 41 imshow("向上采样-1", pyrUpImage_1); 42 43 pyrUp(pyrUpImage_1, pyrUpImage_2); 44 namedWindow("向上采样-2", WINDOW_AUTOSIZE); 45 imshow("向上采样-2", pyrUpImage_2); 46 47 waitKey(0); 48 49 return 0; 50 }
https://blog.csdn.net/u010682375/article/details/70147508
https://www.cnblogs.com/skyfsm/p/6876732.html