Skip to content

Commit 7495a47

Browse files
authored
Merge pull request opencv#18053 from Yosshi999:bit-exact-resizeNN
Bit-exact Nearest Neighbor Resizing * bit exact resizeNN * change the value of method enum * add bitexact-nn to ResizeExactTest * test to compare with non-exact version * add perf for bit-exact resizenn * use cvFloor-equivalent * 1/3 scaling is not stable for floating calculation * stricter test * bugfix: broken data in case of 6 or 12bytes elements * bugfix: broken data in default pix_size * stricter threshold * use raw() for floor * use double instead of int * follow code reviews * fewer cases in perf test * center pixel convention
1 parent 7ce56b3 commit 7495a47

File tree

6 files changed

+249
-2
lines changed

6 files changed

+249
-2
lines changed

modules/imgproc/include/opencv2/imgproc.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ enum InterpolationFlags{
252252
INTER_LANCZOS4 = 4,
253253
/** Bit exact bilinear interpolation */
254254
INTER_LINEAR_EXACT = 5,
255+
/** Bit exact nearest neighbor interpolation. This will produce same results as
256+
the nearest neighbor method in PIL, scikit-image or Matlab. */
257+
INTER_NEAREST_EXACT = 6,
255258
/** mask for interpolation codes */
256259
INTER_MAX = 7,
257260
/** flag, fills all of the destination image pixels. If some of them correspond to outliers in the

modules/imgproc/perf/perf_resize.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,30 @@ PERF_TEST_P(MatInfo_Size_Scale_NN, ResizeNN,
254254
SANITY_CHECK_NOTHING();
255255
}
256256

257+
PERF_TEST_P(MatInfo_Size_Scale_NN, ResizeNNExact,
258+
testing::Combine(
259+
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4),
260+
testing::Values(sz720p, sz1080p),
261+
testing::Values(0.25, 0.5, 2.0)
262+
)
263+
)
264+
{
265+
int matType = get<0>(GetParam());
266+
Size from = get<1>(GetParam());
267+
double scale = get<2>(GetParam());
268+
269+
cv::Mat src(from, matType);
270+
271+
Size to(cvRound(from.width * scale), cvRound(from.height * scale));
272+
cv::Mat dst(to, matType);
273+
274+
declare.in(src, WARMUP_RNG).out(dst);
275+
declare.time(100);
276+
277+
TEST_CYCLE() resize(src, dst, dst.size(), 0, 0, INTER_NEAREST_EXACT);
278+
279+
EXPECT_GT(countNonZero(dst.reshape(1)), 0);
280+
SANITY_CHECK_NOTHING();
281+
}
282+
257283
} // namespace

modules/imgproc/src/fixedpoint.inl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class ufixedpoint64
157157
CV_ALWAYS_INLINE bool isZero() { return val == 0; }
158158
static CV_ALWAYS_INLINE ufixedpoint64 zero() { return ufixedpoint64(); }
159159
static CV_ALWAYS_INLINE ufixedpoint64 one() { return ufixedpoint64((uint64_t)(1ULL << fixedShift)); }
160+
CV_ALWAYS_INLINE uint32_t cvFloor() const { return cv::saturate_cast<uint32_t>(val >> fixedShift); }
160161
friend class ufixedpoint32;
161162
};
162163

modules/imgproc/src/resize.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "opencl_kernels_imgproc.hpp"
5252
#include "hal_replacement.hpp"
5353
#include "opencv2/core/hal/intrin.hpp"
54+
#include "opencv2/core/utils/buffer_area.private.hpp"
5455

5556
#include "opencv2/core/openvx/ovx_defs.hpp"
5657
#include "resize.hpp"
@@ -1104,6 +1105,121 @@ resizeNN( const Mat& src, Mat& dst, double fx, double fy )
11041105
}
11051106
}
11061107

1108+
class resizeNN_bitexactInvoker : public ParallelLoopBody
1109+
{
1110+
public:
1111+
resizeNN_bitexactInvoker(const Mat& _src, Mat& _dst, int* _x_ofse, int _ify, int _ify0)
1112+
: src(_src), dst(_dst), x_ofse(_x_ofse), ify(_ify), ify0(_ify0) {}
1113+
1114+
virtual void operator() (const Range& range) const CV_OVERRIDE
1115+
{
1116+
Size ssize = src.size(), dsize = dst.size();
1117+
int pix_size = (int)src.elemSize();
1118+
for( int y = range.start; y < range.end; y++ )
1119+
{
1120+
uchar* D = dst.ptr(y);
1121+
int _sy = (ify * y + ify0) >> 16;
1122+
int sy = std::min(_sy, ssize.height-1);
1123+
const uchar* S = src.ptr(sy);
1124+
1125+
int x = 0;
1126+
switch( pix_size )
1127+
{
1128+
case 1:
1129+
#if CV_SIMD
1130+
for( ; x <= dsize.width - v_uint8::nlanes; x += v_uint8::nlanes )
1131+
v_store(D + x, vx_lut(S, x_ofse + x));
1132+
#endif
1133+
for( ; x < dsize.width; x++ )
1134+
D[x] = S[x_ofse[x]];
1135+
break;
1136+
case 2:
1137+
#if CV_SIMD
1138+
for( ; x <= dsize.width - v_uint16::nlanes; x += v_uint16::nlanes )
1139+
v_store((ushort*)D + x, vx_lut((ushort*)S, x_ofse + x));
1140+
#endif
1141+
for( ; x < dsize.width; x++ )
1142+
*((ushort*)D + x) = *((ushort*)S + x_ofse[x]);
1143+
break;
1144+
case 3:
1145+
for( ; x < dsize.width; x++, D += 3 )
1146+
{
1147+
const uchar* _tS = S + x_ofse[x] * 3;
1148+
D[0] = _tS[0]; D[1] = _tS[1]; D[2] = _tS[2];
1149+
}
1150+
break;
1151+
case 4:
1152+
#if CV_SIMD
1153+
for( ; x <= dsize.width - v_uint32::nlanes; x += v_uint32::nlanes )
1154+
v_store((uint32_t*)D + x, vx_lut((uint32_t*)S, x_ofse + x));
1155+
#endif
1156+
for( ; x < dsize.width; x++ )
1157+
*((uint32_t*)D + x) = *((uint32_t*)S + x_ofse[x]);
1158+
break;
1159+
case 6:
1160+
for( ; x < dsize.width; x++, D += 6 )
1161+
{
1162+
const ushort* _tS = (const ushort*)(S + x_ofse[x]*6);
1163+
ushort* _tD = (ushort*)D;
1164+
_tD[0] = _tS[0]; _tD[1] = _tS[1]; _tD[2] = _tS[2];
1165+
}
1166+
break;
1167+
case 8:
1168+
#if CV_SIMD
1169+
for( ; x <= dsize.width - v_uint64::nlanes; x += v_uint64::nlanes )
1170+
v_store((uint64_t*)D + x, vx_lut((uint64_t*)S, x_ofse + x));
1171+
#endif
1172+
for( ; x < dsize.width; x++ )
1173+
*((uint64_t*)D + x) = *((uint64_t*)S + x_ofse[x]);
1174+
break;
1175+
case 12:
1176+
for( ; x < dsize.width; x++, D += 12 )
1177+
{
1178+
const int* _tS = (const int*)(S + x_ofse[x]*12);
1179+
int* _tD = (int*)D;
1180+
_tD[0] = _tS[0]; _tD[1] = _tS[1]; _tD[2] = _tS[2];
1181+
}
1182+
break;
1183+
default:
1184+
for( x = 0; x < dsize.width; x++, D += pix_size )
1185+
{
1186+
const uchar* _tS = S + x_ofse[x] * pix_size;
1187+
for (int k = 0; k < pix_size; k++)
1188+
D[k] = _tS[k];
1189+
}
1190+
}
1191+
}
1192+
}
1193+
private:
1194+
const Mat& src;
1195+
Mat& dst;
1196+
int* x_ofse;
1197+
const int ify;
1198+
const int ify0;
1199+
};
1200+
1201+
static void resizeNN_bitexact( const Mat& src, Mat& dst, double /*fx*/, double /*fy*/ )
1202+
{
1203+
Size ssize = src.size(), dsize = dst.size();
1204+
int ifx = ((ssize.width << 16) + dsize.width / 2) / dsize.width; // 16bit fixed-point arithmetic
1205+
int ifx0 = ifx / 2 - 1; // This method uses center pixel coordinate as Pillow and scikit-images do.
1206+
int ify = ((ssize.height << 16) + dsize.height / 2) / dsize.height;
1207+
int ify0 = ify / 2 - 1;
1208+
1209+
cv::utils::BufferArea area;
1210+
int* x_ofse = 0;
1211+
area.allocate(x_ofse, dsize.width, CV_SIMD_WIDTH);
1212+
area.commit();
1213+
1214+
for( int x = 0; x < dsize.width; x++ )
1215+
{
1216+
int sx = (ifx * x + ifx0) >> 16;
1217+
x_ofse[x] = std::min(sx, ssize.width-1); // offset in element (not byte)
1218+
}
1219+
Range range(0, dsize.height);
1220+
resizeNN_bitexactInvoker invoker(src, dst, x_ofse, ify, ify0);
1221+
parallel_for_(range, invoker, dst.total()/(double)(1<<16));
1222+
}
11071223

11081224
struct VResizeNoVec
11091225
{
@@ -3723,6 +3839,12 @@ void resize(int src_type,
37233839
return;
37243840
}
37253841

3842+
if( interpolation == INTER_NEAREST_EXACT )
3843+
{
3844+
resizeNN_bitexact( src, dst, inv_scale_x, inv_scale_y );
3845+
return;
3846+
}
3847+
37263848
int k, sx, sy, dx, dy;
37273849

37283850

modules/imgproc/test/test_imgwarp.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,24 @@ class CV_ResizeExactTest : public CV_ResizeTest
346346

347347
CV_ResizeExactTest::CV_ResizeExactTest() : CV_ResizeTest()
348348
{
349-
max_interpolation = 1;
349+
max_interpolation = 2;
350350
}
351351

352352

353353
void CV_ResizeExactTest::get_test_array_types_and_sizes(int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types)
354354
{
355355
CV_ResizeTest::get_test_array_types_and_sizes(test_case_idx, sizes, types);
356-
interpolation = INTER_LINEAR_EXACT;
356+
switch (interpolation)
357+
{
358+
case 0:
359+
interpolation = INTER_LINEAR_EXACT;
360+
break;
361+
case 1:
362+
interpolation = INTER_NEAREST_EXACT;
363+
break;
364+
default:
365+
CV_Assert(interpolation < max_interpolation);
366+
}
357367
if (CV_MAT_DEPTH(types[INPUT][0]) == CV_32F ||
358368
CV_MAT_DEPTH(types[INPUT][0]) == CV_64F)
359369
types[INPUT][0] = types[INPUT_OUTPUT][0] = types[REF_INPUT_OUTPUT][0] = CV_MAKETYPE(CV_8U, CV_MAT_CN(types[INPUT][0]));

modules/imgproc/test/test_resize_bitexact.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,89 @@ TEST(Resize_Bitexact, Linear8U)
152152
}
153153
}
154154

155+
PARAM_TEST_CASE(Resize_Bitexact, int)
156+
{
157+
public:
158+
int depth;
159+
160+
virtual void SetUp()
161+
{
162+
depth = GET_PARAM(0);
163+
}
164+
165+
double CountDiff(const Mat& src)
166+
{
167+
Mat dstExact; cv::resize(src, dstExact, Size(), 2, 1, INTER_NEAREST_EXACT);
168+
Mat dstNonExact; cv::resize(src, dstNonExact, Size(), 2, 1, INTER_NEAREST);
169+
170+
return cv::norm(dstExact, dstNonExact, NORM_INF);
171+
}
172+
};
173+
174+
TEST_P(Resize_Bitexact, Nearest8U_vsNonExact)
175+
{
176+
Mat mat_color, mat_gray;
177+
Mat src_color = imread(cvtest::findDataFile("shared/lena.png"));
178+
Mat src_gray; cv::cvtColor(src_color, src_gray, COLOR_BGR2GRAY);
179+
src_color.convertTo(mat_color, depth);
180+
src_gray.convertTo(mat_gray, depth);
181+
182+
EXPECT_EQ(CountDiff(mat_color), 0) << "color, type: " << depth;
183+
EXPECT_EQ(CountDiff(mat_gray), 0) << "gray, type: " << depth;
184+
}
185+
186+
// Now INTER_NEAREST's convention and INTER_NEAREST_EXACT's one are different.
187+
INSTANTIATE_TEST_CASE_P(DISABLED_Imgproc, Resize_Bitexact,
188+
testing::Values(CV_8U, CV_16U, CV_32F, CV_64F)
189+
);
190+
191+
TEST(Resize_Bitexact, Nearest8U)
192+
{
193+
Mat src[6], dst[6];
194+
195+
// 2x decimation
196+
src[0] = (Mat_<uint8_t>(1, 6) << 0, 1, 2, 3, 4, 5);
197+
dst[0] = (Mat_<uint8_t>(1, 3) << 0, 2, 4);
198+
199+
// decimation odd to 1
200+
src[1] = (Mat_<uint8_t>(1, 5) << 0, 1, 2, 3, 4);
201+
dst[1] = (Mat_<uint8_t>(1, 1) << 2);
202+
203+
// decimation n*2-1 to n
204+
src[2] = (Mat_<uint8_t>(1, 5) << 0, 1, 2, 3, 4);
205+
dst[2] = (Mat_<uint8_t>(1, 3) << 0, 2, 4);
206+
207+
// decimation n*2+1 to n
208+
src[3] = (Mat_<uint8_t>(1, 5) << 0, 1, 2, 3, 4);
209+
dst[3] = (Mat_<uint8_t>(1, 2) << 1, 3);
210+
211+
// zoom
212+
src[4] = (Mat_<uint8_t>(3, 5) <<
213+
0, 1, 2, 3, 4,
214+
5, 6, 7, 8, 9,
215+
10, 11, 12, 13, 14);
216+
dst[4] = (Mat_<uint8_t>(5, 7) <<
217+
0, 1, 1, 2, 3, 3, 4,
218+
0, 1, 1, 2, 3, 3, 4,
219+
5, 6, 6, 7, 8, 8, 9,
220+
10, 11, 11, 12, 13, 13, 14,
221+
10, 11, 11, 12, 13, 13, 14);
222+
223+
src[5] = (Mat_<uint8_t>(2, 3) <<
224+
0, 1, 2,
225+
3, 4, 5);
226+
dst[5] = (Mat_<uint8_t>(4, 6) <<
227+
0, 0, 1, 1, 2, 2,
228+
0, 0, 1, 1, 2, 2,
229+
3, 3, 4, 4, 5, 5,
230+
3, 3, 4, 4, 5, 5);
231+
232+
for (int i = 0; i < 6; i++)
233+
{
234+
Mat calc;
235+
resize(src[i], calc, dst[i].size(), 0, 0, INTER_NEAREST_EXACT);
236+
EXPECT_EQ(cvtest::norm(calc, dst[i], cv::NORM_L1), 0);
237+
}
238+
}
239+
155240
}} // namespace

0 commit comments

Comments
 (0)