在不使用OpenCV函数的情况下以C ++旋转图像 [英] Rotate an image in C++ without using OpenCV functions

查看:191
本文介绍了在不使用OpenCV函数的情况下以C ++旋转图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说明:- 我试图在不使用C ++中使用OpenCV函数的情况下旋转图像.旋转中心不必是图像的中心.这可能是一个不同的点(与图像中心的偏移).到目前为止,我遵循了多种资源进行图像插值,并且知道可以在MATLAB中完美地完成工作.我试图在没有OpenCV函数的C ++中模仿相同的内容.但是我没有得到预期的旋转图像.相反,我的输出在屏幕上看起来像一条小水平线.

void RotateNearestNeighbor(cv::Mat src, double angle) {
int oldHeight = src.rows;
int oldWidth = src.cols;
int newHeight = std::sqrt(2) * oldHeight;
int newWidth = std::sqrt(2) * oldWidth;
cv::Mat output = cv::Mat(newHeight, newWidth, src.type());
double ctheta = cos(angle);
double stheta = sin(angle);

for (size_t i = 0; i < newHeight; i++) {
    for (size_t j = 0; j < newWidth; j++) {

        int oldRow = static_cast<int> ((i - newHeight / 2) * ctheta +
                                       (j - newWidth / 2) * stheta + oldHeight / 2);
        int oldCol = static_cast<int> (-(i - newHeight / 2) * stheta +
                                       (j - newWidth / 2) * ctheta + oldWidth / 2);

        if (oldRow > 0 && oldCol > 0 && oldRow <= oldHeight && oldCol <= oldWidth)
            output.at<cv::Vec3b>(i, j) = src.at<cv::Vec3b>(oldRow, oldCol);
        else
            output.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 0);
    }
}
cv::imshow("Rotated cat", output);
}

以下是我的输入(左侧)和输出(右侧)图像


UPDATE:-

在受到与此问题相关的许多答案以及下面最详尽,最有用和最慷慨的答案的启发后,我可以修复自己的OpenCV代码以获得所需的结果.

修改后的代码:

// Trivial constant
constexpr double Pi = 3.1415926535897932384626433832795;

/*!
* \brief Function to generate transformation matrix
* \param angle is the angle of rotation from user input
* \param pivot is the amount of translation in x and y axes
* \return translation matrix
*/
cv::Mat CreateTransMat(double angle, std::pair<int, int> &pivot) {
    angle = Pi * angle / 180;
    return (cv::Mat_<double>(3, 3) << cos(angle), -sin(angle), pivot.first,
            sin(angle), cos(angle), pivot.second, 0, 0, 1);
}

/*!
* \brief Function to apply coordinate transform from destination to     source
* \param inv_mat being the inverse transformation matrix for the transform needed
* \return pos being the homogeneous coordinates for transformation
*/
cv::Mat CoordTransform(const cv::Mat &inv_mat, const cv::Mat &pos) {
    assert(inv_mat.cols == pos.rows);
    cv::Mat trans_mat = inv_mat * pos;
    return (cv::Mat_<double>(1, 2) <<
            trans_mat.at<double>(0, 0) / trans_mat.at<double>(0, 2),
            trans_mat.at<double>(0, 1) / trans_mat.at<double>(0, 2));
}

/*!
* \brief Function to transform an image based on a rotation angle and translation
         matrix. When rotation and translation happen at the same time, the
         two matrices can be combined
* \param src being source image
* \param dest being destination image
* \param trans_mat being the transformation (rotation/ translation) matrix
*/
void ImageTransform(const cv::Mat &src, const cv::Mat &trans_mat, cv::Mat &dest) {
    int src_rows = src.rows;
    int src_cols = src.cols;
    int dest_rows = dest.rows;
    int dest_cols = dest.cols;
    const cv::Mat inverse_mat = trans_mat.inv();
    //#pragma omp parallel for simd
    for (int row = 0; row < dest_rows; row++) {
        //#pragma omp parallel for simd
        for (int col = 0; col < dest_cols; col++) {
            cv::Mat src_pos = CoordTransform(inverse_mat,
                                         (cv::Mat_<double>(3, 1) << col, row, 1));
            const int x_actual = static_cast<int>(src_pos.at<double>(0, 0) + 0.5);
            const int y_actual = static_cast<int>(src_pos.at<double>(0, 1) + 0.5);

            if (x_actual >= 0 && x_actual < src_cols &&
                y_actual >= 0 && y_actual < src_rows)
                dest.at<cv::Vec3b>(row, col) = src.at<cv::Vec3b>(y_actual, x_actual);
            else
                dest.at<cv::Vec3b>(row, col) = cv::Vec3b(0, 0, 0);
        }
    }    
}

/*!
* \brief User manual for command-line args input
*/
void Usage() {
    std::cout << "COMMAND INPUT : - \n\n" <<
              "          ./ImageTransform <image> <rotation-angle>" <<
              std::endl;
}
/*!
* \brief main function to read a user input location for an image and then apply the
         required transformations (rotation / translation)
*/
int main(int argc, char *argv[])
{
    auto start = std::chrono::steady_clock::now();
    if (argc == 0 || argc < 3)
        Usage();
    else {
        double degree = std::stod(argv[2]);
        double angle = degree * CV_PI / 180.;
        cv::Mat src_img = cv::imread(argv[1]);
        std::pair<int, int> null_trans = std::make_pair(0, 0);
        std::pair<int, int> translation_initial =
            std::make_pair(src_img.cols / 2 + 1, src_img.rows / 2 + 1);
        std::pair<int, int> translation_final =
            std::make_pair(0, -src_img.rows / 2 - 4);
        if (!src_img.data)
        {
            std::cout << "image null" << std::endl;
            cv::waitKey(0);
        }
        cv::imshow("Source", src_img);
        cv::Mat dest_img = cv::Mat(static_cast<int>(2 * src_img.rows),
                                   static_cast<int>(2 * src_img.cols),
                                   src_img.type());
        cv::Mat trans_mat1 = CreateTransMat(degree, translation_initial);
        ImageTransform(src_img, trans_mat1, dest_img);
        cv::imshow("Interim", dest_img);
        cv::Mat interim_img = dest_img;
        dest_img.release();
        dest_img = cv::Mat(src_img.rows, src_img.cols, src_img.type());
        cv::Mat trans_mat2 = CreateTransMat(0, translation_final);
        ImageTransform(interim_img, trans_mat2, dest_img);
        cv::imshow("Final image", dest_img);
        cv::waitKey(10);
    }
    auto end = std::chrono::steady_clock::now();
    auto diff = end - start;
    std::cout << std::chrono::duration <double, std::milli> (diff).count() <<
              " ms" << std::endl;
}

输入图片

旋转图片

解决方案

首先,我必须承认我同意 generic_opto_guy :

使用循环的方法看起来不错,因此我们需要检查数学.在我注意到的事情上:如果(oldRow> 0&& oldCol> 0&&old; RowRow< = oldHeight&& oldCold< = oldWidth)意味着您开始从1开始索引. 0.

尽管如此,我还是忍不住要回答. (也许是,这只是我的图像阶段.)

我建议不要使用sin()和cos(),而是建议使用矩阵变换.乍一看,这可能看起来是过度设计的,但是稍后您将意识到它具有更大的灵活性.使用转换矩阵,您可以表达很多转换(平移,旋转,缩放,剪切,投影),以及将多个转换组合到一个矩阵中.

(预告片: SO:如何在2D模式下绘制QImage或使其变形?)

在图像中,像素可以通过2d坐标寻址.因此,想到了2×2矩阵,但是2×2矩阵无法表达翻译.为此,引入了同质坐标–通过将尺寸扩展一倍来处理同一空间中的位置和方向的数学技巧.

简而言之,二维位置(x,y)具有齐次坐标(x,y,1).

使用变换矩阵进行变换的位置:

= M · v .

这可能会或可能不会更改第三部分的值.要再次将齐次坐标转换为2D位置,x和y必须除以3 rd 分量.

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

要将源图像转换为目标图像,可以使用以下功能:

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

注意:

转换矩阵mat描述从源图像坐标到目标图像坐标的转换.嵌套循环遍历目标图像.因此,必须使用逆矩阵(代表逆变换)来获取映射到当前目标坐标的相应源图像坐标.

…以及旋转的矩阵构造函数:

enum ArgInitRot { InitRot };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }

可用于构建angle(以度为单位)的旋转:

Mat3x3T<double> mat(InitRot, degToRad(30.0));

注意:

我想强调一下如何使用转换后的坐标:

      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);

将结果取整以产生一个离散的像素位置实际上就是所谓的最近邻".或者,可以将现在丢弃的小数部分用于相邻像素之间的线性插值.


为制作一个小样本,我首先复制了 image.himage.ccimagePPM.himagePPM.cc PPM文件格式,因为它只需要用于文件I/O的最少代码.)

接下来,我使用了 linMath.h (我的最小数学3D转换的集合)以为2D转换建立最小的数学集合– linMath.h:

#ifndef LIN_MATH_H
#define LIN_MATH_H

#include <iostream>
#include <cassert>
#include <cmath>

extern const double Pi;

template <typename VALUE>
inline VALUE degToRad(VALUE angle)
{
  return (VALUE)Pi * angle / (VALUE)180;
}

template <typename VALUE>
inline VALUE radToDeg(VALUE angle)
{
  return (VALUE)180 * angle / (VALUE)Pi;
}

enum ArgNull { Null };

template <typename VALUE>
struct Vec2T {
  typedef VALUE Value;
  Value x, y;
  // default constructor (leaving elements uninitialized)
  Vec2T() { }
  Vec2T(ArgNull): x((Value)0), y((Value)0) { }
  Vec2T(Value x, Value y): x(x), y(y) { }
};

typedef Vec2T<float> Vec2f;
typedef Vec2T<double> Vec2;

template <typename VALUE>
struct Vec3T {
  typedef VALUE Value;
  Value x, y, z;
  // default constructor (leaving elements uninitialized)
  Vec3T() { }
  Vec3T(ArgNull): x((Value)0), y((Value)0), z((Value)0) { }
  Vec3T(Value x, Value y, Value z): x(x), y(y), z(z) { }
  Vec3T(const Vec2T<Value> &xy, Value z): x(xy.x), y(xy.y), z(z) { }
  explicit operator Vec2T<Value>() const { return Vec2T<Value>(x, y); }
  const Vec2f xy() const { return Vec2f(x, y); }
  const Vec2f xz() const { return Vec2f(x, z); }
  const Vec2f yz() const { return Vec2f(y, z); }
};

typedef Vec3T<float> Vec3f;
typedef Vec3T<double> Vec3;

enum ArgInitIdent { InitIdent };
enum ArgInitTrans { InitTrans };
enum ArgInitRot { InitRot };
enum ArgInitScale { InitScale };
enum ArgInitFrame { InitFrame };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // default constructor (leaving elements uninitialized)
  Mat3x3T() { }
  // constructor to build a matrix by elements
  Mat3x3T(
    VALUE _00, VALUE _01, VALUE _02,
    VALUE _10, VALUE _11, VALUE _12,
    VALUE _20, VALUE _21, VALUE _22):
    _00(_00), _01(_01), _02(_02),
    _10(_10), _11(_11), _12(_12),
    _20(_20), _21(_21), _22(_22)
  { }
  // constructor to build an identity matrix
  Mat3x3T(ArgInitIdent):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)0),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)0),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation
  Mat3x3T(ArgInitTrans, const Vec2T<VALUE> &t):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)t.x),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)t.y),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation/rotation
  Mat3x3T(ArgInitFrame, const Vec2T<VALUE> &t, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)t.x),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)t.y),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for scaling
  Mat3x3T(ArgInitScale, VALUE sx, VALUE sy):
    _00((VALUE)sx), _01( (VALUE)0), _02((VALUE)0),
    _10( (VALUE)0), _11((VALUE)sy), _12((VALUE)0),
    _20( (VALUE)0), _21( (VALUE)0), _22((VALUE)1)
  { }
  // operator to allow access with [][]
  VALUE* operator [] (int i)
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }
  // operator to allow access with [][]
  const VALUE* operator [] (int i) const
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }

  // multiply matrix with matrix -> matrix
  Mat3x3T operator * (const Mat3x3T &mat) const
  {
    return Mat3x3T(
      _00 * mat._00 + _01 * mat._10 + _02 * mat._20,
      _00 * mat._01 + _01 * mat._11 + _02 * mat._21,
      _00 * mat._02 + _01 * mat._12 + _02 * mat._22,
      _10 * mat._00 + _11 * mat._10 + _12 * mat._20,
      _10 * mat._01 + _11 * mat._11 + _12 * mat._21,
      _10 * mat._02 + _11 * mat._12 + _12 * mat._22,
      _20 * mat._00 + _21 * mat._10 + _22 * mat._20,
      _20 * mat._01 + _21 * mat._11 + _22 * mat._21,
      _20 * mat._02 + _21 * mat._12 + _22 * mat._22);
  }
  // multiply matrix with vector -> vector
  Vec3T<VALUE> operator * (const Vec3T<VALUE> &vec) const
  {
    return Vec3T<VALUE>(
      _00 * vec.x + _01 * vec.y + _02 * vec.z,
      _10 * vec.x + _11 * vec.y + _12 * vec.z,
      _20 * vec.x + _21 * vec.y + _22 * vec.z);
  }
};

typedef Mat3x3T<float> Mat3x3f;
typedef Mat3x3T<double> Mat3x3;

template <typename VALUE>
std::ostream& operator<<(std::ostream &out, const Mat3x3T<VALUE> &m)
{
  return out
    << m._00 << '\t' << m._01 << '\t' << m._02 << '\n'
    << m._10 << '\t' << m._11 << '\t' << m._12 << '\n'
    << m._20 << '\t' << m._21 << '\t' << m._22 << '\n';
}

/* computes determinant of a matrix.
 *
 * det = |M|
 *
 * mat ... the matrix
 */
template <typename VALUE>
VALUE determinant(const Mat3x3T<VALUE> &mat)
{
  return mat._00 * mat._11 * mat._22
    + mat._01 * mat._12 * mat._20
    + mat._02 * mat._10 * mat._21
    - mat._20 * mat._11 * mat._02
    - mat._21 * mat._12 * mat._00
    - mat._22 * mat._10 * mat._01;
}

/* returns the inverse of a regular matrix.
 *
 * mat matrix to invert
 * eps epsilon for regularity of matrix
 */
template <typename VALUE>
Mat3x3T<VALUE> invert(
  const Mat3x3T<VALUE> &mat, VALUE eps = (VALUE)1E-10)
{
  assert(eps >= (VALUE)0);
  // compute determinant and check that it its unequal to 0
  // (Otherwise, matrix is singular!)
  const VALUE det = determinant(mat);
  if (std::abs(det) < eps) throw std::domain_error("Singular matrix!");
  // reciproke of determinant
  const VALUE detInvPos = (VALUE)1 / det, detInvNeg = -detInvPos;
  // compute each element by determinant of sub-matrix which is build
  // striking out row and column of pivot element itself
  // BTW, the determinant is multiplied with -1 when sum of row and column
  // index is odd (chess board rule)
  // (This is usually called cofactor of related element.)
  // transpose matrix and multiply with 1/determinant of original matrix
  return Mat3x3T<VALUE>(
    detInvPos * (mat._11 * mat._22 - mat._12 * mat._21),
    detInvNeg * (mat._01 * mat._22 - mat._02 * mat._21),
    detInvPos * (mat._01 * mat._12 - mat._02 * mat._11),
    detInvNeg * (mat._10 * mat._22 - mat._12 * mat._20),
    detInvPos * (mat._00 * mat._22 - mat._02 * mat._20),
    detInvNeg * (mat._00 * mat._12 - mat._02 * mat._10),
    detInvPos * (mat._10 * mat._21 - mat._11 * mat._20),
    detInvNeg * (mat._00 * mat._21 - mat._01 * mat._20),
    detInvPos * (mat._00 * mat._11 - mat._01 * mat._10));
}

#endif // LIN_MATH_H

以及linMath.ccPi的定义:

#include "linmath.h"

const double Pi = 3.1415926535897932384626433832795;

在拥有所有可用工具的情况下,我制作了示例应用程序xformRGBImg.cc:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include "linMath.h"
#include "image.h"
#include "imagePPM.h"

typedef unsigned int uint;

struct Error {
  const std::string text;
  Error(const char *text): text(text) { }
};

const char* readArg(int &i, int argc, char **argv)
{
  ++i;
  if (i >= argc) throw Error("Missing argument!");
  return argv[i];
}

uint readArgUInt(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const unsigned long value = strtoul(arg, &end, 0);
  if (arg == end || *end) throw Error("Unsigned integer value expected!");
  if ((uint)value != value) throw Error("Unsigned integer overflow!");
  return (uint)value;
}

double readArgDouble(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const double value = strtod(arg, &end);
  if (arg == end || *end) throw Error("Floating point value expected!");
  return value;
}

std::pair<uint, uint> resize(int &i, int argc, char **argv)
{
  const uint w = readArgUInt(i, argc, argv);
  const uint h = readArgUInt(i, argc, argv);
  return std::make_pair(w, h);
}

Mat3x3 translate(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitTrans, Vec2(x, y));
}

Mat3x3 rotate(int &i, int argc, char **argv)
{
  const double angle = readArgDouble(i, argc, argv);
  return Mat3x3(InitRot, degToRad(angle));
}

Mat3x3 scale(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitScale, x, y);
}

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

const char *const usage =
  "Usage:\n"
  "  xformRGBImg IN_FILE OUT_FILE [[CMD]...]\n"
  "\n"
  "Commands:\n"
  "  resize W H\n"
  "  translate X Y\n"
  "  rotate ANGLE\n"
  "  scale SX SY\n";

int main(int argc, char **argv)
{
  // read command line arguments
  if (argc <= 2) {
    std::cerr << "Missing arguments!\n";
    std::cout << usage;
    return 1;
  }
  const std::string inFile = argv[1];
  const std::string outFile = argv[2];
  std::pair<uint, uint> sizeOut(0, 0);
  Mat3x3 mat(InitIdent);
  for (int i = 3; i < argc; ++i) try {
    const std::string cmd = argv[i];
    if (cmd == "resize") sizeOut = resize(i, argc, argv);
    else if (cmd == "translate") mat = translate(i, argc, argv) * mat;
    else if (cmd == "rotate") mat = rotate(i, argc, argv) * mat;
    else if (cmd == "scale") mat = scale(i, argc, argv) * mat;
    else {
      std::cerr << "Wrong command!\n";
      std::cout << usage;
      return 1;
    }
  } catch (const Error &error) {
    std::cerr << "Wrong argument at $" << i << "\n"
      << error.text << '\n';
    std::cout << usage;
    return 1;
  }
  // read image
  Image imgSrc;
  { std::ifstream fIn(inFile.c_str(), std::ios::binary);
    if (!readPPM(fIn, imgSrc)) {
      std::cerr << "Reading '" << inFile << "' failed!\n";
      return 1;
    }
  }
  // set output image size
  if (sizeOut.first * sizeOut.second == 0) {
    sizeOut = std::make_pair(imgSrc.w(), imgSrc.h());
  }
  // transform image
  Image imgDst;
  imgDst.resize(sizeOut.first, sizeOut.second, 3 * sizeOut.second);
  transform(imgSrc, mat, imgDst);
  // write image
  { std::ofstream fOut(outFile.c_str(), std::ios::binary);
    if (!writePPM(fOut, imgDst) || (fOut.close(), !fOut.good())) {
      std::cerr << "Writing '" << outFile << "' failed!\n";
      return 1;
    }
  }
  // done
  return 0;
}

注意:

按顺序处理命令行参数.从单位矩阵开始,将每个转换命令从左向已组合的转换矩阵相乘.这是因为变换的串联导致矩阵的逆序乘法. (矩阵乘法是右关联的.)

例如转换的相应矩阵:

x' = 翻译( x )
x" = 旋转( x')
x' = 比例( x" )

这是

x' = 比例(旋转(翻译( x )) ))

M transform = M scale · M 旋转· M 翻译

x' = M 比例· M rotate · M translate · x = M transform · x

cygwin 中进行编译和测试:

 $ g++ -std=c++11 -o xformRGBImg image.cc imagePPM.cc linMath.cc xformRGBImg.cc

$ ./xformRGBImg                                                               
Missing arguments!
Usage:
  xformRGBImg IN_FILE OUT_FILE [[CMD]...]

Commands:
  resize W H
  translate X Y
  rotate ANGLE
  scale SX SY

$
 

最后,是一个示例图像cat.jpg(在 PPM "https://www.gimp.org/" rel ="nofollow noreferrer"> GIMP ):

大小为300× 300.

注意:

所有嵌入的图像都从PPM转换为JPEG(再次在 GIMP 中). (图片上传不支持PPM,我也无法想象任何浏览器都能正确显示它.)

从最低开始:

 $ ./xformRGBImg cat.ppm cat.copy.ppm

$
 

看起来像原始的–身份转换应该期待什么.

现在,旋转30°:

 $ ./xformRGBImg cat.ppm cat.rot30.ppm rotate 30

$
 

要绕某个中心旋转,有一个响应.之前和之后都需要翻译:

 $ ./xformRGBImg cat.ppm cat.rot30c150,150.ppm \
  translate -150 -150 rotate 30 translate 150 150

$
 

可以使用w·调整输出图像的大小. √ 2× h· √ 2以适合任何中心旋转.

因此,输出图像的大小调整为425× 425,最后一次翻译分别调整为translate 212.5 212.5:

 $ ./xformRGBImg cat.ppm cat.rot30c150,150.425x425.ppm \
  resize 425 425 translate -150 -150 rotate 30 translate 212.5 212.5

$
 

尚未检查缩放比例:

 $ ./xformRGBImg cat.ppm cat.rot30c150,150s0.7,0.7.ppm \
  translate -150 -150 rotate 30 scale 0.7 0.7 translate 150 150

$
 


最后,说句公道话,我想提一提老大哥".我的小玩具工具: ImageMagick .

Description :- I am trying to rotate an image without using OpenCV functions in C++. The rotation center need not be the center of the image. It could be a different point (offset from the image center). So far I followed a variety of sources to do image interpolation and I am aware of a source which does the job perfectly in MATLAB. I tried to mimic the same in C++ without OpenCV functions. But I am not getting the expected rotated image. Instead my output appears like a small horizontal line on the screen.

void RotateNearestNeighbor(cv::Mat src, double angle) {
int oldHeight = src.rows;
int oldWidth = src.cols;
int newHeight = std::sqrt(2) * oldHeight;
int newWidth = std::sqrt(2) * oldWidth;
cv::Mat output = cv::Mat(newHeight, newWidth, src.type());
double ctheta = cos(angle);
double stheta = sin(angle);

for (size_t i = 0; i < newHeight; i++) {
    for (size_t j = 0; j < newWidth; j++) {

        int oldRow = static_cast<int> ((i - newHeight / 2) * ctheta +
                                       (j - newWidth / 2) * stheta + oldHeight / 2);
        int oldCol = static_cast<int> (-(i - newHeight / 2) * stheta +
                                       (j - newWidth / 2) * ctheta + oldWidth / 2);

        if (oldRow > 0 && oldCol > 0 && oldRow <= oldHeight && oldCol <= oldWidth)
            output.at<cv::Vec3b>(i, j) = src.at<cv::Vec3b>(oldRow, oldCol);
        else
            output.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 0);
    }
}
cv::imshow("Rotated cat", output);
}

The following are my input (left side) and output (right side) images


UPDATE : -

After being inspired by many answers related to this question and also the most elaborate, helpful and generous answer below, I could fix my OpenCV code to get the desired result.

Modified Code :

// Trivial constant
constexpr double Pi = 3.1415926535897932384626433832795;

/*!
* \brief Function to generate transformation matrix
* \param angle is the angle of rotation from user input
* \param pivot is the amount of translation in x and y axes
* \return translation matrix
*/
cv::Mat CreateTransMat(double angle, std::pair<int, int> &pivot) {
    angle = Pi * angle / 180;
    return (cv::Mat_<double>(3, 3) << cos(angle), -sin(angle), pivot.first,
            sin(angle), cos(angle), pivot.second, 0, 0, 1);
}

/*!
* \brief Function to apply coordinate transform from destination to     source
* \param inv_mat being the inverse transformation matrix for the transform needed
* \return pos being the homogeneous coordinates for transformation
*/
cv::Mat CoordTransform(const cv::Mat &inv_mat, const cv::Mat &pos) {
    assert(inv_mat.cols == pos.rows);
    cv::Mat trans_mat = inv_mat * pos;
    return (cv::Mat_<double>(1, 2) <<
            trans_mat.at<double>(0, 0) / trans_mat.at<double>(0, 2),
            trans_mat.at<double>(0, 1) / trans_mat.at<double>(0, 2));
}

/*!
* \brief Function to transform an image based on a rotation angle and translation
         matrix. When rotation and translation happen at the same time, the
         two matrices can be combined
* \param src being source image
* \param dest being destination image
* \param trans_mat being the transformation (rotation/ translation) matrix
*/
void ImageTransform(const cv::Mat &src, const cv::Mat &trans_mat, cv::Mat &dest) {
    int src_rows = src.rows;
    int src_cols = src.cols;
    int dest_rows = dest.rows;
    int dest_cols = dest.cols;
    const cv::Mat inverse_mat = trans_mat.inv();
    //#pragma omp parallel for simd
    for (int row = 0; row < dest_rows; row++) {
        //#pragma omp parallel for simd
        for (int col = 0; col < dest_cols; col++) {
            cv::Mat src_pos = CoordTransform(inverse_mat,
                                         (cv::Mat_<double>(3, 1) << col, row, 1));
            const int x_actual = static_cast<int>(src_pos.at<double>(0, 0) + 0.5);
            const int y_actual = static_cast<int>(src_pos.at<double>(0, 1) + 0.5);

            if (x_actual >= 0 && x_actual < src_cols &&
                y_actual >= 0 && y_actual < src_rows)
                dest.at<cv::Vec3b>(row, col) = src.at<cv::Vec3b>(y_actual, x_actual);
            else
                dest.at<cv::Vec3b>(row, col) = cv::Vec3b(0, 0, 0);
        }
    }    
}

/*!
* \brief User manual for command-line args input
*/
void Usage() {
    std::cout << "COMMAND INPUT : - \n\n" <<
              "          ./ImageTransform <image> <rotation-angle>" <<
              std::endl;
}
/*!
* \brief main function to read a user input location for an image and then apply the
         required transformations (rotation / translation)
*/
int main(int argc, char *argv[])
{
    auto start = std::chrono::steady_clock::now();
    if (argc == 0 || argc < 3)
        Usage();
    else {
        double degree = std::stod(argv[2]);
        double angle = degree * CV_PI / 180.;
        cv::Mat src_img = cv::imread(argv[1]);
        std::pair<int, int> null_trans = std::make_pair(0, 0);
        std::pair<int, int> translation_initial =
            std::make_pair(src_img.cols / 2 + 1, src_img.rows / 2 + 1);
        std::pair<int, int> translation_final =
            std::make_pair(0, -src_img.rows / 2 - 4);
        if (!src_img.data)
        {
            std::cout << "image null" << std::endl;
            cv::waitKey(0);
        }
        cv::imshow("Source", src_img);
        cv::Mat dest_img = cv::Mat(static_cast<int>(2 * src_img.rows),
                                   static_cast<int>(2 * src_img.cols),
                                   src_img.type());
        cv::Mat trans_mat1 = CreateTransMat(degree, translation_initial);
        ImageTransform(src_img, trans_mat1, dest_img);
        cv::imshow("Interim", dest_img);
        cv::Mat interim_img = dest_img;
        dest_img.release();
        dest_img = cv::Mat(src_img.rows, src_img.cols, src_img.type());
        cv::Mat trans_mat2 = CreateTransMat(0, translation_final);
        ImageTransform(interim_img, trans_mat2, dest_img);
        cv::imshow("Final image", dest_img);
        cv::waitKey(10);
    }
    auto end = std::chrono::steady_clock::now();
    auto diff = end - start;
    std::cout << std::chrono::duration <double, std::milli> (diff).count() <<
              " ms" << std::endl;
}

Input image

Rotated image

解决方案

First, I have to admit I agree with generic_opto_guy:

The approach with the loop looks good, so we would need to check the math. On thing I noticed: if (oldRow > 0 && oldCol > 0 && oldRow <= oldHeight && oldCol <= oldWidth) implies you start indexing with 1. I belife that opencv starts indexing with 0.

For all that, I couldn't resist to answer. (May be, it's just an image phase of mine.)

Instead of fiddling with sin() and cos(), I would recommend to use matrix transformation. At the first glance, this might appear over-engineered but later you will recognize that it bears much more flexibility. With a transformation matrix, you can express a lot of transformations (translation, rotation, scaling, shearing, projection) as well as combining multiple transformations into one matrix.

(A teaser for what is possible: SO: How to paint / deform a QImage in 2D?)

In an image, the pixels may be addressed by 2d coordinates. Hence a 2×2 matrix comes into mind but a 2×2 matrix cannot express translations. For this, homogeneous coordinates has been introduced – a math trick to handle positions and directions in the same space by extending the dimension by one.

To make it short, a 2d position (x, y) has the homogeneous coordinates (x, y, 1).

A position transformed with a transformation matrix:

= M · v.

This may or may not change the value of third component. To convert the homogeneous coordinate to 2D position again, x and y has to be divided by 3rd component.

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

To transform a source image into a destination image, the following function can be used:

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

Note:

The transformation matrix mat describes the transformation of source image coordinates to destination image coordinates. The nested loops iterate over destination image. Hence, the inverse matrix (representing the reverse transformation) has to be used to get the corresponding source image coordinates which map to the current destination coordinates.

… and the matrix constructor for the rotation:

enum ArgInitRot { InitRot };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }

can be used to construct a rotation with angle (in degree):

Mat3x3T<double> mat(InitRot, degToRad(30.0));

Note:

I would like to emphasize how the transformed coordinates are used:

      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);

Rounding the results to yield one discrete pixel position is actually what is called Nearest Neighbour. Alternatively, the now discarded fractional parts could be used for a linear interpolation between neighbour pixels.


To make a small sample, I first copied image.h, image.cc, imagePPM.h, and imagePPM.cc from another answer I wrote recently. (The PPM file format has been used as it needs minimal code for file I/O.)

Next, I used linMath.h (my minimal math collection for 3D transformations) to make a minimal math collection for 2D transformations – linMath.h:

#ifndef LIN_MATH_H
#define LIN_MATH_H

#include <iostream>
#include <cassert>
#include <cmath>

extern const double Pi;

template <typename VALUE>
inline VALUE degToRad(VALUE angle)
{
  return (VALUE)Pi * angle / (VALUE)180;
}

template <typename VALUE>
inline VALUE radToDeg(VALUE angle)
{
  return (VALUE)180 * angle / (VALUE)Pi;
}

enum ArgNull { Null };

template <typename VALUE>
struct Vec2T {
  typedef VALUE Value;
  Value x, y;
  // default constructor (leaving elements uninitialized)
  Vec2T() { }
  Vec2T(ArgNull): x((Value)0), y((Value)0) { }
  Vec2T(Value x, Value y): x(x), y(y) { }
};

typedef Vec2T<float> Vec2f;
typedef Vec2T<double> Vec2;

template <typename VALUE>
struct Vec3T {
  typedef VALUE Value;
  Value x, y, z;
  // default constructor (leaving elements uninitialized)
  Vec3T() { }
  Vec3T(ArgNull): x((Value)0), y((Value)0), z((Value)0) { }
  Vec3T(Value x, Value y, Value z): x(x), y(y), z(z) { }
  Vec3T(const Vec2T<Value> &xy, Value z): x(xy.x), y(xy.y), z(z) { }
  explicit operator Vec2T<Value>() const { return Vec2T<Value>(x, y); }
  const Vec2f xy() const { return Vec2f(x, y); }
  const Vec2f xz() const { return Vec2f(x, z); }
  const Vec2f yz() const { return Vec2f(y, z); }
};

typedef Vec3T<float> Vec3f;
typedef Vec3T<double> Vec3;

enum ArgInitIdent { InitIdent };
enum ArgInitTrans { InitTrans };
enum ArgInitRot { InitRot };
enum ArgInitScale { InitScale };
enum ArgInitFrame { InitFrame };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // default constructor (leaving elements uninitialized)
  Mat3x3T() { }
  // constructor to build a matrix by elements
  Mat3x3T(
    VALUE _00, VALUE _01, VALUE _02,
    VALUE _10, VALUE _11, VALUE _12,
    VALUE _20, VALUE _21, VALUE _22):
    _00(_00), _01(_01), _02(_02),
    _10(_10), _11(_11), _12(_12),
    _20(_20), _21(_21), _22(_22)
  { }
  // constructor to build an identity matrix
  Mat3x3T(ArgInitIdent):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)0),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)0),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation
  Mat3x3T(ArgInitTrans, const Vec2T<VALUE> &t):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)t.x),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)t.y),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation/rotation
  Mat3x3T(ArgInitFrame, const Vec2T<VALUE> &t, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)t.x),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)t.y),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for scaling
  Mat3x3T(ArgInitScale, VALUE sx, VALUE sy):
    _00((VALUE)sx), _01( (VALUE)0), _02((VALUE)0),
    _10( (VALUE)0), _11((VALUE)sy), _12((VALUE)0),
    _20( (VALUE)0), _21( (VALUE)0), _22((VALUE)1)
  { }
  // operator to allow access with [][]
  VALUE* operator [] (int i)
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }
  // operator to allow access with [][]
  const VALUE* operator [] (int i) const
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }

  // multiply matrix with matrix -> matrix
  Mat3x3T operator * (const Mat3x3T &mat) const
  {
    return Mat3x3T(
      _00 * mat._00 + _01 * mat._10 + _02 * mat._20,
      _00 * mat._01 + _01 * mat._11 + _02 * mat._21,
      _00 * mat._02 + _01 * mat._12 + _02 * mat._22,
      _10 * mat._00 + _11 * mat._10 + _12 * mat._20,
      _10 * mat._01 + _11 * mat._11 + _12 * mat._21,
      _10 * mat._02 + _11 * mat._12 + _12 * mat._22,
      _20 * mat._00 + _21 * mat._10 + _22 * mat._20,
      _20 * mat._01 + _21 * mat._11 + _22 * mat._21,
      _20 * mat._02 + _21 * mat._12 + _22 * mat._22);
  }
  // multiply matrix with vector -> vector
  Vec3T<VALUE> operator * (const Vec3T<VALUE> &vec) const
  {
    return Vec3T<VALUE>(
      _00 * vec.x + _01 * vec.y + _02 * vec.z,
      _10 * vec.x + _11 * vec.y + _12 * vec.z,
      _20 * vec.x + _21 * vec.y + _22 * vec.z);
  }
};

typedef Mat3x3T<float> Mat3x3f;
typedef Mat3x3T<double> Mat3x3;

template <typename VALUE>
std::ostream& operator<<(std::ostream &out, const Mat3x3T<VALUE> &m)
{
  return out
    << m._00 << '\t' << m._01 << '\t' << m._02 << '\n'
    << m._10 << '\t' << m._11 << '\t' << m._12 << '\n'
    << m._20 << '\t' << m._21 << '\t' << m._22 << '\n';
}

/* computes determinant of a matrix.
 *
 * det = |M|
 *
 * mat ... the matrix
 */
template <typename VALUE>
VALUE determinant(const Mat3x3T<VALUE> &mat)
{
  return mat._00 * mat._11 * mat._22
    + mat._01 * mat._12 * mat._20
    + mat._02 * mat._10 * mat._21
    - mat._20 * mat._11 * mat._02
    - mat._21 * mat._12 * mat._00
    - mat._22 * mat._10 * mat._01;
}

/* returns the inverse of a regular matrix.
 *
 * mat matrix to invert
 * eps epsilon for regularity of matrix
 */
template <typename VALUE>
Mat3x3T<VALUE> invert(
  const Mat3x3T<VALUE> &mat, VALUE eps = (VALUE)1E-10)
{
  assert(eps >= (VALUE)0);
  // compute determinant and check that it its unequal to 0
  // (Otherwise, matrix is singular!)
  const VALUE det = determinant(mat);
  if (std::abs(det) < eps) throw std::domain_error("Singular matrix!");
  // reciproke of determinant
  const VALUE detInvPos = (VALUE)1 / det, detInvNeg = -detInvPos;
  // compute each element by determinant of sub-matrix which is build
  // striking out row and column of pivot element itself
  // BTW, the determinant is multiplied with -1 when sum of row and column
  // index is odd (chess board rule)
  // (This is usually called cofactor of related element.)
  // transpose matrix and multiply with 1/determinant of original matrix
  return Mat3x3T<VALUE>(
    detInvPos * (mat._11 * mat._22 - mat._12 * mat._21),
    detInvNeg * (mat._01 * mat._22 - mat._02 * mat._21),
    detInvPos * (mat._01 * mat._12 - mat._02 * mat._11),
    detInvNeg * (mat._10 * mat._22 - mat._12 * mat._20),
    detInvPos * (mat._00 * mat._22 - mat._02 * mat._20),
    detInvNeg * (mat._00 * mat._12 - mat._02 * mat._10),
    detInvPos * (mat._10 * mat._21 - mat._11 * mat._20),
    detInvNeg * (mat._00 * mat._21 - mat._01 * mat._20),
    detInvPos * (mat._00 * mat._11 - mat._01 * mat._10));
}

#endif // LIN_MATH_H

and the definition of Pi in linMath.cc:

#include "linmath.h"

const double Pi = 3.1415926535897932384626433832795;

Having all tools available, I made the sample application xformRGBImg.cc:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include "linMath.h"
#include "image.h"
#include "imagePPM.h"

typedef unsigned int uint;

struct Error {
  const std::string text;
  Error(const char *text): text(text) { }
};

const char* readArg(int &i, int argc, char **argv)
{
  ++i;
  if (i >= argc) throw Error("Missing argument!");
  return argv[i];
}

uint readArgUInt(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const unsigned long value = strtoul(arg, &end, 0);
  if (arg == end || *end) throw Error("Unsigned integer value expected!");
  if ((uint)value != value) throw Error("Unsigned integer overflow!");
  return (uint)value;
}

double readArgDouble(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const double value = strtod(arg, &end);
  if (arg == end || *end) throw Error("Floating point value expected!");
  return value;
}

std::pair<uint, uint> resize(int &i, int argc, char **argv)
{
  const uint w = readArgUInt(i, argc, argv);
  const uint h = readArgUInt(i, argc, argv);
  return std::make_pair(w, h);
}

Mat3x3 translate(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitTrans, Vec2(x, y));
}

Mat3x3 rotate(int &i, int argc, char **argv)
{
  const double angle = readArgDouble(i, argc, argv);
  return Mat3x3(InitRot, degToRad(angle));
}

Mat3x3 scale(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitScale, x, y);
}

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = (int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

const char *const usage =
  "Usage:\n"
  "  xformRGBImg IN_FILE OUT_FILE [[CMD]...]\n"
  "\n"
  "Commands:\n"
  "  resize W H\n"
  "  translate X Y\n"
  "  rotate ANGLE\n"
  "  scale SX SY\n";

int main(int argc, char **argv)
{
  // read command line arguments
  if (argc <= 2) {
    std::cerr << "Missing arguments!\n";
    std::cout << usage;
    return 1;
  }
  const std::string inFile = argv[1];
  const std::string outFile = argv[2];
  std::pair<uint, uint> sizeOut(0, 0);
  Mat3x3 mat(InitIdent);
  for (int i = 3; i < argc; ++i) try {
    const std::string cmd = argv[i];
    if (cmd == "resize") sizeOut = resize(i, argc, argv);
    else if (cmd == "translate") mat = translate(i, argc, argv) * mat;
    else if (cmd == "rotate") mat = rotate(i, argc, argv) * mat;
    else if (cmd == "scale") mat = scale(i, argc, argv) * mat;
    else {
      std::cerr << "Wrong command!\n";
      std::cout << usage;
      return 1;
    }
  } catch (const Error &error) {
    std::cerr << "Wrong argument at $" << i << "\n"
      << error.text << '\n';
    std::cout << usage;
    return 1;
  }
  // read image
  Image imgSrc;
  { std::ifstream fIn(inFile.c_str(), std::ios::binary);
    if (!readPPM(fIn, imgSrc)) {
      std::cerr << "Reading '" << inFile << "' failed!\n";
      return 1;
    }
  }
  // set output image size
  if (sizeOut.first * sizeOut.second == 0) {
    sizeOut = std::make_pair(imgSrc.w(), imgSrc.h());
  }
  // transform image
  Image imgDst;
  imgDst.resize(sizeOut.first, sizeOut.second, 3 * sizeOut.second);
  transform(imgSrc, mat, imgDst);
  // write image
  { std::ofstream fOut(outFile.c_str(), std::ios::binary);
    if (!writePPM(fOut, imgDst) || (fOut.close(), !fOut.good())) {
      std::cerr << "Writing '" << outFile << "' failed!\n";
      return 1;
    }
  }
  // done
  return 0;
}

Note:

The command line arguments are processed in order. Each transformation command is multiplied from left to the already combined transformation matrix, starting with an identity matrix. This is because a concatenation of transformations results in the reverse ordered multiplication of matrices. (The matrix multiplication is right associative.)

E.g. the corresponding matrix for a transform:

x' = translate(x)
x" = rotate(x')
x"' = scale(x")

which is

x"' = scale(rotate(translate(x)))

is

Mtransform = Mscale · Mrotate · Mtranslate

and

x"' = Mscale · Mrotate · Mtranslate · x = Mtransform · x

Compiled and tested in cygwin:

$ g++ -std=c++11 -o xformRGBImg image.cc imagePPM.cc linMath.cc xformRGBImg.cc

$ ./xformRGBImg                                                               
Missing arguments!
Usage:
  xformRGBImg IN_FILE OUT_FILE [[CMD]...]

Commands:
  resize W H
  translate X Y
  rotate ANGLE
  scale SX SY

$

Finally, a sample image cat.jpg (converted to PPM in GIMP):

with size 300 × 300.

Note:

All embedded images are converted from PPM to JPEG (in GIMP again). (PPM is not supported in image upload, nor can I imagine that any browser can display it properly.)

To start with a minimum:

$ ./xformRGBImg cat.ppm cat.copy.ppm

$

It looks like the original – what should be expected by an identity transform.

Now, a rotation with 30°:

$ ./xformRGBImg cat.ppm cat.rot30.ppm rotate 30

$

To rotate about a certain center, there is a resp. translation before and afterwards needed:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.ppm \
  translate -150 -150 rotate 30 translate 150 150

$

The output image can be resized with w · √2 × h · √2 to fit any center rotation in.

So, the output image is resized to 425 × 425 where the last translation is adjusted respectively to translate 212.5 212.5:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.425x425.ppm \
  resize 425 425 translate -150 -150 rotate 30 translate 212.5 212.5

$

The scaling has not yet been checked:

$ ./xformRGBImg cat.ppm cat.rot30c150,150s0.7,0.7.ppm \
  translate -150 -150 rotate 30 scale 0.7 0.7 translate 150 150

$


Finally, to be fair, I would like to mention the “big brother” of my little toy tool: ImageMagick.

这篇关于在不使用OpenCV函数的情况下以C ++旋转图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆