Skip to content

Commit 97208dd

Browse files
committed
Add affine transformation functions with accuracy and performance tests
1 parent 17398ec commit 97208dd

File tree

4 files changed

+386
-0
lines changed

4 files changed

+386
-0
lines changed

modules/fastcv/include/opencv2/fastcv/warp.hpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,57 @@ CV_EXPORTS_W void warpPerspective(InputArray _src, OutputArray _dst, InputArray
4444
CV_EXPORTS_W void warpPerspective2Plane(InputArray _src1, InputArray _src2, OutputArray _dst1, OutputArray _dst2,
4545
InputArray _M0, Size dsize);
4646

47+
/**
48+
* @brief Applies an affine transformation to a grayscale image using a 2x3 matrix.
49+
* @param _src Input grayscale image of type 'CV_8UC1'.
50+
* @param _dst Output image after transformation.
51+
* @param _M 2x3 affine transformation matrix.
52+
* @param dsize Size of the output image.
53+
* @param interpolation Interpolation method, Supported methods include:
54+
* 'INTER_NEAREST': Nearest neighbor interpolation.
55+
* 'INTER_LINEAR': Bilinear interpolation.
56+
* 'INTER_AREA': Area interpolation.
57+
* @param borderValue Constant pixel value for border pixels.
58+
*
59+
* @note The affine matrix follows the inverse mapping convention, applied to destination coordinates
60+
* to produce corresponding source coordinates.
61+
* @note The function uses 'FASTCV_BORDER_CONSTANT' for border handling, with the specified 'borderValue'.
62+
*/
63+
CV_EXPORTS_W void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, int interpolation, int borderValue);
64+
65+
/**
66+
* @brief Applies an affine transformation on a 3-color channel image using a 2x3 matrix with bicubic interpolation.
67+
* Pixels that would be sampled from outside the source image are not modified in the target image.
68+
* The left-most and right-most pixel coordinates of each scanline are written to dstBorder.
69+
* @param _src Input image (3-channel RGB). Size of buffer is src.step[0] * src.rows bytes.
70+
* WARNING: data should be 128-bit aligned.
71+
* @param _dst Warped output image (3-channel RGB). Size of buffer is dst.step[0] * dst.rows bytes.
72+
* WARNING: data should be 128-bit aligned.
73+
* @param _M 2x3 perspective transformation matrix. The matrix stored in affineMatrix is using row major ordering:
74+
* | a11, a12, a13 |
75+
* | a21, a22, a23 |
76+
* The affine matrix follows the inverse mapping convention.
77+
* @param dsize The output image size.
78+
* @param _dstBorder Output array receiving the x-coordinates of left-most and right-most pixels for each scanline.
79+
* The format of the array is: l0,r0,l1,r1,l2,r2,... where l0 is the left-most pixel coordinate in scanline 0
80+
* and r0 is the right-most pixel coordinate in scanline 0.
81+
* The buffer must therefore be 2 * dsize.height integers in size.
82+
* NOTE: data should be 128-bit aligned.
83+
*/
84+
CV_EXPORTS_W void warpAffine3Channel(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, OutputArray _dstBorder);
85+
86+
/**
87+
* @brief Warps a patch centered at a specified position in the input image using an affine transformation.
88+
* @param src Input grayscale image of type 'CV_8UC1'.
89+
* @param position Position in the image where the patch is centered.
90+
* @param affine 2x2 affine transformation matrix of type 'CV_32FC1'.
91+
* @param patch Output image patch after transformation.
92+
* @param patchSize Size of the output patch.
93+
*
94+
* @return Returns 0 if the transformation is valid.
95+
*/
96+
CV_EXPORTS_W void warpAffineROI(InputArray _src, const cv::Point2f& position, InputArray _affine, OutputArray _patch, Size patchSize);
97+
4798
//! @}
4899

49100
}

modules/fastcv/perf/perf_warp.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ static void getInvertMatrix(Mat& src, Size dstSize, Mat& M)
3939
invert(M,M);
4040
}
4141

42+
static cv::Mat getInverseAffine(const cv::Mat& affine)
43+
{
44+
// Extract the 2x2 part
45+
cv::Mat rotationScaling = affine(cv::Rect(0, 0, 2, 2));
46+
47+
// Invert the 2x2 part
48+
cv::Mat inverseRotationScaling;
49+
cv::invert(rotationScaling, inverseRotationScaling);
50+
51+
// Extract the translation part
52+
cv::Mat translation = affine(cv::Rect(2, 0, 1, 2));
53+
54+
// Compute the new translation
55+
cv::Mat inverseTranslation = -inverseRotationScaling * translation;
56+
57+
// Construct the inverse affine matrix
58+
cv::Mat inverseAffine = cv::Mat::zeros(2, 3, CV_32F);
59+
inverseRotationScaling.copyTo(inverseAffine(cv::Rect(0, 0, 2, 2)));
60+
inverseTranslation.copyTo(inverseAffine(cv::Rect(2, 0, 1, 2)));
61+
62+
return inverseAffine;
63+
}
64+
4265
typedef perf::TestBaseWithParam<Size> WarpPerspective2PlanePerfTest;
4366

4467
PERF_TEST_P(WarpPerspective2PlanePerfTest, run,
@@ -93,4 +116,113 @@ PERF_TEST_P(WarpPerspectivePerfTest, run,
93116
SANITY_CHECK_NOTHING();
94117
}
95118

119+
typedef TestBaseWithParam< tuple<MatType, Size> > WarpAffine3ChannelPerf;
120+
121+
PERF_TEST_P(WarpAffine3ChannelPerf, run, Combine(
122+
Values(CV_8UC3),
123+
Values( szVGA, sz720p, sz1080p)
124+
))
125+
{
126+
Size sz, szSrc(512, 512);
127+
int dataType;
128+
dataType = get<0>(GetParam());
129+
sz = get<1>(GetParam());
130+
131+
cv::Mat src(szSrc, dataType), dst(sz, dataType);
132+
133+
cvtest::fillGradient(src);
134+
135+
//Affine matrix
136+
float angle = 30.0; // Rotation angle in degrees
137+
float scale = 2.2; // Scale factor
138+
cv::Mat affine = cv::getRotationMatrix2D(cv::Point2f(100, 100), angle, scale);
139+
140+
// Compute the inverse affine matrix
141+
cv::Mat inverseAffine = getInverseAffine(affine);
142+
143+
// Create the dstBorder array
144+
Mat dstBorder;
145+
146+
declare.in(src).out(dst);
147+
148+
while (next())
149+
{
150+
startTimer();
151+
cv::fastcv::warpAffine3Channel(src, dst, inverseAffine, sz, dstBorder);
152+
stopTimer();
153+
}
154+
155+
SANITY_CHECK_NOTHING();
156+
}
157+
158+
typedef perf::TestBaseWithParam<std::tuple<cv::Size, cv::Point2f, cv::Mat>> WarpAffineROIPerfTest;
159+
160+
PERF_TEST_P(WarpAffineROIPerfTest, run, ::testing::Combine(
161+
::testing::Values(cv::Size(50, 50), cv::Size(100, 100)), // patch size
162+
::testing::Values(cv::Point2f(50.0f, 50.0f), cv::Point2f(100.0f, 100.0f)), // position
163+
::testing::Values((cv::Mat_<float>(2, 2) << 1, 0, 0, 1), // identity matrix
164+
(cv::Mat_<float>(2, 2) << cos(CV_PI), -sin(CV_PI), sin(CV_PI), cos(CV_PI))) // rotation matrix
165+
))
166+
{
167+
cv::Size patchSize = std::get<0>(GetParam());
168+
cv::Point2f position = std::get<1>(GetParam());
169+
cv::Mat affine = std::get<2>(GetParam());
170+
171+
cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE);
172+
cv::Mat patch;
173+
174+
while (next())
175+
{
176+
startTimer();
177+
cv::fastcv::warpAffineROI(src, position, affine, patch, patchSize);
178+
stopTimer();
179+
}
180+
181+
SANITY_CHECK_NOTHING();
182+
}
183+
184+
typedef TestBaseWithParam<tuple<int, int> > WarpAffinePerfTest;
185+
186+
PERF_TEST_P(WarpAffinePerfTest, run, ::testing::Combine(
187+
::testing::Values(cv::InterpolationFlags::INTER_NEAREST, cv::InterpolationFlags::INTER_LINEAR, cv::InterpolationFlags::INTER_AREA),
188+
::testing::Values(0, 255) // Black and white borders
189+
))
190+
{
191+
// Load the source image
192+
cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE);
193+
ASSERT_FALSE(src.empty());
194+
195+
// Generate random values for the affine matrix
196+
std::srand(std::time(0));
197+
float angle = static_cast<float>(std::rand() % 360); // Random angle between 0 and 360 degrees
198+
float scale = static_cast<float>(std::rand() % 200) / 100.0f + 0.5f; // Random scale between 0.5 and 2.5
199+
float tx = static_cast<float>(std::rand() % 100) - 50; // Random translation between -50 and 50
200+
float ty = static_cast<float>(std::rand() % 100) - 50; // Random translation between -50 and 50
201+
float radians = angle * CV_PI / 180.0;
202+
cv::Mat affine = (cv::Mat_<float>(2, 3) << scale * cos(radians), -scale * sin(radians), tx,
203+
scale * sin(radians), scale * cos(radians), ty);
204+
205+
// Compute the inverse affine matrix
206+
cv::Mat inverseAffine = getInverseAffine(affine);
207+
208+
// Define the destination size
209+
cv::Size dsize(src.cols, src.rows);
210+
211+
// Define the output matrix
212+
cv::Mat dst;
213+
214+
// Get the parameters
215+
int interpolation = std::get<0>(GetParam());
216+
int borderValue = std::get<1>(GetParam());
217+
218+
while (next())
219+
{
220+
startTimer();
221+
cv::fastcv::warpAffine(src, dst, inverseAffine, dsize, interpolation, borderValue);
222+
stopTimer();
223+
}
224+
225+
SANITY_CHECK_NOTHING();
226+
}
227+
96228
} //namespace

modules/fastcv/src/warp.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,109 @@ void warpPerspective(InputArray _src, OutputArray _dst, InputArray _M0, Size dsi
175175
FcvWarpPerspectiveLoop_Invoker(src, tmp, dst, tmp, matrix, fcvInterpolation, fcvBorder, fcvBorderValue), nStripes);
176176
}
177177

178+
void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, int interpolation, int borderValue)
179+
{
180+
INITIALIZATION_CHECK;
181+
CV_Assert(!_src.empty() && _src.type() == CV_8UC1);
182+
CV_Assert(!_M.empty());
183+
184+
Mat src = _src.getMat();
185+
_dst.create(dsize, src.type());
186+
Mat dst = _dst.getMat();
187+
Mat M = _M.getMat();
188+
189+
fcvStatus status;
190+
fcvInterpolationType fcvInterpolation;
191+
192+
float32_t affineMatrix[6] = { M.at<float>(0, 0), M.at<float>(0, 1), M.at<float>(0, 2),
193+
M.at<float>(1, 0), M.at<float>(1, 1), M.at<float>(1, 2) };
194+
195+
switch (interpolation)
196+
{
197+
case cv::InterpolationFlags::INTER_NEAREST:
198+
{
199+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR;
200+
break;
201+
}
202+
case cv::InterpolationFlags::INTER_LINEAR:
203+
{
204+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_BILINEAR;
205+
break;
206+
}
207+
case cv::InterpolationFlags::INTER_AREA:
208+
{
209+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_AREA;
210+
break;
211+
}
212+
default:
213+
CV_Error(cv::Error::StsBadArg, "Unsupported interpolation type");
214+
}
215+
216+
status = fcvTransformAffineClippedu8_v3((const uint8_t*)src.data, src.cols, src.rows, src.step, affineMatrix,
217+
(uint8_t*)dst.data, dst.cols, dst.rows, dst.step, NULL, fcvInterpolation, FASTCV_BORDER_CONSTANT, borderValue);
218+
219+
if (status != FASTCV_SUCCESS)
220+
{
221+
std::string s = fcvStatusStrings.count(status) ? fcvStatusStrings.at(status) : "unknown";
222+
CV_Error( cv::Error::StsInternal, "FastCV error: " + s);
223+
}
224+
}
225+
226+
void warpAffine3Channel(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, OutputArray _dstBorder)
227+
{
228+
INITIALIZATION_CHECK;
229+
230+
CV_Assert(!_src.empty() && _src.type() == CV_8UC3);
231+
CV_Assert(!_M.empty());
232+
233+
234+
Mat src = _src.getMat();
235+
_dst.create(dsize, src.type());
236+
Mat dst = _dst.getMat();
237+
Mat M = _M.getMat();
238+
239+
_dstBorder.create(1, dsize.height * 2, CV_32S);
240+
Mat dstBorder = _dstBorder.getMat();
241+
242+
float32_t affineMatrix[6] = { M.at<float>(0, 0), M.at<float>(0, 1), M.at<float>(0, 2),
243+
M.at<float>(1, 0), M.at<float>(1, 1), M.at<float>(1, 2) };
244+
245+
246+
fcv3ChannelTransformAffineClippedBCu8((const uint8_t*)src.data, src.cols, src.rows, src.step[0],
247+
affineMatrix, (uint8_t*)dst.data, dst.cols, dst.rows, dst.step[0], (uint32_t*)dstBorder.data);
248+
}
249+
250+
void warpAffineROI(InputArray _src, const cv::Point2f& position, InputArray _affine, OutputArray _patch, Size patchSize)
251+
{
252+
INITIALIZATION_CHECK;
253+
254+
cv::Mat src = _src.getMat();
255+
cv::Mat affine = _affine.getMat();
256+
257+
CV_Assert(!src.empty() && src.type() == CV_8UC1);
258+
CV_Assert(!affine.empty() && affine.type() == CV_32FC1);
259+
260+
float affineMatrix[4];
261+
for (int i = 0; i < 4; i++) {
262+
affineMatrix[i] = affine.at<float>(i / 2, i % 2);
263+
}
264+
265+
_patch.create(patchSize, src.type());
266+
cv::Mat patch = _patch.getMat();
267+
268+
float positionArray[2];
269+
positionArray[0] = position.x;
270+
positionArray[1] = position.y;
271+
272+
// Perform affine transformation
273+
int status = fcvTransformAffineu8_v2((const uint8_t*)src.data, src.cols, src.rows, src.step, positionArray,
274+
affineMatrix, (uint8_t*)patch.data, patch.cols, patch.rows, patch.step);
275+
276+
if (status != 0)
277+
{
278+
CV_Error(cv::Error::StsInternal, "FastCV error: General failure");
279+
}
280+
}
281+
178282
} // fastcv::
179283
} // cv::

0 commit comments

Comments
 (0)