仿射变换(affine transformation),又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间.它是一种二维坐标到二维坐标之间的线性变换,保持二维图形的”平直性”(straightness,即变换后直线还是直线不会打弯,圆弧还是圆弧)和”平行性”(parallelness,保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变,但向量间夹角可能会发生变化)。
一个任意的仿射变换都能表示为:乘以一个矩阵(线性变换)接着再加上一个向量(平移)。仿射变换能够表示:旋转(rotation)、平移(translation)、缩放(scale)、翻转(flip)和错切(剪切、shear)操作。事实上,仿射变换代表了两幅图像之间的关系。通常使用2*3矩阵来表示仿射变换。
有了仿射变换矩阵后,计算目的图像的公式为:
dst(x,y)= src(M11x + M12y + M13, M21x + M22y + M23)
这里对OpenCV中warpAffine函数进行了提取,目前支持uchar和float两种类型,经测试,与OpenCV3.1结果完全一致。
实现代码warpAffine.hpp:
// fbc_cv is free software and uses the same licence as OpenCV
// Email: [email protected]
#ifndef FBC_CV_WARP_AFFINE_HPP_
#define FBC_CV_WARP_AFFINE_HPP_
/* reference: include/opencv2/imgproc.hpp
modules/imgproc/src/imgwarp.cpp
*/
#include
#include "core/mat.hpp"
#include "solve.hpp"
#include "imgproc.hpp"
#include "remap.hpp"
namespace fbc {
// Calculates an affine transform from three pairs of the corresponding points
FBC_EXPORTS int getAffineTransform(const Point2f src1[], const Point2f src2[], Mat_& dst);
// Applies an affine transformation to an image
// The function cannot operate in - place
// support type: uchar/float
template
int warpAffine(const Mat_<_Tp1, chs1>& src, Mat_<_Tp1, chs1>& dst, const Mat_<_Tp2, chs2>& M_,
int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar())
{
FBC_Assert(src.data != NULL && dst.data != NULL && M_.data != NULL);
FBC_Assert(src.cols > 0 && src.rows > 0 && dst.cols > 0 && dst.rows > 0);
FBC_Assert(src.data != dst.data);
FBC_Assert(typeid(double) == typeid(_Tp2) && M_.rows == 2 && M_.cols == 3);
FBC_Assert((typeid(uchar).name() == typeid(_Tp1).name()) || (typeid(float).name() == typeid(_Tp1).name())); // uchar/float
double M[6];
Mat_ matM(2, 3, M);
M_.convertTo(matM);
int interpolation = flags & INTER_MAX;
if (interpolation == INTER_AREA)
interpolation = INTER_LINEAR;
if (!(flags & WARP_INVERSE_MAP)) {
double D = M[0] * M[4] - M[1] * M[3];
D = D != 0 ? 1. / D : 0;
double A11 = M[4] * D, A22 = M[0] * D;
M[0] = A11; M[1] *= -D;
M[3] *= -D; M[4] = A22;
double b1 = -M[0] * M[2] - M[1] * M[5];
double b2 = -M[3] * M[2] - M[4] * M[5];
M[2] = b1; M[5] = b2;
}
int x;
AutoBuffer _abdelta(dst.cols * 2);
int* adelta = &_abdelta[0], *bdelta = adelta + dst.cols;
const int AB_BITS = MAX(10, (int)INTER_BITS);
const int AB_SCALE = 1 << AB_BITS;
for (x = 0; x < dst.cols; x++) {
adelta[x] = saturate_cast(M[0] * x*AB_SCALE);
bdelta[x] = saturate_cast(M[3] * x*AB_SCALE);
}
Range range(0, dst.rows);
const int BLOCK_SZ = 64;
short XY[BLOCK_SZ*BLOCK_SZ * 2], A[BLOCK_SZ*BLOCK_SZ];;
int round_delta = interpolation == INTER_NEAREST ? AB_SCALE / 2 : AB_SCALE / INTER_TAB_SIZE / 2, y, x1, y1;
int bh0 = std::min(BLOCK_SZ / 2, dst.rows);
int bw0 = std::min(BLOCK_SZ*BLOCK_SZ / bh0, dst.cols);
bh0 = std::min(BLOCK_SZ*BLOCK_SZ / bw0, dst.rows);
for (y = range.start; y < range.end; y += bh0) {
for (x = 0; x < dst.cols; x += bw0) {
int bw = std::min(bw0, dst.cols - x);
int bh = std::min(bh0, range.end - y);
Mat_ _XY(bh, bw, XY);
Mat_<_Tp1, chs1> dpart;
dst.getROI(dpart, Rect(x, y, bw, bh));
for (y1 = 0; y1 < bh; y1++) {
short* xy = XY + y1*bw * 2;
int X0 = saturate_cast((M[1] * (y + y1) + M[2])*AB_SCALE) + round_delta;
int Y0 = saturate_cast((M[4] * (y + y1) + M[5])*AB_SCALE) + round_delta;
if (interpolation == INTER_NEAREST) {
x1 = 0;
for (; x1 < bw; x1++) {
int X = (X0 + adelta[x + x1]) >> AB_BITS;
int Y = (Y0 + bdelta[x + x1]) >> AB_BITS;
xy[x1 * 2] = saturate_cast(X);
xy[x1 * 2 + 1] = saturate_cast(Y);
}
} else {
short* alpha = A + y1*bw;
x1 = 0;
for (; x1 < bw; x1++) {
int X = (X0 + adelta[x + x1]) >> (AB_BITS - INTER_BITS);
int Y = (Y0 + bdelta[x + x1]) >> (AB_BITS - INTER_BITS);
xy[x1 * 2] = saturate_cast(X >> INTER_BITS);
xy[x1 * 2 + 1] = saturate_cast(Y >> INTER_BITS);
alpha[x1] = (short)((Y & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE +
(X & (INTER_TAB_SIZE - 1)));
}
}
}
if (interpolation == INTER_NEAREST) {
remap(src, dpart, _XY, Mat_(), interpolation, borderMode, borderValue);
} else {
Mat_ _matA(bh, bw, A);
remap(src, dpart, _XY, _matA, interpolation, borderMode, borderValue);
}
}
}
return 0;
}
} // namespace fbc
#endif // FBC_CV_WARP_AFFINE_HPP_
#include "test_warpAffine.hpp"
#include
#include
#include
#include
int test_getAffineTransform()
{
cv::Mat matSrc = cv::imread("E:/GitCode/OpenCV_Test/test_images/lena.png", 1);
if (!matSrc.data) {
std::cout << "read image fail" << std::endl;
return -1;
}
fbc::Point2f srcTri[3];
fbc::Point2f dstTri[3];
// Set your 3 points to calculate the Affine Transform
srcTri[0] = fbc::Point2f(0, 0);
srcTri[1] = fbc::Point2f(matSrc.cols - 1, 0);
srcTri[2] = fbc::Point2f(0, matSrc.rows - 1);
dstTri[0] = fbc::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri[1] = fbc::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri[2] = fbc::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
fbc::Mat_ warp_mat(2, 3);
int ret = fbc::getAffineTransform(srcTri, dstTri, warp_mat);
assert(ret == 0);
cv::Point2f srcTri_[3];
cv::Point2f dstTri_[3];
// Set your 3 points to calculate the Affine Transform
srcTri_[0] = cv::Point2f(0, 0);
srcTri_[1] = cv::Point2f(matSrc.cols - 1, 0);
srcTri_[2] = cv::Point2f(0, matSrc.rows - 1);
dstTri_[0] = cv::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri_[1] = cv::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri_[2] = cv::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
cv::Mat warp_mat_(2, 3, CV_64FC1);
warp_mat_ = cv::getAffineTransform(srcTri_, dstTri_);
assert(warp_mat.cols == warp_mat_.cols && warp_mat.rows == warp_mat_.rows);
assert(warp_mat.step == warp_mat_.step);
for (int y = 0; y < warp_mat.rows; y++) {
const fbc::uchar* p = warp_mat.ptr(y);
const uchar* p_ = warp_mat_.ptr(y);
for (int x = 0; x < warp_mat.step; x++) {
assert(p[x] == p_[x]);
}
}
return 0;
}
int test_warpAffine_uchar()
{
cv::Mat matSrc = cv::imread("E:/GitCode/OpenCV_Test/test_images/lena.png", 1);
if (!matSrc.data) {
std::cout << "read image fail" << std::endl;
return -1;
}
for (int interpolation = 0; interpolation < 5; interpolation++) {
fbc::Point2f srcTri[3];
fbc::Point2f dstTri[3];
// Set your 3 points to calculate the Affine Transform
srcTri[0] = fbc::Point2f(0, 0);
srcTri[1] = fbc::Point2f(matSrc.cols - 1, 0);
srcTri[2] = fbc::Point2f(0, matSrc.rows - 1);
dstTri[0] = fbc::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri[1] = fbc::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri[2] = fbc::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
fbc::Mat_ warp_mat(2, 3);
int ret = fbc::getAffineTransform(srcTri, dstTri, warp_mat);
assert(ret == 0);
fbc::Mat_ mat(matSrc.rows, matSrc.cols, matSrc.data);
fbc::Mat_ warp_dst;
warp_dst.zeros(mat.rows, mat.cols);
fbc::warpAffine(mat, warp_dst, warp_mat, interpolation);
cv::Point2f srcTri_[3];
cv::Point2f dstTri_[3];
// Set your 3 points to calculate the Affine Transform
srcTri_[0] = cv::Point2f(0, 0);
srcTri_[1] = cv::Point2f(matSrc.cols - 1, 0);
srcTri_[2] = cv::Point2f(0, matSrc.rows - 1);
dstTri_[0] = cv::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri_[1] = cv::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri_[2] = cv::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
cv::Mat warp_mat_(2, 3, CV_64FC1);
warp_mat_ = cv::getAffineTransform(srcTri_, dstTri_);
// Set the dst image the same type and size as src
cv::Mat warp_dst_ = cv::Mat::zeros(matSrc.rows, matSrc.cols, matSrc.type());
cv::Mat mat_;
matSrc.copyTo(mat_);
// Apply the Affine Transform just found to the src image
cv::warpAffine(mat_, warp_dst_, warp_mat_, warp_dst_.size(), interpolation);
assert(warp_mat.cols == warp_mat_.cols && warp_mat.rows == warp_mat_.rows);
assert(warp_mat.step == warp_mat_.step);
for (int y = 0; y < warp_mat.rows; y++) {
const fbc::uchar* p = warp_mat.ptr(y);
const uchar* p_ = warp_mat_.ptr(y);
for (int x = 0; x < warp_mat.step; x++) {
assert(p[x] == p_[x]);
}
}
}
return 0;
}
int test_warpAffine_float()
{
cv::Mat matSrc = cv::imread("E:/GitCode/OpenCV_Test/test_images/lena.png", 1);
if (!matSrc.data) {
std::cout << "read image fail" << std::endl;
return -1;
}
cv::cvtColor(matSrc, matSrc, CV_BGR2GRAY);
matSrc.convertTo(matSrc, CV_32FC1);
for (int interpolation = 0; interpolation < 5; interpolation++) {
fbc::Point2f srcTri[3];
fbc::Point2f dstTri[3];
// Set your 3 points to calculate the Affine Transform
srcTri[0] = fbc::Point2f(0, 0);
srcTri[1] = fbc::Point2f(matSrc.cols - 1, 0);
srcTri[2] = fbc::Point2f(0, matSrc.rows - 1);
dstTri[0] = fbc::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri[1] = fbc::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri[2] = fbc::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
fbc::Mat_ warp_mat(2, 3);
int ret = fbc::getAffineTransform(srcTri, dstTri, warp_mat);
assert(ret == 0);
fbc::Mat_ mat(matSrc.rows, matSrc.cols, matSrc.data);
fbc::Mat_ warp_dst;
warp_dst.zeros(mat.rows, mat.cols);
fbc::warpAffine(mat, warp_dst, warp_mat, interpolation);
cv::Point2f srcTri_[3];
cv::Point2f dstTri_[3];
// Set your 3 points to calculate the Affine Transform
srcTri_[0] = cv::Point2f(0, 0);
srcTri_[1] = cv::Point2f(matSrc.cols - 1, 0);
srcTri_[2] = cv::Point2f(0, matSrc.rows - 1);
dstTri_[0] = cv::Point2f(matSrc.cols*0.0, matSrc.rows*0.33);
dstTri_[1] = cv::Point2f(matSrc.cols*0.85, matSrc.rows*0.25);
dstTri_[2] = cv::Point2f(matSrc.cols*0.15, matSrc.rows*0.7);
// Get the Affine Transform
cv::Mat warp_mat_(2, 3, CV_64FC1);
warp_mat_ = cv::getAffineTransform(srcTri_, dstTri_);
// Set the dst image the same type and size as src
cv::Mat warp_dst_ = cv::Mat::zeros(matSrc.rows, matSrc.cols, matSrc.type());
cv::Mat mat_;
matSrc.copyTo(mat_);
// Apply the Affine Transform just found to the src image
cv::warpAffine(mat_, warp_dst_, warp_mat_, warp_dst_.size(), interpolation);
assert(warp_mat.cols == warp_mat_.cols && warp_mat.rows == warp_mat_.rows);
assert(warp_mat.step == warp_mat_.step);
for (int y = 0; y < warp_mat.rows; y++) {
const fbc::uchar* p = warp_mat.ptr(y);
const uchar* p_ = warp_mat_.ptr(y);
for (int x = 0; x < warp_mat.step; x++) {
assert(p[x] == p_[x]);
}
}
}
return 0;
}