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.

image_utils.cc 37 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. /**
  2. * Copyright 2019 Huawei Technologies Co., Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "dataset/kernels/image/image_utils.h"
  17. #include <opencv2/imgproc/types_c.h>
  18. #include <algorithm>
  19. #include <vector>
  20. #include <stdexcept>
  21. #include <utility>
  22. #include <opencv2/imgcodecs.hpp>
  23. #include "common/utils.h"
  24. #include "dataset/core/constants.h"
  25. #include "dataset/core/cv_tensor.h"
  26. #include "dataset/core/tensor.h"
  27. #include "dataset/core/tensor_shape.h"
  28. #include "dataset/util/random.h"
  29. #define MAX_INT_PRECISION 16777216 // float int precision is 16777216
  30. namespace mindspore {
  31. namespace dataset {
  32. int GetCVInterpolationMode(InterpolationMode mode) {
  33. switch (mode) {
  34. case InterpolationMode::kLinear:
  35. return static_cast<int>(cv::InterpolationFlags::INTER_LINEAR);
  36. case InterpolationMode::kCubic:
  37. return static_cast<int>(cv::InterpolationFlags::INTER_CUBIC);
  38. case InterpolationMode::kArea:
  39. return static_cast<int>(cv::InterpolationFlags::INTER_AREA);
  40. case InterpolationMode::kNearestNeighbour:
  41. return static_cast<int>(cv::InterpolationFlags::INTER_NEAREST);
  42. default:
  43. return static_cast<int>(cv::InterpolationFlags::INTER_LINEAR);
  44. }
  45. }
  46. int GetCVBorderType(BorderType type) {
  47. switch (type) {
  48. case BorderType::kConstant:
  49. return static_cast<int>(cv::BorderTypes::BORDER_CONSTANT);
  50. case BorderType::kEdge:
  51. return static_cast<int>(cv::BorderTypes::BORDER_REPLICATE);
  52. case BorderType::kReflect:
  53. return static_cast<int>(cv::BorderTypes::BORDER_REFLECT101);
  54. case BorderType::kSymmetric:
  55. return static_cast<int>(cv::BorderTypes::BORDER_REFLECT);
  56. default:
  57. return static_cast<int>(cv::BorderTypes::BORDER_CONSTANT);
  58. }
  59. }
  60. Status Flip(std::shared_ptr<Tensor> input, std::shared_ptr<Tensor> *output, int flip_code) {
  61. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(std::move(input));
  62. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  63. RETURN_UNEXPECTED_IF_NULL(output_cv);
  64. RETURN_IF_NOT_OK(output_cv->AllocateBuffer(output_cv->SizeInBytes()));
  65. if (input_cv->mat().data) {
  66. try {
  67. cv::flip(input_cv->mat(), output_cv->mat(), flip_code);
  68. *output = std::static_pointer_cast<Tensor>(output_cv);
  69. return Status::OK();
  70. } catch (const cv::Exception &e) {
  71. RETURN_STATUS_UNEXPECTED("Error in flip op.");
  72. }
  73. } else {
  74. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor, the input data is null");
  75. }
  76. }
  77. Status HorizontalFlip(std::shared_ptr<Tensor> input, std::shared_ptr<Tensor> *output) {
  78. return Flip(std::move(input), output, 1);
  79. }
  80. Status VerticalFlip(std::shared_ptr<Tensor> input, std::shared_ptr<Tensor> *output) {
  81. return Flip(std::move(input), output, 0);
  82. }
  83. Status Resize(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, int32_t output_height,
  84. int32_t output_width, double fx, double fy, InterpolationMode mode) {
  85. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  86. if (!input_cv->mat().data) {
  87. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  88. }
  89. if (input_cv->Rank() != 3 && input_cv->Rank() != 2) {
  90. RETURN_STATUS_UNEXPECTED("Input Tensor is not in shape of <H,W,C> or <H,W>");
  91. }
  92. cv::Mat in_image = input_cv->mat();
  93. // resize image too large or too small
  94. if (output_height == 0 || output_height > in_image.rows * 1000 || output_width == 0 ||
  95. output_width > in_image.cols * 1000) {
  96. std::string err_msg =
  97. "The resizing width or height 1) is too big, it's up to "
  98. "1000 times the original image; 2) can not be 0.";
  99. return Status(StatusCode::kShapeMisMatch, err_msg);
  100. }
  101. try {
  102. TensorShape shape{output_height, output_width};
  103. if (input_cv->Rank() == 3) shape = shape.AppendDim(input_cv->shape()[2]);
  104. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(shape, input_cv->type());
  105. RETURN_UNEXPECTED_IF_NULL(output_cv);
  106. auto cv_mode = GetCVInterpolationMode(mode);
  107. cv::resize(in_image, output_cv->mat(), cv::Size(output_width, output_height), fx, fy, cv_mode);
  108. *output = std::static_pointer_cast<Tensor>(output_cv);
  109. return Status::OK();
  110. } catch (const cv::Exception &e) {
  111. RETURN_STATUS_UNEXPECTED("Error in image resize.");
  112. }
  113. }
  114. bool HasJpegMagic(const std::shared_ptr<Tensor> &input) {
  115. const unsigned char *kJpegMagic = (unsigned char *)"\xFF\xD8\xFF";
  116. constexpr size_t kJpegMagicLen = 3;
  117. return input->SizeInBytes() >= kJpegMagicLen && memcmp(input->GetBuffer(), kJpegMagic, kJpegMagicLen) == 0;
  118. }
  119. Status Decode(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output) {
  120. if (HasJpegMagic(input)) {
  121. return JpegCropAndDecode(input, output);
  122. } else {
  123. return DecodeCv(input, output);
  124. }
  125. }
  126. Status DecodeCv(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output) {
  127. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  128. if (!input_cv->mat().data) {
  129. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  130. }
  131. try {
  132. cv::Mat img_mat = cv::imdecode(input_cv->mat(), cv::IMREAD_COLOR | cv::IMREAD_IGNORE_ORIENTATION);
  133. if (img_mat.data == nullptr) {
  134. std::string err = "Error in decoding\t";
  135. RETURN_STATUS_UNEXPECTED(err);
  136. }
  137. cv::cvtColor(img_mat, img_mat, static_cast<int>(cv::COLOR_BGR2RGB));
  138. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(img_mat);
  139. RETURN_UNEXPECTED_IF_NULL(output_cv);
  140. *output = std::static_pointer_cast<Tensor>(output_cv);
  141. return Status::OK();
  142. } catch (const cv::Exception &e) {
  143. RETURN_STATUS_UNEXPECTED("Error in image Decode");
  144. }
  145. }
  146. static void JpegInitSource(j_decompress_ptr cinfo) {}
  147. static boolean JpegFillInputBuffer(j_decompress_ptr cinfo) {
  148. if (cinfo->src->bytes_in_buffer == 0) {
  149. ERREXIT(cinfo, JERR_INPUT_EMPTY);
  150. return FALSE;
  151. }
  152. return TRUE;
  153. }
  154. static void JpegTermSource(j_decompress_ptr cinfo) {}
  155. static void JpegSkipInputData(j_decompress_ptr cinfo, int64_t jump) {
  156. if (jump < 0) {
  157. return;
  158. }
  159. if (static_cast<size_t>(jump) > cinfo->src->bytes_in_buffer) {
  160. cinfo->src->bytes_in_buffer = 0;
  161. return;
  162. } else {
  163. cinfo->src->bytes_in_buffer -= jump;
  164. cinfo->src->next_input_byte += jump;
  165. }
  166. }
  167. void JpegSetSource(j_decompress_ptr cinfo, const void *data, int64_t datasize) {
  168. cinfo->src = static_cast<struct jpeg_source_mgr *>(
  169. (*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(struct jpeg_source_mgr)));
  170. cinfo->src->init_source = JpegInitSource;
  171. cinfo->src->fill_input_buffer = JpegFillInputBuffer;
  172. #if defined(_WIN32) || defined(_WIN64)
  173. cinfo->src->skip_input_data = reinterpret_cast<void (*)(j_decompress_ptr, long)>(JpegSkipInputData);
  174. #else
  175. cinfo->src->skip_input_data = JpegSkipInputData;
  176. #endif
  177. cinfo->src->resync_to_restart = jpeg_resync_to_restart;
  178. cinfo->src->term_source = JpegTermSource;
  179. cinfo->src->bytes_in_buffer = datasize;
  180. cinfo->src->next_input_byte = static_cast<const JOCTET *>(data);
  181. }
  182. static Status JpegReadScanlines(jpeg_decompress_struct *const cinfo, int max_scanlines_to_read, JSAMPLE *buffer,
  183. int buffer_size, int crop_w, int crop_w_aligned, int offset, int stride) {
  184. // scanlines will be read to this buffer first, must have the number
  185. // of components equal to the number of components in the image
  186. int64_t scanline_size = crop_w_aligned * cinfo->output_components;
  187. std::vector<JSAMPLE> scanline(scanline_size);
  188. JSAMPLE *scanline_ptr = &scanline[0];
  189. while (cinfo->output_scanline < static_cast<unsigned int>(max_scanlines_to_read)) {
  190. int num_lines_read = jpeg_read_scanlines(cinfo, &scanline_ptr, 1);
  191. if (cinfo->out_color_space == JCS_CMYK && num_lines_read > 0) {
  192. for (int i = 0; i < crop_w; ++i) {
  193. int cmyk_pixel = 4 * i + offset;
  194. const int c = scanline_ptr[cmyk_pixel];
  195. const int m = scanline_ptr[cmyk_pixel + 1];
  196. const int y = scanline_ptr[cmyk_pixel + 2];
  197. const int k = scanline_ptr[cmyk_pixel + 3];
  198. int r, g, b;
  199. if (cinfo->saw_Adobe_marker) {
  200. r = (k * c) / 255;
  201. g = (k * m) / 255;
  202. b = (k * y) / 255;
  203. } else {
  204. r = (255 - c) * (255 - k) / 255;
  205. g = (255 - m) * (255 - k) / 255;
  206. b = (255 - y) * (255 - k) / 255;
  207. }
  208. buffer[3 * i + 0] = r;
  209. buffer[3 * i + 1] = g;
  210. buffer[3 * i + 2] = b;
  211. }
  212. } else if (num_lines_read > 0) {
  213. int copy_status = memcpy_s(buffer, buffer_size, scanline_ptr + offset, stride);
  214. if (copy_status != 0) {
  215. jpeg_destroy_decompress(cinfo);
  216. RETURN_STATUS_UNEXPECTED("memcpy failed");
  217. }
  218. } else {
  219. jpeg_destroy_decompress(cinfo);
  220. std::string err_msg = "failed to read scanline";
  221. RETURN_STATUS_UNEXPECTED(err_msg);
  222. }
  223. buffer += stride;
  224. buffer_size = buffer_size - stride;
  225. }
  226. return Status::OK();
  227. }
  228. static Status JpegSetColorSpace(jpeg_decompress_struct *cinfo) {
  229. switch (cinfo->num_components) {
  230. case 1:
  231. // we want to output 3 components if it's grayscale
  232. cinfo->out_color_space = JCS_RGB;
  233. return Status::OK();
  234. case 3:
  235. cinfo->out_color_space = JCS_RGB;
  236. return Status::OK();
  237. case 4:
  238. // Need to manually convert to RGB
  239. cinfo->out_color_space = JCS_CMYK;
  240. return Status::OK();
  241. default:
  242. jpeg_destroy_decompress(cinfo);
  243. std::string err_msg = "wrong number of components";
  244. RETURN_STATUS_UNEXPECTED(err_msg);
  245. }
  246. }
  247. void JpegErrorExitCustom(j_common_ptr cinfo) {
  248. char jpeg_last_error_msg[JMSG_LENGTH_MAX];
  249. (*(cinfo->err->format_message))(cinfo, jpeg_last_error_msg);
  250. throw std::runtime_error(jpeg_last_error_msg);
  251. }
  252. Status JpegCropAndDecode(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, int crop_x, int crop_y,
  253. int crop_w, int crop_h) {
  254. struct jpeg_decompress_struct cinfo;
  255. auto DestroyDecompressAndReturnError = [&cinfo](const std::string &err) {
  256. jpeg_destroy_decompress(&cinfo);
  257. RETURN_STATUS_UNEXPECTED(err);
  258. };
  259. struct JpegErrorManagerCustom jerr;
  260. cinfo.err = jpeg_std_error(&jerr.pub);
  261. jerr.pub.error_exit = JpegErrorExitCustom;
  262. try {
  263. jpeg_create_decompress(&cinfo);
  264. JpegSetSource(&cinfo, input->GetBuffer(), input->SizeInBytes());
  265. (void)jpeg_read_header(&cinfo, TRUE);
  266. RETURN_IF_NOT_OK(JpegSetColorSpace(&cinfo));
  267. jpeg_calc_output_dimensions(&cinfo);
  268. } catch (std::runtime_error &e) {
  269. return DestroyDecompressAndReturnError(e.what());
  270. }
  271. if (crop_x == 0 && crop_y == 0 && crop_w == 0 && crop_h == 0) {
  272. crop_w = cinfo.output_width;
  273. crop_h = cinfo.output_height;
  274. } else if (crop_w == 0 || static_cast<unsigned int>(crop_w + crop_x) > cinfo.output_width || crop_h == 0 ||
  275. static_cast<unsigned int>(crop_h + crop_y) > cinfo.output_height) {
  276. return DestroyDecompressAndReturnError("Crop window is not valid");
  277. }
  278. const int mcu_size = cinfo.min_DCT_scaled_size;
  279. unsigned int crop_x_aligned = (crop_x / mcu_size) * mcu_size;
  280. unsigned int crop_w_aligned = crop_w + crop_x - crop_x_aligned;
  281. try {
  282. (void)jpeg_start_decompress(&cinfo);
  283. jpeg_crop_scanline(&cinfo, &crop_x_aligned, &crop_w_aligned);
  284. } catch (std::runtime_error &e) {
  285. return DestroyDecompressAndReturnError(e.what());
  286. }
  287. JDIMENSION skipped_scanlines = jpeg_skip_scanlines(&cinfo, crop_y);
  288. // three number of output components, always convert to RGB and output
  289. constexpr int kOutNumComponents = 3;
  290. TensorShape ts = TensorShape({crop_h, crop_w, kOutNumComponents});
  291. auto output_tensor = std::make_shared<Tensor>(ts, DataType(DataType::DE_UINT8));
  292. const int buffer_size = output_tensor->SizeInBytes();
  293. JSAMPLE *buffer = static_cast<JSAMPLE *>(reinterpret_cast<uchar *>(&(*output_tensor->begin<uint8_t>())));
  294. const int max_scanlines_to_read = skipped_scanlines + crop_h;
  295. // stride refers to output tensor, which has 3 components at most
  296. const int stride = crop_w * kOutNumComponents;
  297. // offset is calculated for scanlines read from the image, therefore
  298. // has the same number of components as the image
  299. const int offset = (crop_x - crop_x_aligned) * cinfo.output_components;
  300. RETURN_IF_NOT_OK(
  301. JpegReadScanlines(&cinfo, max_scanlines_to_read, buffer, buffer_size, crop_w, crop_w_aligned, offset, stride));
  302. *output = output_tensor;
  303. jpeg_destroy_decompress(&cinfo);
  304. return Status::OK();
  305. }
  306. Status Rescale(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, float rescale, float shift) {
  307. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  308. if (!input_cv->mat().data) {
  309. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  310. }
  311. cv::Mat input_image = input_cv->mat();
  312. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(input_cv->shape(), DataType(DataType::DE_FLOAT32));
  313. RETURN_UNEXPECTED_IF_NULL(output_cv);
  314. try {
  315. input_image.convertTo(output_cv->mat(), CV_32F, rescale, shift);
  316. *output = std::static_pointer_cast<Tensor>(output_cv);
  317. } catch (const cv::Exception &e) {
  318. RETURN_STATUS_UNEXPECTED("Error in image rescale");
  319. }
  320. return Status::OK();
  321. }
  322. Status Crop(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, int x, int y, int w, int h) {
  323. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  324. if (!input_cv->mat().data) {
  325. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  326. }
  327. if (input_cv->Rank() != 3 && input_cv->Rank() != 2) {
  328. RETURN_STATUS_UNEXPECTED("Shape not <H,W,C> or <H,W>");
  329. }
  330. try {
  331. TensorShape shape{h, w};
  332. if (input_cv->Rank() == 3) shape = shape.AppendDim(input_cv->shape()[2]);
  333. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(shape, input_cv->type());
  334. RETURN_UNEXPECTED_IF_NULL(output_cv);
  335. cv::Rect roi(x, y, w, h);
  336. (input_cv->mat())(roi).copyTo(output_cv->mat());
  337. *output = std::static_pointer_cast<Tensor>(output_cv);
  338. return Status::OK();
  339. } catch (const cv::Exception &e) {
  340. RETURN_STATUS_UNEXPECTED("Unexpected error in crop.");
  341. }
  342. }
  343. Status HwcToChw(std::shared_ptr<Tensor> input, std::shared_ptr<Tensor> *output) {
  344. try {
  345. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  346. if (!input_cv->mat().data) {
  347. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  348. }
  349. if (input_cv->Rank() == 2) {
  350. // If input tensor is 2D, we assume we have hw dimensions
  351. *output = input;
  352. return Status::OK();
  353. }
  354. if (input_cv->shape().Size() < 2 || input_cv->shape().Size() > 3 ||
  355. (input_cv->shape().Size() == 3 && input_cv->shape()[2] != 3 && input_cv->shape()[2] != 1)) {
  356. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3 nor 1");
  357. }
  358. cv::Mat output_img;
  359. int height = input_cv->shape()[0];
  360. int width = input_cv->shape()[1];
  361. int num_channels = input_cv->shape()[2];
  362. auto output_cv = std::make_unique<CVTensor>(TensorShape{num_channels, height, width}, input_cv->type());
  363. for (int i = 0; i < num_channels; ++i) {
  364. cv::Mat mat;
  365. RETURN_IF_NOT_OK(output_cv->Mat({i}, &mat));
  366. cv::extractChannel(input_cv->mat(), mat, i);
  367. }
  368. *output = std::move(output_cv);
  369. return Status::OK();
  370. } catch (const cv::Exception &e) {
  371. RETURN_STATUS_UNEXPECTED("Unexpected error in ChannelSwap.");
  372. }
  373. }
  374. Status SwapRedAndBlue(std::shared_ptr<Tensor> input, std::shared_ptr<Tensor> *output) {
  375. try {
  376. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(std::move(input));
  377. if (input_cv->shape().Size() != 3 || input_cv->shape()[2] != 3) {
  378. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3");
  379. }
  380. auto output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  381. RETURN_UNEXPECTED_IF_NULL(output_cv);
  382. cv::cvtColor(input_cv->mat(), output_cv->mat(), static_cast<int>(cv::COLOR_BGR2RGB));
  383. *output = std::static_pointer_cast<Tensor>(output_cv);
  384. return Status::OK();
  385. } catch (const cv::Exception &e) {
  386. RETURN_STATUS_UNEXPECTED("Unexpected error in ChangeMode.");
  387. }
  388. }
  389. Status CropAndResize(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, int x, int y,
  390. int crop_height, int crop_width, int target_height, int target_width, InterpolationMode mode) {
  391. try {
  392. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  393. if (!input_cv->mat().data) {
  394. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  395. }
  396. if (input_cv->Rank() != 3 && input_cv->Rank() != 2) {
  397. RETURN_STATUS_UNEXPECTED("Shape not <H,W,C> or <H,W>");
  398. }
  399. // image too large or too small
  400. if (crop_height == 0 || crop_width == 0 || target_height == 0 || target_height > crop_height * 1000 ||
  401. target_width == 0 || target_height > crop_width * 1000) {
  402. std::string err_msg =
  403. "The resizing width or height 1) is too big, it's up to "
  404. "1000 times the original image; 2) can not be 0.";
  405. RETURN_STATUS_UNEXPECTED(err_msg);
  406. }
  407. cv::Rect roi(x, y, crop_width, crop_height);
  408. auto cv_mode = GetCVInterpolationMode(mode);
  409. cv::Mat cv_in = input_cv->mat();
  410. TensorShape shape{target_height, target_width};
  411. if (input_cv->Rank() == 3) shape = shape.AppendDim(input_cv->shape()[2]);
  412. std::shared_ptr<CVTensor> cvt_out = std::make_shared<CVTensor>(shape, input_cv->type());
  413. RETURN_UNEXPECTED_IF_NULL(cvt_out);
  414. cv::resize(cv_in(roi), cvt_out->mat(), cv::Size(target_width, target_height), 0, 0, cv_mode);
  415. *output = std::static_pointer_cast<Tensor>(cvt_out);
  416. return Status::OK();
  417. } catch (const cv::Exception &e) {
  418. RETURN_STATUS_UNEXPECTED("Unexpected error in CropAndResize.");
  419. }
  420. }
  421. Status Rotate(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, float fx, float fy, float degree,
  422. InterpolationMode interpolation, bool expand, uint8_t fill_r, uint8_t fill_g, uint8_t fill_b) {
  423. try {
  424. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  425. if (!input_cv->mat().data) {
  426. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  427. }
  428. cv::Mat input_img = input_cv->mat();
  429. if (input_img.cols > (MAX_INT_PRECISION * 2) || input_img.rows > (MAX_INT_PRECISION * 2)) {
  430. RETURN_STATUS_UNEXPECTED("Image too large center not precise");
  431. }
  432. // default to center of image
  433. if (fx == -1 && fy == -1) {
  434. fx = (input_img.cols - 1) / 2.0;
  435. fy = (input_img.rows - 1) / 2.0;
  436. }
  437. cv::Mat output_img;
  438. cv::Scalar fill_color = cv::Scalar(fill_b, fill_g, fill_r);
  439. // maybe don't use uint32 for image dimension here
  440. cv::Point2f pc(fx, fy);
  441. cv::Mat rot = cv::getRotationMatrix2D(pc, degree, 1.0);
  442. std::shared_ptr<CVTensor> output_cv;
  443. if (!expand) {
  444. // this case means that the shape doesn't change, size stays the same
  445. // We may not need this memcpy if it is in place.
  446. output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  447. RETURN_UNEXPECTED_IF_NULL(output_cv);
  448. // using inter_nearest to comply with python default
  449. cv::warpAffine(input_img, output_cv->mat(), rot, input_img.size(), GetCVInterpolationMode(interpolation),
  450. cv::BORDER_CONSTANT, fill_color);
  451. } else {
  452. // we resize here since the shape changes
  453. // create a new bounding box with the rotate
  454. cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), input_img.size(), degree).boundingRect2f();
  455. rot.at<double>(0, 2) += bbox.width / 2.0 - input_img.cols / 2.0;
  456. rot.at<double>(1, 2) += bbox.height / 2.0 - input_img.rows / 2.0;
  457. // use memcpy and don't compute the new shape since openCV has a rounding problem
  458. cv::warpAffine(input_img, output_img, rot, bbox.size(), GetCVInterpolationMode(interpolation),
  459. cv::BORDER_CONSTANT, fill_color);
  460. output_cv = std::make_shared<CVTensor>(output_img);
  461. RETURN_UNEXPECTED_IF_NULL(output_cv);
  462. }
  463. *output = std::static_pointer_cast<Tensor>(output_cv);
  464. } catch (const cv::Exception &e) {
  465. RETURN_STATUS_UNEXPECTED("Error in image rotation");
  466. }
  467. return Status::OK();
  468. }
  469. Status Normalize(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output,
  470. const std::shared_ptr<Tensor> &mean, const std::shared_ptr<Tensor> &std) {
  471. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  472. if (!(input_cv->mat().data && input_cv->Rank() == 3)) {
  473. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  474. }
  475. cv::Mat in_image = input_cv->mat();
  476. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(input_cv->shape(), DataType(DataType::DE_FLOAT32));
  477. RETURN_UNEXPECTED_IF_NULL(output_cv);
  478. mean->Squeeze();
  479. if (mean->type() != DataType::DE_FLOAT32 || mean->Rank() != 1 || mean->shape()[0] != 3) {
  480. std::string err_msg = "Mean tensor should be of size 3 and type float.";
  481. return Status(StatusCode::kShapeMisMatch, err_msg);
  482. }
  483. std->Squeeze();
  484. if (std->type() != DataType::DE_FLOAT32 || std->Rank() != 1 || std->shape()[0] != 3) {
  485. std::string err_msg = "Std tensor should be of size 3 and type float.";
  486. return Status(StatusCode::kShapeMisMatch, err_msg);
  487. }
  488. try {
  489. // NOTE: We are assuming the input image is in RGB and the mean
  490. // and std are in RGB
  491. cv::Mat rgb[3];
  492. cv::split(in_image, rgb);
  493. for (uint8_t i = 0; i < 3; i++) {
  494. float mean_c, std_c;
  495. RETURN_IF_NOT_OK(mean->GetItemAt<float>(&mean_c, {i}));
  496. RETURN_IF_NOT_OK(std->GetItemAt<float>(&std_c, {i}));
  497. rgb[i].convertTo(rgb[i], CV_32F, 1.0 / std_c, (-mean_c / std_c));
  498. }
  499. cv::merge(rgb, 3, output_cv->mat());
  500. *output = std::static_pointer_cast<Tensor>(output_cv);
  501. return Status::OK();
  502. } catch (const cv::Exception &e) {
  503. RETURN_STATUS_UNEXPECTED("Unexpected error in Normalize");
  504. }
  505. }
  506. Status AdjustBrightness(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const float &alpha) {
  507. try {
  508. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  509. cv::Mat input_img = input_cv->mat();
  510. if (!input_cv->mat().data) {
  511. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  512. }
  513. if (input_cv->Rank() != 3 || input_cv->shape()[2] != 3) {
  514. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3");
  515. }
  516. auto output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  517. RETURN_UNEXPECTED_IF_NULL(output_cv);
  518. output_cv->mat() = input_img * alpha;
  519. *output = std::static_pointer_cast<Tensor>(output_cv);
  520. } catch (const cv::Exception &e) {
  521. RETURN_STATUS_UNEXPECTED("Error in adjust brightness");
  522. }
  523. return Status::OK();
  524. }
  525. Status AdjustContrast(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const float &alpha) {
  526. try {
  527. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  528. cv::Mat input_img = input_cv->mat();
  529. if (!input_cv->mat().data) {
  530. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  531. }
  532. if (input_cv->Rank() != 3 || input_cv->shape()[2] != 3) {
  533. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3");
  534. }
  535. cv::Mat gray, output_img;
  536. cv::cvtColor(input_img, gray, CV_RGB2GRAY);
  537. int mean_img = static_cast<int>(cv::mean(gray).val[0] + 0.5);
  538. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  539. RETURN_UNEXPECTED_IF_NULL(output_cv);
  540. output_img = cv::Mat::zeros(input_img.rows, input_img.cols, CV_8UC1);
  541. output_img = output_img + mean_img;
  542. cv::cvtColor(output_img, output_img, CV_GRAY2RGB);
  543. output_cv->mat() = output_img * (1.0 - alpha) + input_img * alpha;
  544. *output = std::static_pointer_cast<Tensor>(output_cv);
  545. } catch (const cv::Exception &e) {
  546. RETURN_STATUS_UNEXPECTED("Error in adjust contrast");
  547. }
  548. return Status::OK();
  549. }
  550. Status AdjustSaturation(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const float &alpha) {
  551. try {
  552. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  553. cv::Mat input_img = input_cv->mat();
  554. if (!input_cv->mat().data) {
  555. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  556. }
  557. if (input_cv->Rank() != 3 || input_cv->shape()[2] != 3) {
  558. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3");
  559. }
  560. auto output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  561. RETURN_UNEXPECTED_IF_NULL(output_cv);
  562. cv::Mat output_img = output_cv->mat();
  563. cv::Mat gray;
  564. cv::cvtColor(input_img, gray, CV_RGB2GRAY);
  565. cv::cvtColor(gray, output_img, CV_GRAY2RGB);
  566. output_cv->mat() = output_img * (1.0 - alpha) + input_img * alpha;
  567. *output = std::static_pointer_cast<Tensor>(output_cv);
  568. } catch (const cv::Exception &e) {
  569. RETURN_STATUS_UNEXPECTED("Error in adjust saturation");
  570. }
  571. return Status::OK();
  572. }
  573. Status AdjustHue(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const float &hue) {
  574. if (hue > 0.5 || hue < -0.5) {
  575. MS_LOG(ERROR) << "Hue factor is not in [-0.5, 0.5].";
  576. RETURN_STATUS_UNEXPECTED("hue_factor is not in [-0.5, 0.5].");
  577. }
  578. try {
  579. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  580. cv::Mat input_img = input_cv->mat();
  581. if (!input_cv->mat().data) {
  582. RETURN_STATUS_UNEXPECTED("Could not convert to CV Tensor");
  583. }
  584. if (input_cv->Rank() != 3 || input_cv->shape()[2] != 3) {
  585. RETURN_STATUS_UNEXPECTED("The shape is incorrect: number of channels does not equal 3");
  586. }
  587. auto output_cv = std::make_shared<CVTensor>(input_cv->shape(), input_cv->type());
  588. RETURN_UNEXPECTED_IF_NULL(output_cv);
  589. cv::Mat output_img;
  590. cv::cvtColor(input_img, output_img, CV_RGB2HSV_FULL);
  591. for (int y = 0; y < output_img.cols; y++) {
  592. for (int x = 0; x < output_img.rows; x++) {
  593. uint8_t cur1 = output_img.at<cv::Vec3b>(cv::Point(y, x))[0];
  594. uint8_t h_hue = 0;
  595. h_hue = static_cast<uint8_t>(hue * 255);
  596. cur1 += h_hue;
  597. output_img.at<cv::Vec3b>(cv::Point(y, x))[0] = cur1;
  598. }
  599. }
  600. cv::cvtColor(output_img, output_cv->mat(), CV_HSV2RGB_FULL);
  601. *output = std::static_pointer_cast<Tensor>(output_cv);
  602. } catch (const cv::Exception &e) {
  603. RETURN_STATUS_UNEXPECTED("Error in adjust hue");
  604. }
  605. return Status::OK();
  606. }
  607. Status Erase(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, int32_t box_height,
  608. int32_t box_width, int32_t num_patches, bool bounded, bool random_color, std::mt19937 *rnd, uint8_t fill_r,
  609. uint8_t fill_g, uint8_t fill_b) {
  610. try {
  611. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  612. if (input_cv->mat().data == nullptr || input_cv->Rank() != 3 || input_cv->shape()[2] != 3) {
  613. RETURN_STATUS_UNEXPECTED("bad CV Tensor input for erase");
  614. }
  615. cv::Mat input_img = input_cv->mat();
  616. int32_t image_h = input_cv->shape()[0];
  617. int32_t image_w = input_cv->shape()[1];
  618. // check if erase size is bigger than image itself
  619. if (box_height > image_h || box_width > image_w) {
  620. RETURN_STATUS_UNEXPECTED("input box size too large for image erase");
  621. }
  622. // for random color
  623. std::normal_distribution<double> normal_distribution(0, 1);
  624. std::uniform_int_distribution<int> height_distribution_bound(0, image_h - box_height);
  625. std::uniform_int_distribution<int> width_distribution_bound(0, image_w - box_width);
  626. std::uniform_int_distribution<int> height_distribution_unbound(0, image_h + box_height);
  627. std::uniform_int_distribution<int> width_distribution_unbound(0, image_w + box_width);
  628. // core logic
  629. // update values based on random erasing or cutout
  630. for (int32_t i = 0; i < num_patches; i++) {
  631. // rows in cv mat refers to the height of the cropped box
  632. // we determine h_start and w_start using two different distributions as erasing is used by two different
  633. // image augmentations. The bounds are also different in each case.
  634. int32_t h_start = (bounded) ? height_distribution_bound(*rnd) : (height_distribution_unbound(*rnd) - box_height);
  635. int32_t w_start = (bounded) ? width_distribution_bound(*rnd) : (width_distribution_unbound(*rnd) - box_width);
  636. int32_t max_width = (w_start + box_width > image_w) ? image_w : w_start + box_width;
  637. int32_t max_height = (h_start + box_height > image_h) ? image_h : h_start + box_height;
  638. // check for starting range >= 0, here the start range is checked after for cut out, for random erasing
  639. // w_start and h_start will never be less than 0.
  640. h_start = (h_start < 0) ? 0 : h_start;
  641. w_start = (w_start < 0) ? 0 : w_start;
  642. for (int y = w_start; y < max_width; y++) {
  643. for (int x = h_start; x < max_height; x++) {
  644. if (random_color) {
  645. // fill each box with a random value
  646. input_img.at<cv::Vec3b>(cv::Point(y, x))[0] = static_cast<int32_t>(normal_distribution(*rnd));
  647. input_img.at<cv::Vec3b>(cv::Point(y, x))[1] = static_cast<int32_t>(normal_distribution(*rnd));
  648. input_img.at<cv::Vec3b>(cv::Point(y, x))[2] = static_cast<int32_t>(normal_distribution(*rnd));
  649. } else {
  650. input_img.at<cv::Vec3b>(cv::Point(y, x))[0] = fill_r;
  651. input_img.at<cv::Vec3b>(cv::Point(y, x))[1] = fill_g;
  652. input_img.at<cv::Vec3b>(cv::Point(y, x))[2] = fill_b;
  653. }
  654. }
  655. }
  656. }
  657. *output = std::static_pointer_cast<Tensor>(input);
  658. return Status::OK();
  659. } catch (const cv::Exception &e) {
  660. RETURN_STATUS_UNEXPECTED("Error in erasing");
  661. }
  662. }
  663. Status Pad(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const int32_t &pad_top,
  664. const int32_t &pad_bottom, const int32_t &pad_left, const int32_t &pad_right, const BorderType &border_types,
  665. uint8_t fill_r, uint8_t fill_g, uint8_t fill_b) {
  666. try {
  667. // input image
  668. std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
  669. // get the border type in openCV
  670. auto b_type = GetCVBorderType(border_types);
  671. // output image
  672. cv::Mat out_image;
  673. if (b_type == cv::BORDER_CONSTANT) {
  674. cv::Scalar fill_color = cv::Scalar(fill_b, fill_g, fill_r);
  675. cv::copyMakeBorder(input_cv->mat(), out_image, pad_top, pad_bottom, pad_left, pad_right, b_type, fill_color);
  676. } else {
  677. cv::copyMakeBorder(input_cv->mat(), out_image, pad_top, pad_bottom, pad_left, pad_right, b_type);
  678. }
  679. std::shared_ptr<CVTensor> output_cv = std::make_shared<CVTensor>(out_image);
  680. RETURN_UNEXPECTED_IF_NULL(output_cv);
  681. // pad the dimension if shape information is only 2 dimensional, this is grayscale
  682. if (input_cv->Rank() == 3 && input_cv->shape()[2] == 1 && output_cv->Rank() == 2) output_cv->ExpandDim(2);
  683. *output = std::static_pointer_cast<Tensor>(output_cv);
  684. return Status::OK();
  685. } catch (const cv::Exception &e) {
  686. RETURN_STATUS_UNEXPECTED("Unexpected error in pad");
  687. }
  688. }
  689. // -------- BBOX OPERATIONS -------- //
  690. Status UpdateBBoxesForCrop(std::shared_ptr<Tensor> *bboxList, size_t *bboxCount, int CB_Xmin, int CB_Ymin, int CB_Xmax,
  691. int CB_Ymax) {
  692. // PASS LIST, COUNT OF BOUNDING BOXES
  693. // Also PAss X/Y Min/Max of image cropped region - normally obtained from 'GetCropBox' functions
  694. uint32_t bb_Xmin_t, bb_Ymin_t, bb_Xmax_t, bb_Ymax_t;
  695. std::vector<int> correct_ind;
  696. std::vector<uint32_t> copyVals;
  697. dsize_t bboxDim = (*bboxList)->shape()[1];
  698. bool retFlag = false; // true unless overlap found
  699. for (int i = 0; i < *bboxCount; i++) {
  700. int bb_Xmin, bb_Xmax, bb_Ymin, bb_Ymax;
  701. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&bb_Xmin_t, {i, 0}));
  702. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&bb_Ymin_t, {i, 1}));
  703. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&bb_Xmax_t, {i, 2}));
  704. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&bb_Ymax_t, {i, 3}));
  705. bb_Xmin = bb_Xmin_t;
  706. bb_Ymin = bb_Ymin_t;
  707. bb_Xmax = bb_Xmax_t;
  708. bb_Ymax = bb_Ymax_t;
  709. bb_Xmax = bb_Xmin + bb_Xmax;
  710. bb_Ymax = bb_Ymin + bb_Ymax;
  711. // check for image / BB overlap
  712. if (((bb_Xmin > CB_Xmax) || (bb_Ymin > CB_Ymax)) || ((bb_Xmax < CB_Xmin) || (bb_Ymax < CB_Ymin))) {
  713. continue; // no overlap found
  714. }
  715. // Update this bbox and select it to move to the final output tensor
  716. correct_ind.push_back(i);
  717. // adjust BBox corners by bringing into new CropBox if beyond
  718. // Also reseting/adjusting for boxes to lie within CropBox instead of Image - subtract CropBox Xmin/YMin
  719. bb_Xmin = bb_Xmin - (std::min(0, (bb_Xmin - CB_Xmin)) + CB_Xmin);
  720. bb_Xmax = bb_Xmax - (std::max(0, (bb_Xmax - CB_Xmax)) + CB_Xmin);
  721. bb_Ymin = bb_Ymin - (std::min(0, (bb_Ymin - CB_Ymin)) + CB_Ymin);
  722. bb_Ymax = bb_Ymax - (std::max(0, (bb_Ymax - CB_Ymax)) + CB_Ymin);
  723. // reset min values and calculate width/height from Box corners
  724. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 0}, static_cast<uint32_t>(bb_Xmin)));
  725. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 1}, static_cast<uint32_t>(bb_Ymin)));
  726. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 2}, static_cast<uint32_t>(bb_Xmax - bb_Xmin)));
  727. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 3}, static_cast<uint32_t>(bb_Ymax - bb_Ymin)));
  728. }
  729. // create new tensor and copy over bboxes still valid to the image
  730. // bboxes outside of new cropped region are ignored - empty tensor returned in case of none
  731. *bboxCount = correct_ind.size();
  732. uint32_t temp;
  733. for (auto slice : correct_ind) { // for every index in the loop
  734. for (int ix = 0; ix < bboxDim; ix++) {
  735. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&temp, {slice, ix}));
  736. copyVals.push_back(temp);
  737. }
  738. }
  739. std::shared_ptr<Tensor> retV;
  740. RETURN_IF_NOT_OK(Tensor::CreateTensor(&retV, copyVals, TensorShape({static_cast<dsize_t>(*bboxCount), bboxDim})));
  741. (*bboxList) = retV; // reset pointer
  742. return Status::OK();
  743. }
  744. Status PadBBoxes(std::shared_ptr<Tensor> *bboxList, const size_t &bboxCount, int32_t pad_top, int32_t pad_left) {
  745. for (int i = 0; i < bboxCount; i++) {
  746. uint32_t xMin, yMin;
  747. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&xMin, {i, 0}));
  748. RETURN_IF_NOT_OK((*bboxList)->GetUnsignedIntAt(&yMin, {i, 1}));
  749. xMin += static_cast<uint32_t>(pad_left); // should not be negative
  750. yMin += static_cast<uint32_t>(pad_top);
  751. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 0}, xMin));
  752. RETURN_IF_NOT_OK((*bboxList)->SetItemAt({i, 1}, yMin));
  753. }
  754. return Status::OK();
  755. }
  756. Status UpdateBBoxesForResize(const std::shared_ptr<Tensor> &bboxList, const size_t &bboxCount, int32_t target_width_,
  757. int32_t target_height_, int orig_width, int orig_height) {
  758. uint32_t bb_Xmin, bb_Ymin, bb_Xwidth, bb_Ywidth;
  759. // cast to float to preseve fractional
  760. double W_aspRatio = (target_width_ * 1.0) / (orig_width * 1.0);
  761. double H_aspRatio = (target_height_ * 1.0) / (orig_height * 1.0);
  762. for (int i = 0; i < bboxCount; i++) {
  763. // for each bounding box
  764. RETURN_IF_NOT_OK(bboxList->GetUnsignedIntAt(&bb_Xmin, {i, 0}));
  765. RETURN_IF_NOT_OK(bboxList->GetUnsignedIntAt(&bb_Ymin, {i, 1}));
  766. RETURN_IF_NOT_OK(bboxList->GetUnsignedIntAt(&bb_Xwidth, {i, 2}));
  767. RETURN_IF_NOT_OK(bboxList->GetUnsignedIntAt(&bb_Ywidth, {i, 3}));
  768. // update positions and widths
  769. bb_Xmin = bb_Xmin * W_aspRatio;
  770. bb_Ymin = bb_Ymin * H_aspRatio;
  771. bb_Xwidth = bb_Xwidth * W_aspRatio;
  772. bb_Ywidth = bb_Ywidth * H_aspRatio;
  773. // reset bounding box values
  774. RETURN_IF_NOT_OK(bboxList->SetItemAt({i, 0}, bb_Xmin));
  775. RETURN_IF_NOT_OK(bboxList->SetItemAt({i, 1}, bb_Ymin));
  776. RETURN_IF_NOT_OK(bboxList->SetItemAt({i, 2}, bb_Xwidth));
  777. RETURN_IF_NOT_OK(bboxList->SetItemAt({i, 3}, bb_Ywidth));
  778. }
  779. return Status::OK();
  780. }
  781. } // namespace dataset
  782. } // namespace mindspore