一.简介
图像的几何变换有距离变换 坐标映射 平移 镜像 旋转 缩放 仿射变换等
二.重映射
把一张图像重新排列像素,比如倒置
CV_EXPORTS_W void remap( InputArray src, OutputArray dst,
InputArray map1, InputArray map2,
int interpolation, int borderMode=BORDER_CONSTANT,
const Scalar& borderValue=Scalar());
- src
源图像
- dst
目标图像
- map1
x坐标
- map2
y坐标
- interpolation
表示插值方法
- borderMode
表示边界插值类型
- borderValue
表示插值数值
#include <iostream> #include "opencv2/opencv.hpp" using namespace std; int main(int argc, char* argv[]) { cv::Mat srcImage = cv::imread("a.jpg"); if(!srcImage.data) return -1; // 输出矩阵 cv::Mat resultImage(srcImage.size(), srcImage.type()); // x与y方向矩阵 cv::Mat xMapImage(srcImage.size(), CV_32FC1); cv::Mat yMapImage(srcImage.size(), CV_32FC1); // 取图像的宽高 int rows = srcImage.rows; int cols = srcImage.cols; // 图像遍历 for( int j = 0; j < rows; j++ ) { for( int i = 0; i < cols; i++ ) { //x与y均翻转 xMapImage.at<float>(j,i) = cols - i; yMapImage.at<float>(j,i) = rows - j; } } // 重映射操作 remap(srcImage, resultImage, xMapImage, yMapImage, CV_INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); // 输出结果 cv::imshow("srcImage", srcImage); cv::imshow("resultImage", resultImage); cv::waitKey(0); return 0; }
三.平移
图像的平移操作是将图像的所有像素坐标进行水平或垂直方向移动
//图像平移并且图像大小不变和改变 #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> // 平移操作 图像大小不变 cv::Mat imageTranslation1(cv::Mat& srcImage,int xOffset,int yOffset) { int nRows = srcImage.rows; int nCols = srcImage.cols; cv::Mat resultImage(srcImage.size(), srcImage.type()); // 遍历图像 for(int i = 0; i < nRows; ++i) { for(int j = 0; j < nCols; ++j) { // 映射变换 int x = j - xOffset; int y = i - yOffset; // 边界判断 if(x >= 0 && y >= 0 && x < nCols && y < nRows) resultImage.at<cv::Vec3b>(i,j) = srcImage.ptr<cv::Vec3b>(y)[x]; } } return resultImage; } // 平移操作,图像大小改变 cv::Mat imageTranslation2(cv::Mat& srcImage, int xOffset, int yOffset) { // 设置平移尺寸 int nRows = srcImage.rows + abs(yOffset); int nCols = srcImage.cols + abs(xOffset); cv::Mat resultImage(nRows, nCols, srcImage.type()); // 图像遍历 for(int i = 0; i < nRows; ++i) { for(int j = 0; j < nCols; ++j) { // 映射变换 int x = j - xOffset; int y = i - yOffset; // 边界判断 if(x >= 0 && y >= 0 && x < nCols && y < nRows) resultImage.at<cv::Vec3b>(i,j) = srcImage.ptr<cv::Vec3b>(y)[x]; } } return resultImage; } int main() { // 图像读取及判定是否正确读入 cv::Mat srcImage = cv::imread("a.jpg"); if(!srcImage.data) return -1; cv::imshow("srcImage", srcImage); int xOffset = 50, yOffset = 80; // 图像左平移不改变大小 cv::Mat resultImage1 = imageTranslation1(srcImage, xOffset, yOffset); cv::imshow("resultImage1", resultImage1); // 图像左平移改变大小 cv::Mat resultImage2 = imageTranslation2(srcImage, xOffset, yOffset); cv::imshow("resultImage2", resultImage2); //图像右平移不改变大小 xOffset = -50, yOffset = -80; cv::Mat resultImage3 = imageTranslation1(srcImage, xOffset, yOffset); cv::imshow("resultImage3", resultImage3); cv::waitKey(0); return 0; }
四.缩放
图像缩放会减少或增加图像数据的像素个数,造成信息的丢失
1.基于等间隔提取图像缩放
等间隔提取图像缩放是通过对源图像进行均匀采样来完成的
2.基于区域子块提取图像缩放
区域子块提取图像缩放是通过对源图像进行区域子块划分,然后提取子块中像素值作为采样像素以构成新图像来完成
提取子块像素值常用的有计算子块像素的中值和计算子块像素的均值
对源图像的区域划分也有根据缩放因子等比例提取子块和自适应因子提取子块
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; // 基于等间隔提取图像缩放 cv::Mat imageReduction1(cv::Mat& srcImage, float kx, float ky) { // 获取输出图像分辨率 int nRows = cvRound(srcImage.rows * kx); int nCols = cvRound(srcImage.cols * ky); cv::Mat resultImage(nRows, nCols, srcImage.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; resultImage.at<cv::Vec3b>(i, j) = srcImage.at<cv::Vec3b>(x, y); } } return resultImage; } cv::Vec3b areaAverage(const cv::Mat& srcImage,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 += srcImage.at<cv::Vec3b>(i, j)[0]; temp2 += srcImage.at<cv::Vec3b>(i, j)[1]; temp3 += srcImage.at<cv::Vec3b>(i, j)[2]; } } // 对每个通道求均值 Vec3b vecTemp; vecTemp[0] = temp1 / nPix; vecTemp[1] = temp2 / nPix; vecTemp[2] = temp3 / nPix; return vecTemp; } } cv::Mat imageReduction2(const Mat& srcImage, double kx, double ky) { // 获取输出图像分辨率 int nRows = cvRound(srcImage.rows * kx); int nCols = cvRound(srcImage.cols * ky); cv::Mat resultImage(nRows, nCols, srcImage.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) / ky + 0.5) - 1; // 求解区域子块的均值 resultImage.at<Vec3b>(i, j) = areaAverage(srcImage, Point_<int>(leftRowCoordinate, leftColCoordinate), Point_<int>(x, y)); // 更新下子块左上角的列坐标,行坐标不变 leftColCoordinate = y + 1; } leftColCoordinate = 0; // 更新下子块左上角的行坐标 leftRowCoordinate = x + 1; } return resultImage; } int main() { cv::Mat srcImage = cv::imread("a.jpg"); if (!srcImage.data) return -1; cv::imshow("srcImage", srcImage); cv::Mat resultImage1 = imageReduction1(srcImage, 0.5, 0.5); cv::imshow("res1", resultImage1); cv::Mat resultImage2 = imageReduction2(srcImage, 0.5, 0.5); cv::imshow("res2", resultImage2); cv::waitKey(0); return 0; }
五.旋转
1.角度旋转
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <cmath> using namespace cv; using namespace std; cv::Mat angleRotate(cv::Mat& src, int angle) { // 角度变换 float alpha = angle * CV_PI / 180; // 构造旋转矩阵 float rotateMat[3][3] = { {cos(alpha), -sin(alpha), 0}, {sin(alpha), cos(alpha), 0}, {0, 0, 1} }; int nSrcRows = src.rows; int nSrcCols = src.cols; // 计算旋转后图像矩阵的各个顶点位置 float a1 = nSrcCols * rotateMat[0][0]; float b1 = nSrcCols * rotateMat[1][0]; float a2 = nSrcCols * rotateMat[0][0] + nSrcRows * rotateMat[0][1]; float b2 = nSrcCols * rotateMat[1][0] + nSrcRows * rotateMat[1][1]; float a3 = nSrcRows * rotateMat[0][1]; float b3 = nSrcRows * rotateMat[1][1]; // 计算出极值点 float kxMin = min( min( min(0.0f, a1), a2), a3); float kxMax = max( max( max(0.0f, a1), a2), a3); float kyMin = min( min( min(0.0f, b1), b2), b3); float kyMax = max( max( max(0.0f, b1), b2), b3); // 计算输出矩阵的尺寸 int nRows = abs(kxMax - kxMin); int nCols = abs(kyMax - kyMin); cv::Mat dst(nRows, nCols, src.type(), cv::Scalar::all(0)); for (int i = 0; i < nRows; ++i) { for (int j = 0; j < nCols; ++j) { // 旋转坐标转换 int x = (j + kxMin) * rotateMat[0][0] - (i + kyMin) * rotateMat[0][1]; int y = -(j + kxMin) * rotateMat[1][0] + (i + kyMin) * rotateMat[1][1]; // 区域选择 if((x >= 0) && (x < nSrcCols) && (y >= 0) && (y < nSrcRows)) { dst.at<cv::Vec3b>(i, j) = src.at<cv::Vec3b>(y, x); } } } return dst; } int main() { cv::Mat srcImage = cv::imread("a.jpg"); if (!srcImage.data) return -1; cv::imshow("srcImage", srcImage); int angle = 30; cv::Mat resultImage = angleRotate(srcImage, angle); imshow("resultImage", resultImage); cv::waitKey(0); return 0; }
2.直接翻转
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <cmath> using namespace cv; using namespace std; int main() { cv::Mat srcImage = cv::imread("a.jpg"); if (!srcImage.data) return -1; // 逆时针旋转90度 cv::Mat resultImage1; transpose(srcImage, resultImage1); // 水平翻转 cv::Mat resultImage2; flip(resultImage1, resultImage2, 1); // 垂直翻转 cv::Mat resultImage3; flip(resultImage1, resultImage3, 0); // 垂直和水平翻转 cv::Mat resultImage4; flip(srcImage, resultImage4, -1); cv::imshow("srcImage", srcImage); cv::imshow("resultImage1", resultImage1); cv::imshow("resultImage2", resultImage2); cv::imshow("resultImage3", resultImage3); cv::imshow("resultImage4", resultImage4); cv::waitKey(0); return 0; }