You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

imgproc.cpp 34 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. /**
  2. * \file src/opr/test/imgproc.cpp
  3. * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
  4. *
  5. * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
  6. *
  7. * Unless required by applicable law or agreed to in writing,
  8. * software distributed under the License is distributed on an
  9. * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. */
  11. #include "megbrain/opr/imgproc.h"
  12. #include "dnn/legacy_checker.h"
  13. #include "megbrain/opr/basic_arith.h"
  14. #include "megbrain/opr/tensor_manip.h"
  15. #include "megbrain/test/autocheck.h"
  16. #include "megbrain/test/helper.h"
  17. #include "megbrain/test/megdnn_helper.h"
  18. #include <megdnn.h>
  19. #include <cmath>
  20. #include <iomanip>
  21. #include <random>
  22. #include <sstream>
  23. using namespace mgb;
  24. namespace {
  25. megdnn::thin_function<void(HostTensorND&)> warp_perspective_mat_gen(
  26. size_t N, size_t INP_H, size_t INP_W) {
  27. static std::mt19937 rng(next_rand_seed());
  28. auto rand_real = [&](double lo, double hi) {
  29. return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo;
  30. };
  31. auto rand_real2 = [&](double range) { return rand_real(-range, range); };
  32. auto gen = [N, INP_H, INP_W, rand_real, rand_real2](HostTensorND& mat) {
  33. auto ptr = mat.ptr<float>();
  34. for (size_t i = 0; i < N; ++i) {
  35. auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2),
  36. sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5),
  37. dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H),
  38. kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1;
  39. ptr[0] = ptr[4] = cos(rot) * scale;
  40. ptr[1] = -(ptr[3] = sin(rot) * scale);
  41. ptr[3] *= sheer;
  42. ptr[4] *= sheer;
  43. ptr[2] = dx;
  44. ptr[5] = dy;
  45. ptr[6] = kx;
  46. ptr[7] = ky;
  47. ptr[8] = kb;
  48. ptr += 9;
  49. }
  50. mgb_assert(ptr == mat.ptr<float>() + mat.shape().total_nr_elems());
  51. };
  52. return gen;
  53. }
  54. } // namespace
  55. TEST(TestOprImgproc, WarpPerspective) {
  56. set_rand_seed(20190813); // a seed that can pass the test
  57. constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3;
  58. using Checker = AutoOprChecker<2, 1>;
  59. TensorShape out_shp{N, C, 9, 10};
  60. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  61. return {opr::WarpPerspective::make(
  62. inputs[0], inputs[1], TensorShape{out_shp.shape[2], out_shp.shape[3]})};
  63. };
  64. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  65. auto opr = megdnn_naive_handle()->create_operator<megdnn::WarpPerspective>();
  66. dest[0].resize(out_shp);
  67. opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {});
  68. };
  69. auto dump_mat = [&](const Checker::NumInpArray& inp) -> std::string {
  70. std::ostringstream ostr;
  71. ostr << std::setprecision(3);
  72. auto&& mat = *inp[1];
  73. mgb_assert(mat.shape().ndim == 3);
  74. auto ptr = mat.ptr<float>();
  75. for (size_t n = 0; n < mat.shape().shape[0]; ++n) {
  76. ostr << "mat " << n << ":\n";
  77. for (size_t i = 0; i < 3; ++i) {
  78. for (size_t j = 0; j < 3; ++j) {
  79. ostr << std::setw(10) << *(ptr++);
  80. }
  81. ostr << '\n';
  82. }
  83. }
  84. return ostr.str();
  85. };
  86. Checker::RunOptions opt;
  87. opt.numdiff_eps_single_inp[1] = 1e-5;
  88. opt.numdiff_max_err_single_inp[1] = 0.5;
  89. Checker(make_graph, fwd)
  90. .set_input_generator(1, warp_perspective_mat_gen(N, INP_H, INP_W))
  91. .set_input_dump_on_error(dump_mat)
  92. .run({TensorShape{N, C, 4, 5}, {N, 3, 3}}, opt)
  93. .run({TensorShape{N, C, 6, 5}, {N, 3, 3}}, opt)
  94. .run({TensorShape{N, C, 10, 9}, {N, 3, 3}}, opt);
  95. }
  96. TEST(TestOprImgproc, WarpPerspective_NCHW4) {
  97. set_rand_seed(19931102);
  98. constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 12;
  99. using Checker = AutoOprChecker<2, 1>;
  100. TensorShape out_shp{N, C / 4, 9, 10, 4};
  101. opr::WarpPerspective::Param param;
  102. param.format = opr::WarpPerspective::Param::Format::NCHW4;
  103. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  104. auto x = opr::TypeCvt::make(inputs[0], dtype::QuantizedS8(0.01f));
  105. auto y = opr::WarpPerspective::make(
  106. x, inputs[1], TensorShape{out_shp.shape[2], out_shp.shape[3]}, param);
  107. return {opr::TypeCvt::make(y, dtype::Float32())};
  108. };
  109. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  110. auto opr = megdnn_naive_handle()->create_operator<megdnn::WarpPerspective>();
  111. opr->param() = param;
  112. auto typecvt = megdnn_naive_handle()->create_operator<megdnn::TypeCvt>();
  113. HostTensorND host_x{
  114. CompNode::load("xpux"), inp[0]->shape(), dtype::QuantizedS8(0.01f)};
  115. HostTensorND host_y{CompNode::load("xpux"), out_shp, dtype::QuantizedS8(0.01f)};
  116. typecvt->exec(inp[0]->as_megdnn(), host_x.as_megdnn());
  117. dest[0].resize(out_shp);
  118. opr->exec(host_x.as_megdnn(), inp[1]->as_megdnn(), host_y.as_megdnn(), {});
  119. typecvt->exec(host_y.as_megdnn(), dest[0].as_megdnn());
  120. };
  121. auto dump_mat = [&](const Checker::NumInpArray& inp) -> std::string {
  122. std::ostringstream ostr;
  123. ostr << std::setprecision(3);
  124. auto&& mat = *inp[1];
  125. mgb_assert(mat.shape().ndim == 3);
  126. auto ptr = mat.ptr<float>();
  127. for (size_t n = 0; n < mat.shape().shape[0]; ++n) {
  128. ostr << "mat " << n << ":\n";
  129. for (size_t i = 0; i < 3; ++i) {
  130. for (size_t j = 0; j < 3; ++j) {
  131. ostr << std::setw(10) << *(ptr++);
  132. }
  133. ostr << '\n';
  134. }
  135. }
  136. return ostr.str();
  137. };
  138. Checker::RunOptions opt;
  139. opt.outputs_max_err = 2e-2;
  140. Checker(make_graph, fwd)
  141. .disable_grad_check()
  142. .set_input_generator(1, warp_perspective_mat_gen(N, INP_H, INP_W))
  143. .set_input_dump_on_error(dump_mat)
  144. .run({TensorShape{N, C / 4, 4, 5, 4}, {N, 3, 3}}, opt)
  145. .run({TensorShape{N, C / 4, 6, 5, 4}, {N, 3, 3}}, opt)
  146. .run({TensorShape{N, C / 4, 10, 9, 4}, {N, 3, 3}}, opt);
  147. }
  148. TEST(TestOprImgproc, WarpPerspectiveWithMatIdx) {
  149. constexpr size_t INP_H = 13, INP_W = 9, N_MAT = 23, N_SRC = 5, C = 3;
  150. std::mt19937 rng(next_rand_seed());
  151. auto rand_real = [&](double lo, double hi) {
  152. return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo;
  153. };
  154. auto rand_real2 = [&](double range) { return rand_real(-range, range); };
  155. using Checker = AutoOprChecker<3, 1>;
  156. TensorShape out_shp{N_MAT, C, 9, 10};
  157. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  158. return {opr::WarpPerspective::make(
  159. inputs[0], inputs[1], inputs[2],
  160. cg::var_from_tensor_shape(
  161. inputs[0], {out_shp.shape[2], out_shp.shape[3]}))};
  162. };
  163. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  164. auto opr = megdnn_naive_handle()->create_operator<megdnn::WarpPerspective>();
  165. dest[0].resize(out_shp);
  166. opr->exec(
  167. inp[0]->as_megdnn(), inp[1]->as_megdnn(), inp[2]->as_megdnn(),
  168. dest[0].as_megdnn(), {});
  169. };
  170. auto gen_mat = [&](HostTensorND& mat) {
  171. auto ptr = mat.ptr<float>();
  172. for (size_t i = 0; i < N_MAT; ++i) {
  173. auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2),
  174. sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5),
  175. dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H),
  176. kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1;
  177. ptr[0] = ptr[4] = cos(rot) * scale;
  178. ptr[1] = -(ptr[3] = sin(rot) * scale);
  179. ptr[3] *= sheer;
  180. ptr[4] *= sheer;
  181. ptr[2] = dx;
  182. ptr[5] = dy;
  183. ptr[6] = kx;
  184. ptr[7] = ky;
  185. ptr[8] = kb;
  186. ptr += 9;
  187. }
  188. mgb_assert(ptr == mat.ptr<float>() + mat.shape().total_nr_elems());
  189. };
  190. HostTensorGenerator<dtype::Int32> gen_mat_idx_rng{0, N_SRC};
  191. auto gen_mat_idx = [&](HostTensorND& mat) { mat = *gen_mat_idx_rng(mat.shape()); };
  192. Checker(make_graph, fwd)
  193. .set_input_generator(1, gen_mat)
  194. .set_input_generator(2, gen_mat_idx)
  195. .set_input_dtype(2, dtype::Int32{})
  196. /*! it's hard to make the grad check success,
  197. the cuda implementation is grad sum */
  198. .disable_grad_check()
  199. .set_input_allow_grad(2, false)
  200. .run({TensorShape{N_SRC, C, 4, 5}, {N_MAT, 3, 3}, {N_MAT}})
  201. .run({TensorShape{N_SRC, C, 6, 5}, {N_MAT, 3, 3}, {N_MAT}})
  202. .run({TensorShape{N_SRC, C, 22, 19}, {N_MAT, 3, 3}, {N_MAT}});
  203. }
  204. TEST(TestOprImgproc, WarpPerspective_NHWC) {
  205. constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3;
  206. std::mt19937 rng(next_rand_seed());
  207. auto rand_real = [&](double lo, double hi) {
  208. return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo;
  209. };
  210. auto rand_real2 = [&](double range) { return rand_real(-range, range); };
  211. using Checker = AutoOprChecker<2, 1>;
  212. TensorShape out_shp{N, 9, 10, C};
  213. opr::WarpPerspective::Param param;
  214. param.format = opr::WarpPerspective::Param::Format::NHWC;
  215. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  216. return {opr::WarpPerspective::make(
  217. inputs[0], inputs[1], TensorShape{out_shp.shape[1], out_shp.shape[2]},
  218. param)};
  219. };
  220. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  221. auto opr = megdnn_naive_handle()->create_operator<megdnn::WarpPerspective>();
  222. dest[0].resize(out_shp);
  223. opr->param() = param;
  224. opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {});
  225. };
  226. auto gen_mat = [&](HostTensorND& mat) {
  227. auto ptr = mat.ptr<float>();
  228. for (size_t i = 0; i < N; ++i) {
  229. auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2),
  230. sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5),
  231. dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H),
  232. kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1;
  233. ptr[0] = ptr[4] = cos(rot) * scale;
  234. ptr[1] = -(ptr[3] = sin(rot) * scale);
  235. ptr[3] *= sheer;
  236. ptr[4] *= sheer;
  237. ptr[2] = dx;
  238. ptr[5] = dy;
  239. ptr[6] = kx;
  240. ptr[7] = ky;
  241. ptr[8] = kb;
  242. ptr += 9;
  243. }
  244. mgb_assert(ptr == mat.ptr<float>() + mat.shape().total_nr_elems());
  245. };
  246. auto dump_mat = [&](const Checker::NumInpArray& inp) -> std::string {
  247. std::ostringstream ostr;
  248. ostr << std::setprecision(3);
  249. auto&& mat = *inp[1];
  250. mgb_assert(mat.shape().ndim == 3);
  251. auto ptr = mat.ptr<float>();
  252. for (size_t n = 0; n < mat.shape().shape[0]; ++n) {
  253. ostr << "mat " << n << ":\n";
  254. for (size_t i = 0; i < 3; ++i) {
  255. for (size_t j = 0; j < 3; ++j) {
  256. ostr << std::setw(10) << *(ptr++);
  257. }
  258. ostr << '\n';
  259. }
  260. }
  261. return ostr.str();
  262. };
  263. Checker::RunOptions opt;
  264. opt.outputs_max_err = 0.1; // cuda NHWC impl is different from naive
  265. opt.numdiff_eps_single_inp[1] = 1e-5;
  266. opt.numdiff_max_err_single_inp[1] = 0.5;
  267. Checker(make_graph, fwd)
  268. .set_input_generator(1, gen_mat)
  269. .set_output_allow_grad(0, false)
  270. .set_input_dump_on_error(dump_mat)
  271. .run({TensorShape{N, 4, 5, C}, {N, 3, 3}}, opt)
  272. .run({TensorShape{N, 6, 5, C}, {N, 3, 3}}, opt)
  273. .run({TensorShape{N, 10, 9, C}, {N, 3, 3}}, opt);
  274. }
  275. TEST(TestOprImgproc, RotateForward) {
  276. constexpr size_t N = 2, C = 3;
  277. opr::Rotate::Param param;
  278. using Checker = AutoOprChecker<1, 1>;
  279. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  280. return {opr::Rotate::make(inputs[0], param)};
  281. };
  282. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  283. auto out_shape = inp[0]->shape();
  284. std::swap(out_shape[1], out_shape[2]);
  285. dest[0].resize(out_shape);
  286. auto opr = megdnn_naive_handle()->create_operator<megdnn::Rotate>();
  287. opr->param() = param;
  288. opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {});
  289. };
  290. Checker::RunOptions opt;
  291. Checker(make_graph, fwd, CompNode::load("cpu1"))
  292. .set_output_allow_grad(0, false)
  293. .run({TensorShape{N, 4, 5, C}}, opt)
  294. .run({TensorShape{N, 6, 5, C}}, opt)
  295. .run({TensorShape{N, 10, 9, C}}, opt);
  296. }
  297. TEST(TestOprImgproc, CvtColorForward) {
  298. constexpr size_t N = 2, C = 3;
  299. opr::CvtColor::Param param;
  300. using Checker = AutoOprChecker<1, 1>;
  301. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  302. return {opr::CvtColor::make(inputs[0], param)};
  303. };
  304. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  305. TensorLayout out_layout;
  306. auto opr = megdnn_naive_handle()->create_operator<megdnn::CvtColor>();
  307. opr->param() = param;
  308. opr->deduce_layout(inp[0]->layout(), out_layout);
  309. dest[0].resize(out_layout);
  310. opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {});
  311. };
  312. Checker::RunOptions opt;
  313. Checker(make_graph, fwd, CompNode::load("cpu1"))
  314. .set_output_allow_grad(0, false)
  315. .run({TensorShape{N, 4, 5, C}}, opt)
  316. .run({TensorShape{N, 6, 5, C}}, opt)
  317. .run({TensorShape{N, 10, 9, C}}, opt);
  318. }
  319. TEST(TestOprImgproc, GaussianBlurForward) {
  320. constexpr size_t N = 2, C = 3;
  321. opr::GaussianBlur::Param param;
  322. param.kernel_height = param.kernel_width = 5;
  323. using Checker = AutoOprChecker<1, 1>;
  324. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  325. return {opr::GaussianBlur::make(inputs[0], param)};
  326. };
  327. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  328. TensorLayout out_layout;
  329. auto opr = megdnn_naive_handle()->create_operator<megdnn::GaussianBlur>();
  330. opr->param() = param;
  331. opr->deduce_layout(inp[0]->layout(), out_layout);
  332. dest[0].resize(out_layout);
  333. opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {});
  334. };
  335. Checker::RunOptions opt;
  336. Checker(make_graph, fwd, CompNode::load("cpu1"))
  337. .set_output_allow_grad(0, false)
  338. .run({TensorShape{N, 4, 5, C}}, opt)
  339. .run({TensorShape{N, 6, 5, C}}, opt)
  340. .run({TensorShape{N, 10, 9, C}}, opt);
  341. }
  342. TEST(TestOprImgproc, ResizeForward) {
  343. constexpr size_t N = 2, C = 3;
  344. opr::Resize::Param param;
  345. using Checker = AutoOprChecker<1, 1>;
  346. TensorShape out_shp{N, 9, 10, C};
  347. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  348. return {opr::Resize::make(
  349. inputs[0], TensorShape{out_shp.shape[1], out_shp.shape[2]}, param)};
  350. };
  351. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  352. auto opr = megdnn_naive_handle()->create_operator<megdnn::Resize>();
  353. opr->param() = param;
  354. dest[0].resize(out_shp);
  355. opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {});
  356. };
  357. Checker::RunOptions opt;
  358. Checker(make_graph, fwd, CompNode::load("cpu1"))
  359. .set_output_allow_grad(0, false)
  360. .run({TensorShape{N, 4, 5, C}}, opt)
  361. .run({TensorShape{N, 6, 5, C}}, opt)
  362. .run({TensorShape{N, 10, 9, C}}, opt);
  363. }
  364. TEST(TestOprImgproc, ResizeForward_NCHW) {
  365. constexpr size_t N = 2, C = 8;
  366. opr::Resize::Param param;
  367. using Checker = AutoOprChecker<1, 1>;
  368. TensorShape out_shp{N, C, 9, 10};
  369. param.format = opr::Resize::Param::Format::NCHW;
  370. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  371. return {opr::Resize::make(
  372. inputs[0], TensorShape{out_shp.shape[2], out_shp.shape[3]}, param)};
  373. };
  374. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  375. auto opr = megdnn_naive_handle()->create_operator<megdnn::Resize>();
  376. opr->param() = param;
  377. dest[0].resize(out_shp);
  378. opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {});
  379. };
  380. Checker::RunOptions opt;
  381. Checker(make_graph, fwd)
  382. .run({TensorShape{N, C, 4, 5}}, opt)
  383. .run({TensorShape{N, C, 6, 5}}, opt)
  384. .run({TensorShape{N, C, 10, 9}}, opt);
  385. }
  386. TEST(TestOprImgproc, ResizeForward_NCHW_NonContiguous) {
  387. opr::Resize::Param param;
  388. param.format = opr::Resize::Param::Format::NCHW;
  389. using Checker = AutoOprChecker<1, 2>;
  390. SymbolVar sub, sub_rev, input;
  391. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  392. input = inputs[0];
  393. auto graph = input.node()->owner_graph();
  394. auto cn = input.node()->comp_node();
  395. auto tshp = SymbolVar::make_scalar(3, *graph, cn).broadcast({2});
  396. auto zero = SymbolVar::make_scalar(0, *graph, cn);
  397. auto one = SymbolVar::make_scalar(1, *graph, cn);
  398. auto minus_one = SymbolVar::make_scalar(-1, *graph, cn);
  399. auto src_h = opr::GetVarShape::make(input, 2);
  400. auto src_w = opr::GetVarShape::make(input, 3);
  401. sub = opr::Subtensor::make(
  402. input,
  403. {opr::Subtensor::AxisIndexer::make_interval(2, one, src_h - 1, None),
  404. opr::Subtensor::AxisIndexer::make_interval(3, one, src_w - 1, None)});
  405. sub_rev = opr::Subtensor::make(
  406. input, {opr::Subtensor::AxisIndexer::make_interval(
  407. 2, src_h - 2, zero, minus_one),
  408. opr::Subtensor::AxisIndexer::make_interval(
  409. 3, src_w - 2, zero, minus_one)});
  410. auto dst = opr::Resize::make(sub, tshp, param);
  411. auto dst_rev = opr::Resize::make(sub_rev, tshp, param);
  412. return {dst, dst_rev};
  413. };
  414. auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray in) {
  415. auto cn = in[0]->comp_node();
  416. TensorShape in_shp = in[0]->shape();
  417. TensorShape sub_shp{2, 3, in_shp[2] - 2, in_shp[3] - 2};
  418. auto sub = std::make_shared<HostTensorND>(cn, sub_shp);
  419. auto sub_rev = std::make_shared<HostTensorND>(cn, sub_shp);
  420. float* in_ptr = in[0]->ptr<float>();
  421. const ptrdiff_t* in_stride = in[0]->layout().stride;
  422. float* sub_ptr = sub->ptr<float>();
  423. float* sub_rev_ptr = sub_rev->ptr<float>();
  424. // get subtensor manually and make it contiguous
  425. for (size_t n = 0; n < sub_shp[0]; ++n)
  426. for (size_t c = 0; c < sub_shp[1]; ++c)
  427. for (size_t h = 0; h < sub_shp[2]; ++h)
  428. for (size_t w = 0; w < sub_shp[3]; ++w) {
  429. *(sub_ptr++) =
  430. in_ptr[n * in_stride[0] + c * in_stride[1] +
  431. (h + 1) * in_stride[2] + (w + 1) * in_stride[3]];
  432. *(sub_rev_ptr++) =
  433. in_ptr[n * in_stride[0] + c * in_stride[1] +
  434. (in_shp[2] - 2 - h) * in_stride[2] +
  435. (in_shp[3] - 2 - w) * in_stride[3]];
  436. }
  437. auto opr = megdnn_naive_handle()->create_operator<megdnn::Resize>();
  438. opr->param() = param;
  439. out[0].resize({2, 3, 3, 3});
  440. out[1].resize({2, 3, 3, 3});
  441. opr->exec(sub->as_megdnn(), out[0].as_megdnn(), {});
  442. opr->exec(sub_rev->as_megdnn(), out[1].as_megdnn(), {});
  443. };
  444. Checker checker(make_graph, fwd);
  445. checker.disable_grad_check().disable_graph_opt();
  446. auto test = [&](TensorShape&& shape) {
  447. checker.run({shape});
  448. auto inp_dev_ptr = static_cast<const float*>(prev_dev_ptr(input));
  449. ASSERT_EQ(
  450. inp_dev_ptr + shape[3] + 1,
  451. static_cast<const float*>(prev_dev_ptr(sub)));
  452. ASSERT_EQ(
  453. inp_dev_ptr + (shape[2] - 1) * shape[3] - 2,
  454. static_cast<const float*>(prev_dev_ptr(sub_rev)));
  455. };
  456. test(TensorShape{2, 3, 4, 4});
  457. test(TensorShape{2, 3, 5, 5});
  458. test(TensorShape{2, 3, 6, 7});
  459. }
  460. TEST(TestOprImgproc, ResizeForward_NCHW4) {
  461. constexpr size_t N = 2, C = 8;
  462. opr::Resize::Param param;
  463. using Checker = AutoOprChecker<1, 1>;
  464. TensorShape out_shp{N, C / 4, 9, 10, 4};
  465. param.format = opr::Resize::Param::Format::NCHW4;
  466. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  467. auto x = opr::TypeCvt::make(inputs[0], dtype::QuantizedS8(0.01f));
  468. auto y = opr::Resize::make(
  469. x, TensorShape{out_shp.shape[2], out_shp.shape[3]}, param);
  470. return {opr::TypeCvt::make(y, dtype::Float32())};
  471. };
  472. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  473. auto opr = megdnn_naive_handle()->create_operator<megdnn::Resize>();
  474. auto typecvt = megdnn_naive_handle()->create_operator<megdnn::TypeCvt>();
  475. HostTensorND host_x{
  476. CompNode::load("xpux"), inp[0]->shape(), dtype::QuantizedS8(0.01f)};
  477. HostTensorND host_y{CompNode::load("xpux"), out_shp, dtype::QuantizedS8(0.01f)};
  478. typecvt->exec(inp[0]->as_megdnn(), host_x.as_megdnn());
  479. opr->param() = param;
  480. opr->exec(host_x.as_megdnn(), host_y.as_megdnn(), {});
  481. dest[0].resize(out_shp);
  482. typecvt->exec(host_y.as_megdnn(), dest[0].as_megdnn());
  483. };
  484. Checker::RunOptions opt;
  485. opt.outputs_max_err = 2e-2;
  486. Checker(make_graph, fwd)
  487. .disable_grad_check()
  488. .run({TensorShape{N, C / 4, 4, 5, 4}}, opt)
  489. .run({TensorShape{N, C / 4, 6, 5, 4}}, opt)
  490. .run({TensorShape{N, C / 4, 10, 9, 4}}, opt);
  491. }
  492. TEST(TestOprImgproc, ResizeBackward) {
  493. opr::Resize::Param param;
  494. param.format = opr::Resize::Param::Format::NCHW;
  495. opr::test::BackwardChecker<opr::ResizeForward, 2> backward_checker(
  496. {{10, 8, 8, 4}, {10, 8, 4, 8}}, param, 1e-1, 1e-2);
  497. }
  498. TEST(TestOprImgproc, WarpAffineForward) {
  499. constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3;
  500. opr::WarpAffine::Param param;
  501. using Checker = AutoOprChecker<2, 1>;
  502. TensorShape out_shp{N, 9, 10, C};
  503. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  504. return {opr::WarpAffine::make(
  505. inputs[0], inputs[1], TensorShape{out_shp.shape[1], out_shp.shape[2]},
  506. param)};
  507. };
  508. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  509. auto opr = megdnn_naive_handle()->create_operator<megdnn::WarpAffine>();
  510. opr->param() = param;
  511. dest[0].resize(out_shp);
  512. opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {});
  513. };
  514. std::mt19937 rng(next_rand_seed());
  515. auto rand_real = [&](double lo, double hi) {
  516. return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo;
  517. };
  518. auto rand_real2 = [&](double range) { return rand_real(-range, range); };
  519. auto gen_mat = [&](HostTensorND& mat) {
  520. auto ptr = mat.ptr<float>();
  521. for (size_t i = 0; i < N; ++i) {
  522. auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2),
  523. dy = rand_real2(INP_H * 0.5), dx = rand_real2(INP_W * 0.5);
  524. ptr[0] = cos(rot) * scale;
  525. ptr[1] = -(sin(rot) * scale);
  526. ptr[2] = dx;
  527. ptr[3] = sin(rot) * scale;
  528. ptr[4] = cos(rot) * scale;
  529. ptr[5] = dy;
  530. ptr += 6;
  531. }
  532. mgb_assert(ptr == mat.ptr<float>() + mat.shape().total_nr_elems());
  533. };
  534. auto dump_mat = [&](const Checker::NumInpArray& inp) -> std::string {
  535. std::ostringstream ostr;
  536. ostr << std::setprecision(3);
  537. auto&& mat = *inp[1];
  538. mgb_assert(mat.shape().ndim == 3);
  539. auto ptr = mat.ptr<float>();
  540. for (size_t n = 0; n < mat.shape().shape[0]; ++n) {
  541. ostr << "mat " << n << ":\n";
  542. for (size_t i = 0; i < 2; ++i) {
  543. for (size_t j = 0; j < 3; ++j) {
  544. ostr << std::setw(10) << *(ptr++);
  545. }
  546. ostr << '\n';
  547. }
  548. }
  549. return ostr.str();
  550. };
  551. Checker::RunOptions opt;
  552. opt.outputs_max_err = 0.08;
  553. Checker(make_graph, fwd, CompNode::load("cpu1"))
  554. .set_input_generator(1, gen_mat)
  555. .set_output_allow_grad(0, false)
  556. .set_input_dump_on_error(dump_mat)
  557. .run({TensorShape{N, 4, 5, C}, {N, 2, 3}}, opt)
  558. .run({TensorShape{N, 6, 5, C}, {N, 2, 3}}, opt)
  559. .run({TensorShape{N, 10, 9, C}, {N, 2, 3}}, opt);
  560. }
  561. TEST(TestOprImgproc, Remap_NCHW) {
  562. constexpr size_t N = 2, C = 8, OH = 10, OW = 10;
  563. opr::Remap::Param param;
  564. using Checker = AutoOprChecker<2, 1>;
  565. TensorShape out_shp{N, C, OH, OW};
  566. param.format = opr::Remap::Param::Format::NCHW;
  567. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  568. return {opr::Remap::make(inputs[0], inputs[1], param)};
  569. };
  570. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  571. auto opr = megdnn_naive_handle()->create_operator<megdnn::Remap>();
  572. opr->param() = param;
  573. dest[0].resize(out_shp);
  574. opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {});
  575. };
  576. std::mt19937 rng(next_rand_seed());
  577. auto rand_real = [&](double lo, double hi) {
  578. auto real = rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo;
  579. if (std::abs(std::round(real) - real) <= 1e-2)
  580. return real + 1e-1;
  581. return real;
  582. };
  583. auto rand_real2 = [&](double range) { return rand_real(-range, range); };
  584. auto gen_mat = [&](HostTensorND& mat) {
  585. auto ptr = mat.ptr<float>();
  586. for (size_t i = 0; i < N; ++i) {
  587. for (size_t j = 0; j < OH * OW * 2; j++) {
  588. //! undifferentiable when map is an integer
  589. ptr[j] = static_cast<float>(rand_real2(20));
  590. }
  591. ptr += OH * OW * 2;
  592. }
  593. mgb_assert(ptr == mat.ptr<float>() + mat.shape().total_nr_elems());
  594. };
  595. Checker::RunOptions opt;
  596. Checker(make_graph, fwd, CompNode::load("cpu1"))
  597. .set_input_generator(1, gen_mat)
  598. .run({TensorShape{N, C, 3, 20}, TensorShape{N, OH, OW, 2}}, opt)
  599. .run({TensorShape{N, C, 6, 5}, TensorShape{N, OH, OW, 2}}, opt)
  600. .run({TensorShape{N, C, 20, 20}, TensorShape{N, OH, OW, 2}}, opt);
  601. }
  602. TEST(TestOprImgproc, Remap_NHWC) {
  603. constexpr size_t N = 2, C = 8;
  604. opr::Remap::Param param;
  605. using Checker = AutoOprChecker<2, 1>;
  606. TensorShape out_shp{N, 10, 10, C};
  607. param.format = opr::Remap::Param::Format::NHWC;
  608. auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
  609. return {opr::Remap::make(inputs[0], inputs[1], param)};
  610. };
  611. auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) {
  612. auto opr = megdnn_naive_handle()->create_operator<megdnn::Remap>();
  613. opr->param() = param;
  614. dest[0].resize(out_shp);
  615. opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {});
  616. };
  617. Checker::RunOptions opt;
  618. Checker(make_graph, fwd, CompNode::load("cpu1"))
  619. .disable_grad_check()
  620. .run({TensorShape{N, 3, 20, C}, TensorShape{N, 10, 10, 2}}, opt)
  621. .run({TensorShape{N, 6, 5, C}, TensorShape{N, 10, 10, 2}}, opt)
  622. .run({TensorShape{N, 20, 20, C}, TensorShape{N, 10, 10, 2}}, opt);
  623. }
  624. TEST(TestOprImgproc, DCT) {
  625. REQUIRE_GPU(1);
  626. using Checker3 = AutoOprChecker<3, 1>;
  627. using Checker1 = AutoOprChecker<1, 1>;
  628. opr::DctChannelSelectForward::Param param;
  629. opr::DctChannelSelectForward::Param param_nchw4;
  630. param_nchw4.format = opr::DctChannelSelectForward::Param::Format::NCHW4;
  631. auto make_graph3 =
  632. [&](const Checker3::SymInpArray& inputs) -> Checker3::SymOutArray {
  633. return {opr::DctChannelSelectForward::make(
  634. inputs[0], inputs[1], inputs[2], param)};
  635. };
  636. auto fwd3 = [&](Checker3::NumOutArray& dest, Checker3::NumInpArray inp) {
  637. auto opr = megdnn_naive_handle()
  638. ->create_operator<megdnn::DctChannelSelectForward>();
  639. auto& in_shape = inp[0]->shape();
  640. TensorShape out_shp{
  641. in_shape[0], in_shape[1] * 64, in_shape[2] / 8, in_shape[3] / 8};
  642. dest[0].comp_node(inp[0]->comp_node()).resize(out_shp);
  643. opr->param() = param;
  644. opr->exec(
  645. inp[0]->as_megdnn(), inp[1]->as_megdnn(), inp[2]->as_megdnn(),
  646. dest[0].as_megdnn(), {});
  647. };
  648. auto make_graph1 =
  649. [&](const Checker1::SymInpArray& inputs) -> Checker1::SymOutArray {
  650. return {opr::DctChannelSelectForward::make(inputs[0], param)};
  651. };
  652. auto make_graph1_s8 =
  653. [&](const Checker1::SymInpArray& inputs) -> Checker1::SymOutArray {
  654. return {opr::DctChannelSelectForward::make(
  655. inputs[0], param_nchw4, OperatorNodeConfig(dtype::QuantizedS8(10.f)))};
  656. };
  657. auto fwd1 = [&](Checker1::NumOutArray& dest, Checker1::NumInpArray inp) {
  658. auto opr = megdnn_naive_handle()
  659. ->create_operator<megdnn::DctChannelSelectForward>();
  660. auto& in_shape = inp[0]->shape();
  661. TensorShape out_shp{
  662. in_shape[0], in_shape[1] * 64, in_shape[2] / 8, in_shape[3] / 8};
  663. dest[0].comp_node(inp[0]->comp_node()).resize(out_shp);
  664. opr->param() = param;
  665. opr->exec(inp[0]->as_megdnn(), {}, {}, dest[0].as_megdnn(), {});
  666. };
  667. auto fwd1_s8 = [&](Checker1::NumOutArray& dest, Checker1::NumInpArray inp) {
  668. auto opr = megdnn_naive_handle()
  669. ->create_operator<megdnn::DctChannelSelectForward>();
  670. auto& in_shape = inp[0]->shape();
  671. TensorShape out_shp{
  672. in_shape[0], in_shape[1] * 64 / 4, in_shape[2] / 8, in_shape[3] / 8, 4};
  673. dest[0].comp_node(inp[0]->comp_node()).resize(out_shp);
  674. opr->param() = param_nchw4;
  675. opr->exec(inp[0]->as_megdnn(), {}, {}, dest[0].as_megdnn(), {});
  676. };
  677. Checker3::RunOptions opt3;
  678. Checker1::RunOptions opt1;
  679. Checker1::RunOptions opt1_qint8;
  680. opt3.outputs_max_err = 1e-3;
  681. opt1.outputs_max_err = 1e-3;
  682. opt1_qint8.outputs_max_err = 1.001;
  683. auto gen_input = [](HostTensorND& dest) {
  684. HostTensorGenerator<dtype::Uint8, RandomDistribution::UNIFORM> mask_generator{
  685. 0, 255};
  686. dest = *mask_generator(dest.shape(), dest.comp_node());
  687. };
  688. auto gen_mask = [](HostTensorND& dest) {
  689. HostTensorGenerator<dtype::Int32, RandomDistribution::UNIFORM> mask_generator{
  690. 0, 8};
  691. dest = *mask_generator(dest.shape(), dest.comp_node());
  692. };
  693. Checker1(make_graph1, fwd1, CompNode::load("gpu0"))
  694. .disable_grad_check()
  695. .set_input_generator(0, gen_input)
  696. .set_input_dtype(0, dtype::Uint8())
  697. .run({TensorShape{1, 1, 16, 16}}, opt1)
  698. .run({TensorShape{1, 3, 256, 256}}, opt1)
  699. .run({TensorShape{4, 3, 512, 512}}, opt1);
  700. Checker1(make_graph1_s8, fwd1_s8, CompNode::load("gpu0"))
  701. .disable_grad_check()
  702. .set_input_generator(0, gen_input)
  703. .set_input_dtype(0, dtype::Uint8())
  704. .run({TensorShape{1, 1, 16, 16}}, opt1_qint8)
  705. .run({TensorShape{1, 3, 256, 256}}, opt1_qint8)
  706. .run({TensorShape{4, 3, 512, 512}}, opt1_qint8);
  707. MGB_MARK_USED_VAR(make_graph3);
  708. MGB_MARK_USED_VAR(fwd3);
  709. MGB_MARK_USED_VAR(gen_mask);
  710. }
  711. TEST(TestOprImgproc, DCT_BAD_MASK) {
  712. HostTensorGenerator<dtype::Uint8> gen_u8;
  713. HostTensorGenerator<dtype::Int32> gen_s32;
  714. TensorShape src_shape({1, 2, 256, 256}), mask_offset_shape({3}),
  715. mask_val_shape({8});
  716. opr::DctChannelSelectForward::Param param;
  717. auto graph = ComputingGraph::make();
  718. auto src_tensor = gen_u8(src_shape);
  719. auto mask_offset_tensor = gen_s32(mask_offset_shape);
  720. auto mask_val_tensor = gen_s32(mask_val_shape);
  721. auto mask_offset_ptr = mask_offset_tensor->ptr<int32_t>();
  722. auto mask_val_ptr = mask_val_tensor->ptr<int32_t>();
  723. mask_offset_ptr[0] = 1;
  724. mask_val_ptr[0] = 64;
  725. auto src_sym = opr::ImmutableTensor::make(*graph, *src_tensor);
  726. auto mask_offset_sym = opr::ImmutableTensor::make(*graph, *mask_offset_tensor);
  727. auto mask_val_sym = opr::ImmutableTensor::make(*graph, *mask_val_tensor);
  728. ASSERT_THROW(
  729. opr::DctChannelSelect::make(src_sym, mask_offset_sym, mask_val_sym, param),
  730. MegBrainError);
  731. mask_offset_ptr[0] = 0;
  732. mask_offset_ptr[1] = 2;
  733. mask_offset_ptr[2] = 8;
  734. mask_offset_sym = opr::ImmutableTensor::make(*graph, *mask_offset_tensor);
  735. ASSERT_THROW(
  736. opr::DctChannelSelect::make(src_sym, mask_offset_sym, mask_val_sym, param),
  737. MegBrainError);
  738. mask_val_ptr[0] = 0;
  739. mask_val_ptr[1] = 1;
  740. mask_val_ptr[2] = 2;
  741. mask_val_ptr[3] = 3;
  742. mask_val_ptr[4] = 4;
  743. mask_val_ptr[5] = 5;
  744. mask_val_ptr[6] = 6;
  745. mask_val_ptr[7] = 7;
  746. mask_val_sym = opr::ImmutableTensor::make(*graph, *mask_val_tensor);
  747. opr::DctChannelSelect::make(src_sym, mask_offset_sym, mask_val_sym, param);
  748. param.format = opr::DctChannelSelect::Param::Format::NCHW4;
  749. ASSERT_THROW(
  750. opr::DctChannelSelect::make(src_sym, mask_offset_sym, mask_val_sym, param),
  751. MegBrainError);
  752. }
  753. // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}