OpenCV2:幼儿园篇 第五章 图像几何变换

一.简介

图像的几何变换有距离变换 坐标映射 平移  镜像 旋转  缩放  仿射变换等

 

二.重映射

把一张图像重新排列像素,比如倒置

 

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

   表示插值数值

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#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;
 
}

 

三.平移

图像的平移操作是将图像的所有像素坐标进行水平或垂直方向移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//图像平移并且图像大小不变和改变
 
#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.基于区域子块提取图像缩放

区域子块提取图像缩放是通过对源图像进行区域子块划分,然后提取子块中像素值作为采样像素以构成新图像来完成

提取子块像素值常用的有计算子块像素的中值计算子块像素的均值

对源图像的区域划分也有根据缩放因子等比例提取子块自适应因子提取子块

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#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.角度旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#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.直接翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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;
}

 

 

六.仿射变换

 

posted @   言午丶  阅读(309)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示