Skip to content

Commit f373ee2

Browse files
committed
Refactor: unify warpAffine, warpAffine3Channel, and warpAffineROI into a single warpAffine function
1 parent 32b9dc4 commit f373ee2

File tree

4 files changed

+157
-122
lines changed

4 files changed

+157
-122
lines changed

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

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,55 +45,44 @@ CV_EXPORTS_W void warpPerspective2Plane(InputArray _src1, InputArray _src2, Outp
4545
InputArray _M0, Size dsize);
4646

4747
/**
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.
48+
* @brief Performs an affine transformation on an input image using a provided transformation matrix.
49+
*
50+
* This function performs two types of operations based on the transformation matrix:
51+
*
52+
* 1. Standard Affine Transformation (2x3 matrix):
53+
* - Transforms the entire input image using the affine matrix
54+
* - Supports both CV_8UC1 and CV_8UC3 types
55+
*
56+
* 2. Patch Extraction with Transformation (2x2 matrix):
57+
* - Extracts and transforms a patch from the input image
58+
* - Only supports CV_8UC1 type
59+
* - If input is a ROI: patch is extracted from ROI center in the original image
60+
* - If input is full image: patch is extracted from image center
61+
*
62+
* @param _src Input image. Supported formats:
63+
* - CV_8UC1: 8-bit single-channel
64+
* - CV_8UC3: 8-bit three-channel - only for 2x3 matrix
65+
* @param _dst Output image. Will have the same type as src and size specified by dsize
66+
* @param _M 2x2/2x3 affine transformation matrix (inversed map)
67+
* @param dsize Output size:
68+
* - For 2x3 matrix: Size of the output image
69+
* - For 2x2 matrix: Size of the extracted patch
70+
* @param interpolation Interpolation method. Only applicable for 2x3 transformation with CV_8UC1 input.
71+
* Options:
72+
* - INTER_NEAREST: Nearest-neighbor interpolation
73+
* - INTER_LINEAR: Bilinear interpolation (default)
74+
* - INTER_AREA: Area-based interpolation
75+
* - INTER_CUBIC: Bicubic interpolation
76+
* Note: CV_8UC3 input always use bicubic interpolation internally
77+
* @param borderValue Constant pixel value for border pixels. Only applicable for 2x3 transformations
78+
* with single-channel input.
5879
*
5980
* @note The affine matrix follows the inverse mapping convention, applied to destination coordinates
6081
* to produce corresponding source coordinates.
6182
* @note The function uses 'FASTCV_BORDER_CONSTANT' for border handling, with the specified 'borderValue'.
6283
*/
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);
84+
CV_EXPORTS_W void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, int interpolation = INTER_LINEAR,
85+
int borderValue = 0);
9786

9887
//! @}
9988

modules/fastcv/perf/perf_warp.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ PERF_TEST_P(WarpAffine3ChannelPerf, run, Combine(
148148
while (next())
149149
{
150150
startTimer();
151-
cv::fastcv::warpAffine3Channel(src, dst, inverseAffine, sz, dstBorder);
151+
cv::fastcv::warpAffine(src, dst, inverseAffine, sz);
152152
stopTimer();
153153
}
154154

@@ -169,12 +169,20 @@ PERF_TEST_P(WarpAffineROIPerfTest, run, ::testing::Combine(
169169
cv::Mat affine = std::get<2>(GetParam());
170170

171171
cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE);
172+
173+
// Create ROI with top-left at the specified position
174+
cv::Rect roiRect(static_cast<int>(position.x), static_cast<int>(position.y), patchSize.width, patchSize.height);
175+
176+
// Ensure ROI is within image bounds
177+
roiRect = roiRect & cv::Rect(0, 0, src.cols, src.rows);
178+
cv::Mat roi = src(roiRect);
179+
172180
cv::Mat patch;
173181

174182
while (next())
175183
{
176184
startTimer();
177-
cv::fastcv::warpAffineROI(src, position, affine, patch, patchSize);
185+
cv::fastcv::warpAffine(roi, patch, affine, patchSize);
178186
stopTimer();
179187
}
180188

modules/fastcv/src/warp.cpp

Lines changed: 109 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -175,107 +175,144 @@ 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)
178+
void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize,
179+
int interpolation, int borderValue)
179180
{
180181
INITIALIZATION_CHECK;
181-
CV_Assert(!_src.empty() && _src.type() == CV_8UC1);
182+
CV_Assert(!_src.empty());
182183
CV_Assert(!_M.empty());
183184

184185
Mat src = _src.getMat();
185-
_dst.create(dsize, src.type());
186-
Mat dst = _dst.getMat();
187186
Mat M = _M.getMat();
188187

189-
fcvStatus status;
190-
fcvInterpolationType fcvInterpolation;
188+
CV_CheckEQ(M.rows, 2, "Affine Matrix must have 2 rows");
189+
CV_Check(M.cols, M.cols == 2 || M.cols == 3, "Affine Matrix must be 2x2 or 2x3");
191190

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)
191+
if (M.rows == 2 && M.cols == 2)
196192
{
197-
case cv::InterpolationFlags::INTER_NEAREST:
198-
{
199-
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR;
200-
break;
201-
}
202-
case cv::InterpolationFlags::INTER_LINEAR:
193+
CV_CheckTypeEQ(src.type(), CV_8UC1, "2x2 matrix transformation only supports CV_8UC1");
194+
195+
// Check if src is a ROI
196+
Size wholeSize;
197+
Point ofs;
198+
src.locateROI(wholeSize, ofs);
199+
bool isROI = (wholeSize.width > src.cols || wholeSize.height > src.rows);
200+
201+
Mat fullImage;
202+
Point2f center;
203+
204+
if (isROI)
203205
{
204-
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_BILINEAR;
205-
break;
206+
center.x = ofs.x + src.cols / 2.0f;
207+
center.y = ofs.y + src.rows / 2.0f;
208+
209+
CV_Check(center.x, center.x >= 0 && center.x < wholeSize.width, "ROI center X is outside full image bounds");
210+
CV_Check(center.y, center.y >= 0 && center.y < wholeSize.height, "ROI center Y is outside full image bounds");
211+
212+
size_t offset = ofs.y * src.step + ofs.x * src.elemSize();
213+
fullImage = Mat(wholeSize, src.type(), src.data - offset);
206214
}
207-
case cv::InterpolationFlags::INTER_AREA:
215+
else
208216
{
209-
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_AREA;
210-
break;
217+
// Use src as is, center at image center
218+
fullImage = src;
219+
center.x = src.cols / 2.0f;
220+
center.y = src.rows / 2.0f;
221+
222+
CV_LOG_WARNING(NULL, "2x2 matrix with non-ROI input. Using image center for patch extraction.");
211223
}
212-
default:
213-
CV_Error(cv::Error::StsBadArg, "Unsupported interpolation type");
214-
}
215224

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);
225+
float affineMatrix[4] = {
226+
M.at<float>(0, 0), M.at<float>(0, 1),
227+
M.at<float>(1, 0), M.at<float>(1, 1)};
218228

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+
float position[2] = {center.x, center.y};
229230

230-
CV_Assert(!_src.empty() && _src.type() == CV_8UC3);
231-
CV_Assert(!_M.empty());
231+
_dst.create(dsize, src.type());
232+
Mat dst = _dst.getMat();
233+
dst.step = dst.cols * src.elemSize();
232234

235+
int status = fcvTransformAffineu8_v2(
236+
(const uint8_t *)fullImage.data,
237+
fullImage.cols, fullImage.rows, fullImage.step,
238+
position,
239+
affineMatrix,
240+
(uint8_t *)dst.data,
241+
dst.cols, dst.rows, dst.step);
233242

234-
Mat src = _src.getMat();
235-
_dst.create(dsize, src.type());
236-
Mat dst = _dst.getMat();
237-
Mat M = _M.getMat();
243+
if (status != 0)
244+
{
245+
CV_Error(Error::StsInternal, "FastCV patch extraction failed");
246+
}
238247

239-
_dstBorder.create(1, dsize.height * 2, CV_32S);
240-
Mat dstBorder = _dstBorder.getMat();
248+
return;
249+
}
241250

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) };
251+
// Validate 2x3 matrix for standard transformation
252+
CV_CheckEQ(M.cols, 3, "Matrix must be 2x3 for standard affine transformation");
253+
CV_Check(src.type(), src.type() == CV_8UC1 || src.type() == CV_8UC3, "Standard transformation supports CV_8UC1 or CV_8UC3");
244254

255+
float32_t affineMatrix[6] = {
256+
M.at<float>(0, 0), M.at<float>(0, 1), M.at<float>(0, 2),
257+
M.at<float>(1, 0), M.at<float>(1, 1), M.at<float>(1, 2)};
245258

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-
}
259+
_dst.create(dsize, src.type());
260+
Mat dst = _dst.getMat();
249261

250-
void warpAffineROI(InputArray _src, const cv::Point2f& position, InputArray _affine, OutputArray _patch, Size patchSize)
251-
{
252-
INITIALIZATION_CHECK;
262+
if (src.channels() == 1)
263+
{
264+
fcvStatus status;
265+
fcvInterpolationType fcvInterpolation;
253266

254-
cv::Mat src = _src.getMat();
255-
cv::Mat affine = _affine.getMat();
267+
switch (interpolation)
268+
{
269+
case cv::InterpolationFlags::INTER_NEAREST:
270+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR;
271+
break;
272+
case cv::InterpolationFlags::INTER_LINEAR:
273+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_BILINEAR;
274+
break;
275+
case cv::InterpolationFlags::INTER_AREA:
276+
fcvInterpolation = FASTCV_INTERPOLATION_TYPE_AREA;
277+
break;
278+
default:
279+
CV_Error(cv::Error::StsBadArg, "Unsupported interpolation type");
280+
}
256281

257-
CV_Assert(!src.empty() && src.type() == CV_8UC1);
258-
CV_Assert(!affine.empty() && affine.type() == CV_32FC1);
282+
status = fcvTransformAffineClippedu8_v3(
283+
(const uint8_t *)src.data, src.cols, src.rows, src.step,
284+
affineMatrix,
285+
(uint8_t *)dst.data, dst.cols, dst.rows, dst.step,
286+
NULL,
287+
fcvInterpolation,
288+
FASTCV_BORDER_CONSTANT,
289+
borderValue);
259290

260-
float affineMatrix[4];
261-
for (int i = 0; i < 4; i++) {
262-
affineMatrix[i] = affine.at<float>(i / 2, i % 2);
291+
if (status != FASTCV_SUCCESS)
292+
{
293+
std::string s = fcvStatusStrings.count(status) ? fcvStatusStrings.at(status) : "unknown";
294+
CV_Error(cv::Error::StsInternal, "FastCV error: " + s);
295+
}
263296
}
297+
else if (src.channels() == 3)
298+
{
299+
CV_LOG_INFO(NULL, "warpAffine: 3-channel images use bicubic interpolation internally.");
264300

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);
301+
std::vector<uint32_t> dstBorder;
302+
try
303+
{
304+
dstBorder.resize(dsize.height * 2);
305+
}
306+
catch (const std::bad_alloc &)
307+
{
308+
CV_Error(Error::StsNoMem, "Failed to allocate border array");
309+
}
275310

276-
if (status != 0)
277-
{
278-
CV_Error(cv::Error::StsInternal, "FastCV error: General failure");
311+
fcv3ChannelTransformAffineClippedBCu8(
312+
(const uint8_t *)src.data, src.cols, src.rows, src.step[0],
313+
affineMatrix,
314+
(uint8_t *)dst.data, dst.cols, dst.rows, dst.step[0],
315+
dstBorder.data());
279316
}
280317
}
281318

modules/fastcv/test/test_warp.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,8 @@ TEST(WarpAffine3ChannelTest, accuracy)
139139
cv::Size dsize(src.cols, src.rows);
140140

141141
cv::Mat dst;
142-
cv::Mat dstBorder;
143142

144-
cv::fastcv::warpAffine3Channel(src, dst, M, dsize, dstBorder);
143+
cv::fastcv::warpAffine(src, dst, M, dsize);
145144

146145
EXPECT_FALSE(dst.empty());
147146
}
@@ -158,10 +157,12 @@ TEST(WarpAffineROITest, accuracy)
158157
cv::Mat affine = (cv::Mat_<float>(2, 2) << cos(radians), -sin(radians), sin(radians), cos(radians));
159158

160159
cv::Mat patch;
161-
162-
cv::fastcv::warpAffineROI(src, position, affine, patch, cv::Size(100, 100));
160+
cv::Mat roi = src(cv::Rect(0, 0, 100, 100));
161+
cv::fastcv::warpAffine(roi, patch, affine, cv::Size(100, 100));
163162

164163
EXPECT_FALSE(patch.empty());
164+
EXPECT_EQ(patch.size(), cv::Size(100, 100));
165+
EXPECT_EQ(patch.type(), CV_8UC1);
165166
}
166167

167168
typedef testing::TestWithParam<tuple<int, int>> WarpAffineTest;

0 commit comments

Comments
 (0)