test_images.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // This file is part of OpenCV project.
  2. // It is subject to the license terms in the LICENSE file found in the top-level directory
  3. // of this distribution and at http://opencv.org/license.html.
  4. #include "test_precomp.hpp"
  5. #include "opencv2/core/utils/filesystem.hpp"
  6. #include "opencv2/imgcodecs.hpp"
  7. #include "opencv2/videoio/utils.private.hpp"
  8. using namespace std;
  9. namespace opencv_test { namespace {
  10. struct ImageCollection
  11. {
  12. string dirname;
  13. string base;
  14. string ext;
  15. size_t first_idx;
  16. size_t last_idx;
  17. size_t width;
  18. public:
  19. ImageCollection(const char *dirname_template = "opencv_test_images")
  20. : first_idx(0), last_idx(0), width(0)
  21. {
  22. dirname = cv::tempfile(dirname_template);
  23. cv::utils::fs::createDirectory(dirname);
  24. }
  25. ~ImageCollection()
  26. {
  27. cleanup();
  28. }
  29. void cleanup()
  30. {
  31. cv::utils::fs::remove_all(dirname);
  32. }
  33. void generate(size_t count, size_t first = 0, size_t width_ = 4, const string & base_ = "test", const string & ext_ = "png")
  34. {
  35. base = base_;
  36. ext = ext_;
  37. first_idx = first;
  38. last_idx = first + count - 1;
  39. width = width_;
  40. for (size_t idx = first_idx; idx <= last_idx; ++idx)
  41. {
  42. const string filename = getFilename(idx);
  43. imwrite(filename, getFrame(idx));
  44. }
  45. }
  46. string getFilename(size_t idx = 0) const
  47. {
  48. ostringstream buf;
  49. buf << dirname << "/" << base << setw(width) << setfill('0') << idx << "." << ext;
  50. return buf.str();
  51. }
  52. string getPatternFilename() const
  53. {
  54. ostringstream buf;
  55. buf << dirname << "/" << base << "%0" << width << "d" << "." << ext;
  56. return buf.str();
  57. }
  58. string getFirstFilename() const
  59. {
  60. return getFilename(first_idx);
  61. }
  62. Mat getFirstFrame() const
  63. {
  64. return getFrame(first_idx);
  65. }
  66. size_t getCount() const
  67. {
  68. return last_idx - first_idx + 1;
  69. }
  70. string getDirname() const
  71. {
  72. return dirname;
  73. }
  74. static Mat getFrame(size_t idx)
  75. {
  76. const int sz = 100; // 100x100 or bigger
  77. Mat res(sz, sz, CV_8UC3, Scalar::all(0));
  78. circle(res, Point(idx % 100), idx % 50, Scalar::all(255), 2, LINE_8);
  79. return res;
  80. }
  81. };
  82. //==================================================================================================
  83. TEST(videoio_images, basic_read)
  84. {
  85. ImageCollection col;
  86. col.generate(20);
  87. VideoCapture cap(col.getFirstFilename(), CAP_IMAGES);
  88. ASSERT_TRUE(cap.isOpened());
  89. size_t idx = 0;
  90. while (cap.isOpened()) // TODO: isOpened is always true, even if there are no more images
  91. {
  92. Mat img;
  93. const bool read_res = cap.read(img);
  94. if (!read_res)
  95. break;
  96. EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0);
  97. ++idx;
  98. }
  99. EXPECT_EQ(col.getCount(), idx);
  100. }
  101. TEST(videoio_images, basic_write)
  102. {
  103. // writer should create files: test0000.png, ... test0019.png
  104. ImageCollection col;
  105. col.generate(1);
  106. VideoWriter wri(col.getFirstFilename(), CAP_IMAGES, 0, 0, col.getFrame(0).size());
  107. ASSERT_TRUE(wri.isOpened());
  108. size_t idx = 0;
  109. while (wri.isOpened())
  110. {
  111. wri << col.getFrame(idx);
  112. Mat actual = imread(col.getFilename(idx));
  113. EXPECT_MAT_N_DIFF(col.getFrame(idx), actual, 0);
  114. if (++idx >= 20)
  115. break;
  116. }
  117. wri.release();
  118. ASSERT_FALSE(wri.isOpened());
  119. }
  120. TEST(videoio_images, bad)
  121. {
  122. ImageCollection col;
  123. {
  124. ostringstream buf; buf << col.getDirname() << "/missing0000.png";
  125. VideoCapture cap(buf.str(), CAP_IMAGES);
  126. EXPECT_FALSE(cap.isOpened());
  127. Mat img;
  128. EXPECT_FALSE(cap.read(img));
  129. }
  130. }
  131. TEST(videoio_images, seek)
  132. {
  133. // check files: test0005.png, ..., test0024.png
  134. // seek to valid and invalid frame numbers
  135. // position is zero-based: valid frame numbers are 0, ..., 19
  136. const int count = 20;
  137. ImageCollection col;
  138. col.generate(count, 5);
  139. VideoCapture cap(col.getFirstFilename(), CAP_IMAGES);
  140. ASSERT_TRUE(cap.isOpened());
  141. EXPECT_EQ((size_t)count, (size_t)cap.get(CAP_PROP_FRAME_COUNT));
  142. vector<int> positions { count / 2, 0, 1, count - 1, count, count + 100, -1, -100 };
  143. for (const auto &pos : positions)
  144. {
  145. Mat img;
  146. const bool res = cap.set(CAP_PROP_POS_FRAMES, pos);
  147. if (pos >= count || pos < 0) // invalid position
  148. {
  149. // EXPECT_FALSE(res); // TODO: backend clamps invalid value to valid range, actual result is 'true'
  150. }
  151. else
  152. {
  153. EXPECT_TRUE(res);
  154. EXPECT_GE(1., cap.get(CAP_PROP_POS_AVI_RATIO));
  155. EXPECT_NEAR((double)pos / (count - 1), cap.get(CAP_PROP_POS_AVI_RATIO), 1e-2);
  156. EXPECT_EQ(pos, static_cast<decltype(pos)>(cap.get(CAP_PROP_POS_FRAMES)));
  157. EXPECT_TRUE(cap.read(img));
  158. EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx + pos), 0);
  159. }
  160. }
  161. }
  162. TEST(videoio_images, pattern_overflow)
  163. {
  164. // check files: test0.png, ..., test11.png
  165. ImageCollection col;
  166. col.generate(12, 0, 1);
  167. {
  168. VideoCapture cap(col.getFirstFilename(), CAP_IMAGES);
  169. ASSERT_TRUE(cap.isOpened());
  170. for (size_t idx = col.first_idx; idx <= col.last_idx; ++idx)
  171. {
  172. Mat img;
  173. EXPECT_TRUE(cap.read(img));
  174. EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0);
  175. }
  176. }
  177. {
  178. VideoCapture cap(col.getPatternFilename(), CAP_IMAGES);
  179. ASSERT_TRUE(cap.isOpened());
  180. for (size_t idx = col.first_idx; idx <= col.last_idx; ++idx)
  181. {
  182. Mat img;
  183. EXPECT_TRUE(cap.read(img));
  184. EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0);
  185. }
  186. }
  187. }
  188. TEST(videoio_images, pattern_max)
  189. {
  190. // max supported number width for starting image is 9 digits
  191. // but following images can be read as well
  192. // test999999999.png ; test1000000000.png
  193. ImageCollection col;
  194. col.generate(2, 1000000000 - 1);
  195. {
  196. VideoCapture cap(col.getFirstFilename(), CAP_IMAGES);
  197. ASSERT_TRUE(cap.isOpened());
  198. Mat img;
  199. EXPECT_TRUE(cap.read(img));
  200. EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx), 0);
  201. EXPECT_TRUE(cap.read(img));
  202. EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx + 1), 0);
  203. }
  204. {
  205. VideoWriter wri(col.getFirstFilename(), CAP_IMAGES, 0, 0, col.getFirstFrame().size());
  206. ASSERT_TRUE(wri.isOpened());
  207. Mat img = col.getFrame(0);
  208. wri.write(img);
  209. wri.write(img);
  210. Mat actual;
  211. actual = imread(col.getFilename(col.first_idx));
  212. EXPECT_MAT_N_DIFF(actual, img, 0);
  213. actual = imread(col.getFilename(col.first_idx));
  214. EXPECT_MAT_N_DIFF(actual, img, 0);
  215. }
  216. }
  217. TEST(videoio_images, extract_pattern)
  218. {
  219. unsigned offset = 0;
  220. // Min and max values
  221. EXPECT_EQ("%01d.png", cv::icvExtractPattern("0.png", &offset));
  222. EXPECT_EQ(0u, offset);
  223. EXPECT_EQ("%09d.png", cv::icvExtractPattern("999999999.png", &offset));
  224. EXPECT_EQ(999999999u, offset);
  225. // Regular usage - start, end, middle
  226. EXPECT_EQ("abc%04ddef.png", cv::icvExtractPattern("abc0048def.png", &offset));
  227. EXPECT_EQ(48u, offset);
  228. EXPECT_EQ("%05dabcdef.png", cv::icvExtractPattern("00049abcdef.png", &offset));
  229. EXPECT_EQ(49u, offset);
  230. EXPECT_EQ("abcdef%06d.png", cv::icvExtractPattern("abcdef000050.png", &offset));
  231. EXPECT_EQ(50u, offset);
  232. // Minus handling (should not handle)
  233. EXPECT_EQ("abcdef-%01d.png", cv::icvExtractPattern("abcdef-8.png", &offset));
  234. EXPECT_EQ(8u, offset);
  235. // Two numbers (should select first)
  236. // TODO: shouldn't it be last number?
  237. EXPECT_EQ("%01d-abcdef-8.png", cv::icvExtractPattern("7-abcdef-8.png", &offset));
  238. EXPECT_EQ(7u, offset);
  239. // Paths (should select filename)
  240. EXPECT_EQ("images005/abcdef%03d.png", cv::icvExtractPattern("images005/abcdef006.png", &offset));
  241. EXPECT_EQ(6u, offset);
  242. // TODO: fix
  243. // EXPECT_EQ("images03\\abcdef%02d.png", cv::icvExtractPattern("images03\\abcdef04.png", &offset));
  244. // EXPECT_EQ(4, offset);
  245. EXPECT_EQ("/home/user/test/0/3348/../../3442/./0/1/3/4/5/14304324234/%01d.png",
  246. cv::icvExtractPattern("/home/user/test/0/3348/../../3442/./0/1/3/4/5/14304324234/2.png", &offset));
  247. EXPECT_EQ(2u, offset);
  248. // Patterns '%0?[0-9][du]'
  249. EXPECT_EQ("test%d.png", cv::icvExtractPattern("test%d.png", &offset));
  250. EXPECT_EQ(0u, offset);
  251. EXPECT_EQ("test%0d.png", cv::icvExtractPattern("test%0d.png", &offset));
  252. EXPECT_EQ(0u, offset);
  253. EXPECT_EQ("test%09d.png", cv::icvExtractPattern("test%09d.png", &offset));
  254. EXPECT_EQ(0u, offset);
  255. EXPECT_EQ("test%5u.png", cv::icvExtractPattern("test%5u.png", &offset));
  256. EXPECT_EQ(0u, offset);
  257. // Invalid arguments
  258. EXPECT_THROW(cv::icvExtractPattern(string(), &offset), cv::Exception);
  259. // TODO: fix?
  260. // EXPECT_EQ(0u, offset);
  261. EXPECT_THROW(cv::icvExtractPattern("test%010d.png", &offset), cv::Exception);
  262. EXPECT_EQ(0u, offset);
  263. EXPECT_THROW(cv::icvExtractPattern("1000000000.png", &offset), cv::Exception);
  264. EXPECT_EQ(0u, offset);
  265. EXPECT_THROW(cv::icvExtractPattern("1.png", NULL), cv::Exception);
  266. }
  267. TEST(videoio_images, bug_26457)
  268. {
  269. ImageCollection col;
  270. col.generate(1u);
  271. ASSERT_EQ(col.getCount(), 1u);
  272. VideoCapture cap(col.getFirstFilename(), CAP_IMAGES);
  273. ASSERT_TRUE(cap.isOpened());
  274. Mat img;
  275. const bool read_res = cap.read(img);
  276. EXPECT_TRUE(read_res);
  277. EXPECT_MAT_N_DIFF(img, col.getFirstFrame(), 0);
  278. }
  279. // TODO: should writer overwrite files?
  280. // TODO: is clamping good for seeking?
  281. // TODO: missing files? E.g. 3, 4, 6, 7, 8 (should it finish OR jump over OR return empty frame?)
  282. // TODO: non-numbered files (https://github.com/opencv/opencv/pull/23815)
  283. // TODO: when opening with pattern (e.g. test%01d.png), first frame can be only 0 (test0.png)
  284. }} // opencv_test::<anonymous>::