image_process.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. #include "image_process.h"
  2. #include <glm/ext/matrix_transform.hpp>
  3. #include <glm/gtx/matrix_transform_2d.hpp>
  4. #include <opencv2/cudaarithm.hpp>
  5. #include <opencv2/cudaimgproc.hpp>
  6. #include <opencv2/cudawarping.hpp>
  7. #include <opencv2/imgcodecs.hpp>
  8. namespace {
  9. // TODO: hack OpenCV code to make it support construction from cudaStream_t
  10. thread_local std::optional<cv::cuda::Stream> cv_stream;
  11. auto &get_cv_stream() {
  12. current_cuda_stream(); // initialize CUDA
  13. if (!cv_stream) [[unlikely]] {
  14. cv_stream.emplace();
  15. }
  16. return *cv_stream;
  17. }
  18. }
  19. size_t normal_height_to_nv12(const size_t height) {
  20. assert(height % 2 == 0);
  21. return height / 2 * 3;
  22. }
  23. size_t nv12_height_to_normal(const size_t height) {
  24. assert(height % 3 == 0);
  25. return height / 3 * 2;
  26. }
  27. cv::Size normal_size_to_nv12(const cv::Size size) {
  28. return cv::Size(size.width, normal_height_to_nv12(size.height));
  29. }
  30. cv::Size nv12_size_to_normal(cv::Size size) {
  31. return cv::Size(size.width, nv12_height_to_normal(size.height));
  32. }
  33. sp_image nv12_luma_view(const sp_image &img) {
  34. assert(img.cv_type() == CV_8UC1);
  35. const auto luma_size = nv12_size_to_normal(img.cv_size());
  36. return img.sub_view(luma_size);
  37. }
  38. sp_image nv12_chrome_view(const sp_image &img) {
  39. assert(img.cv_type() == CV_8UC1);
  40. const auto chroma_size = cv::Size(img.width(), img.height() / 3);
  41. const auto img_chrome = img.sub_view(
  42. chroma_size, cv::Size(0, nv12_height_to_normal(img.height())));
  43. return img_chrome.cast_view(CV_8UC2);
  44. }
  45. cv_stream_guard::cv_stream_guard() {
  46. push_cuda_stream((cudaStream_t) get_cv_stream().cudaPtr());
  47. }
  48. cv_stream_guard::~cv_stream_guard() {
  49. pop_cuda_stream();
  50. }
  51. cv::cuda::Stream &cv_stream_guard::cv_stream() {
  52. return get_cv_stream();
  53. }
  54. namespace {
  55. struct image_opencv_cuda_helper {
  56. const sp_image *read;
  57. sp_image *write;
  58. using proxy_type = auto_memory_info::cuda_proxy;
  59. cv_stream_guard stream_guard;
  60. pair_access_helper<proxy_type, proxy_type> access_helper;
  61. image_opencv_cuda_helper(const sp_image &src, sp_image &dst)
  62. : read(&src), write(&dst),
  63. access_helper(read->cuda(), write->cuda()) { (void) 0; }
  64. [[nodiscard]] cv::cuda::GpuMat input() const {
  65. return read->cv_gpu_mat(access_helper.read_ptr());
  66. }
  67. [[nodiscard]] cv::cuda::GpuMat output() const {
  68. return write->cv_gpu_mat(access_helper.write_ptr());
  69. }
  70. };
  71. }
  72. sp_image image_debayer(const sp_image &img) {
  73. assert(img.cv_type() == CV_8UC1);
  74. auto ret = sp_image::create<uchar3>(img.cv_size());
  75. const auto helper = image_opencv_cuda_helper(img, ret);
  76. cv::cuda::cvtColor(helper.input(), helper.output(),
  77. cv::COLOR_BayerRG2BGR, 3, get_cv_stream());
  78. ret.merge_meta(img);
  79. return ret;
  80. }
  81. void image_resize(const sp_image &src, sp_image &dst) {
  82. assert(src.cv_type() == dst.cv_type());
  83. const auto helper = image_opencv_cuda_helper(src, dst);
  84. cv::cuda::resize(helper.input(), helper.output(),
  85. dst.cv_size(), 0, 0, cv::INTER_LINEAR, get_cv_stream());
  86. dst.merge_meta(src);
  87. }
  88. sp_image image_resize(const sp_image &img, const cv::Size size) {
  89. auto ret = sp_image::create(img.cv_type(), size);
  90. image_resize(img, ret);
  91. return ret;
  92. }
  93. sp_image image_flip_y(const sp_image &img) {
  94. auto ret = sp_image::create(img.cv_type(), img.cv_size());
  95. const auto helper = image_opencv_cuda_helper(img, ret);
  96. cv::cuda::flip(helper.input(), helper.output(), 1, get_cv_stream()); // flip vertically
  97. ret.merge_meta(img);
  98. return ret;
  99. }
  100. sp_image image_warp_affine(const sp_image &img, const glm::mat3 &matrix) {
  101. auto cv_matrix = cv::Mat(2, 3, CV_32FC1);
  102. for (auto i = 0; i < 3; ++i)
  103. for (auto j = 0; j < 2; ++j) {
  104. cv_matrix.at<float>(j, i) = matrix[i][j];
  105. }
  106. auto ret = sp_image::create_like(img);
  107. const auto helper = image_opencv_cuda_helper(img, ret);
  108. cv::cuda::warpAffine(helper.input(), helper.output(),
  109. cv_matrix, img.cv_size(), cv::INTER_LINEAR,
  110. cv::BORDER_CONSTANT, {},
  111. get_cv_stream());
  112. ret.merge_meta(img);
  113. return ret;
  114. }
  115. namespace {
  116. float pixel_center(const float size) {
  117. return 0.5f * size - 0.5f;
  118. }
  119. }
  120. sp_image image_rotate(const sp_image &img, const float angle,
  121. std::optional<glm::vec2> center) {
  122. if (!center) {
  123. center = glm::vec2(pixel_center(img.width()),
  124. pixel_center(img.height()));
  125. }
  126. auto matrix = glm::identity<glm::mat3>();
  127. matrix = glm::translate(matrix, -*center);
  128. matrix = glm::rotate(matrix, angle);
  129. matrix = glm::translate(matrix, *center);
  130. return image_warp_affine(img, matrix);
  131. }
  132. sp_image image_translate(const sp_image &img, const glm::vec2 offset) {
  133. const auto matrix = glm::translate(glm::identity<glm::mat3>(), offset);
  134. return image_warp_affine(img, matrix);
  135. }
  136. image_stereo_pair image_stereo_split(const sp_image &img) {
  137. assert(img.width() % 2 == 0);
  138. const auto mono_size = cv::Size(img.width() / 2, img.height());
  139. auto ret_left = sp_image::create(img.cv_type(), mono_size);
  140. const auto img_left = img.sub_view(mono_size);
  141. copy_sp_image(img_left, ret_left);
  142. auto ret_right = sp_image::create(img.cv_type(), mono_size);
  143. const auto img_right = img.sub_view(mono_size,
  144. cv::Size(img.width() / 2, 0));
  145. copy_sp_image(img_right, ret_right);
  146. return std::make_tuple(ret_left, ret_right);
  147. }
  148. image_stereo_pair image_stereo_split_view(const sp_image &img) {
  149. assert(img.width() % 2 == 0);
  150. const auto mono_size = cv::Size(img.width() / 2, img.height());
  151. const auto img_left = img.sub_view(mono_size);
  152. const auto img_right = img.sub_view(mono_size,
  153. cv::Size(img.width() / 2, 0));
  154. return std::make_tuple(img_left, img_right);
  155. }
  156. sp_image image_stereo_combine(const sp_image &left, const sp_image &right) {
  157. assert(left.cv_type() == right.cv_type());
  158. assert(left.shape_array() == right.shape_array());
  159. const auto stereo_shape = cv::Size(left.width() * 2, left.height());
  160. auto ret_img = sp_image::create(left.cv_type(), stereo_shape);
  161. auto [left_view, right_view] = image_stereo_split_view(ret_img);
  162. copy_sp_image(left, left_view);
  163. copy_sp_image(right, right_view);
  164. ret_img.merge_meta(left);
  165. ret_img.merge_meta(right);
  166. return ret_img;
  167. }
  168. #include "image_process/cuda_impl/pixel_convert.cuh"
  169. namespace {
  170. template<typename Input, typename Output>
  171. struct image_cuda_v2_helper {
  172. const sp_image *read;
  173. sp_image *write;
  174. using proxy_type = auto_memory_info::cuda_proxy;
  175. pair_access_helper<proxy_type, proxy_type> access_helper;
  176. image_cuda_v2_helper(const sp_image &src, sp_image &dst)
  177. : read(&src), write(&dst),
  178. access_helper(read->cuda(), write->cuda()) { (void) 0; }
  179. template<typename T = Input>
  180. image_type_v2<T> input() {
  181. return to_cuda_v2(read->as_ndarray<T>(access_helper.read_ptr()));
  182. }
  183. template<typename T = Output>
  184. image_type_v2<T> output() {
  185. return to_cuda_v2(write->as_ndarray<T>(access_helper.write_ptr()));
  186. }
  187. };
  188. }
  189. sp_image image_rgb_to_bgr(const sp_image &img) {
  190. assert(img.cv_type() == CV_8UC3);
  191. auto ret = sp_image::create(CV_8UC3, img.cv_size());
  192. auto helper = image_cuda_v2_helper<uchar3, uchar3>(img, ret);
  193. call_cvt_rgb_bgr_u8(helper.input(), helper.output(), current_cuda_stream());
  194. ret.merge_meta(img);
  195. return ret;
  196. }
  197. sp_image image_rgb_to_bgra(const sp_image &img) {
  198. assert(img.cv_type() == CV_8UC3);
  199. auto ret = sp_image::create(CV_8UC4, img.cv_size());
  200. auto helper = image_cuda_v2_helper<uchar3, uchar4>(img, ret);
  201. call_cvt_rgb_bgra_u8(helper.input(), helper.output(), current_cuda_stream());
  202. ret.merge_meta(img);
  203. return ret;
  204. }
  205. sp_image image_rgb_to_gray(const sp_image &img) {
  206. assert(img.cv_type() == CV_8UC3);
  207. auto ret = sp_image::create<uchar1>(img.cv_size());
  208. const auto helper = image_opencv_cuda_helper(img, ret);
  209. cv::cuda::cvtColor(helper.input(), helper.output(),
  210. cv::COLOR_RGB2GRAY, 1, get_cv_stream());
  211. ret.merge_meta(img);
  212. return ret;
  213. }
  214. sp_image image_rgb_to_nv12(const sp_image &img) {
  215. assert(img.cv_type() == CV_8UC3);
  216. auto ret = sp_image::create(CV_8UC1, normal_size_to_nv12(img.cv_size()));
  217. auto helper = image_cuda_v2_helper<uchar3, uchar1>(img, ret);
  218. call_rgb_to_nv12(helper.input(), helper.output(), current_cuda_stream());
  219. ret.merge_meta(img);
  220. return ret;
  221. }
  222. sp_image image_nv12_to_rgb(const sp_image &img) {
  223. assert(img.cv_type() == CV_8UC1);
  224. auto ret = sp_image::create(CV_8UC3, nv12_size_to_normal(img.cv_size()));
  225. auto helper = image_cuda_v2_helper<uchar1, uchar3>(img, ret);
  226. call_nv12_to_rgb(helper.input(), helper.output(), current_cuda_stream());
  227. ret.merge_meta(img);
  228. return ret;
  229. }
  230. sp_image image_yuyv_to_rgb(const sp_image &img) {
  231. assert(img.cv_type() == CV_8UC2);
  232. auto ret = sp_image::create(CV_8UC3, img.cv_size());
  233. auto helper = image_cuda_v2_helper<uchar2, uchar3>(img, ret);
  234. call_yuyv_to_rgb(helper.input(), helper.output(), current_cuda_stream());
  235. ret.merge_meta(img);
  236. return ret;
  237. }
  238. sp_image image_remap_np_to_tex(const sp_image &img, const float fov, const float aspect) {
  239. assert(img.cv_type() == CV_32FC2);
  240. auto ret = sp_image::create(CV_32FC2, img.cv_size());
  241. auto helper = image_cuda_v2_helper<float2, float2>(img, ret);
  242. call_np_to_tex(helper.input(), helper.output(),
  243. fov, aspect, current_cuda_stream());
  244. ret.merge_meta(img);
  245. return ret;
  246. }
  247. namespace {
  248. void image_save_opencv(sp_image img, const std::string &filename) {
  249. if (CV_MAT_CN(img.cv_type()) == 3) {
  250. img = image_rgb_to_bgr(img);
  251. }
  252. const auto helper = read_access_helper(img.host());
  253. const auto img_mat = img.cv_mat(helper.ptr());
  254. cv::imwrite(filename, img_mat);
  255. }
  256. }
  257. void image_save_jpg(const sp_image &img, const std::string &filename) {
  258. image_save_opencv(img, fmt::format("{}.jpg", filename));
  259. }
  260. void image_save_png(const sp_image &img, const std::string &filename) {
  261. image_save_opencv(img, fmt::format("{}.png", filename));
  262. }
  263. #include "render/render_utility.h"
  264. struct image_output_helper::impl {
  265. create_config conf;
  266. obj_conn_type conn;
  267. void image_callback_impl() {
  268. const auto img = OBJ_QUERY(sp_image, conf.in_name);
  269. auto ret_rect = simple_rect(0, 0, conf.size.width, conf.size.height);
  270. ret_rect = ret_rect.fit_aspect(img.cv_size().aspectRatio());
  271. auto ret_img = sp_image::create(img.cv_type(), conf.size);
  272. ret_img.initialize_meta();
  273. auto ret_view = ret_img.sub_view(cv::Size(ret_rect.width, ret_rect.height),
  274. cv::Size(ret_rect.x, ret_rect.y));
  275. image_resize(img, ret_view);
  276. if (conf.flip_y) ret_img = image_flip_y(ret_img);
  277. OBJ_SAVE(conf.out_name, ret_img);
  278. }
  279. void image_callback(const obj_name_type _name) {
  280. assert(conf.in_name == _name);
  281. try {
  282. image_callback_impl();
  283. } catch (...) { (void) 0; }
  284. }
  285. explicit impl(const create_config _conf) : conf(_conf) {
  286. conn = OBJ_SIG(conf.in_name)->connect(
  287. [this](auto name) { image_callback(name); });
  288. }
  289. ~impl() {
  290. conn.disconnect();
  291. }
  292. };
  293. image_output_helper::image_output_helper(create_config conf)
  294. : pimpl(std::make_unique<impl>(conf)) {
  295. }
  296. image_output_helper::~image_output_helper() = default;
  297. struct stereo_output_helper::impl {
  298. create_config conf;
  299. obj_conn_type left_conn, right_conn;
  300. bool left_updated = false;
  301. bool right_updated = false;
  302. void image_callback_impl() {
  303. const auto left_img = OBJ_QUERY(sp_image, conf.left_name);
  304. const auto right_img = OBJ_QUERY(sp_image, conf.right_name);
  305. assert(left_img.cv_type() == right_img.cv_type());
  306. assert(left_img.cv_size() == right_img.cv_size());
  307. auto ret_size = conf.size;
  308. if (ret_size.empty()) {
  309. if (conf.halve_width) {
  310. ret_size = left_img.cv_size();
  311. } else {
  312. ret_size = cv::Size(left_img.width() * 2, left_img.height());
  313. }
  314. }
  315. assert(ret_size.width % 2 == 0);
  316. auto ret_rect = simple_rect(0, 0,
  317. conf.halve_width ? ret_size.width : (ret_size.width / 2),
  318. ret_size.height);
  319. ret_rect = ret_rect.fit_aspect(left_img.cv_size().aspectRatio());
  320. if (conf.halve_width) {
  321. ret_rect.x /= 2;
  322. ret_rect.width /= 2;
  323. }
  324. auto ret_img = sp_image::create(left_img.cv_type(), ret_size);
  325. ret_img.initialize_meta();
  326. auto left_view = ret_img.sub_view(cv::Size(ret_rect.width, ret_rect.height),
  327. cv::Size(ret_rect.x, ret_rect.y));
  328. image_resize(left_img, left_view);
  329. auto right_view = ret_img.sub_view(cv::Size(ret_rect.width, ret_rect.height),
  330. cv::Size(ret_rect.x + ret_size.width / 2, ret_rect.y));
  331. image_resize(right_img, right_view);
  332. if (conf.flip_y) ret_img = image_flip_y(ret_img);
  333. OBJ_SAVE(conf.out_name, ret_img);
  334. }
  335. void image_callback(const obj_name_type name) {
  336. if (name == conf.left_name) left_updated = true;
  337. if (name == conf.right_name) right_updated = true;
  338. if (!left_updated || !right_updated) return;
  339. try {
  340. image_callback_impl();
  341. } catch (...) { (void) 0; }
  342. left_updated = false;
  343. right_updated = false;
  344. }
  345. explicit impl(const create_config &_conf) : conf(_conf) {
  346. left_conn = OBJ_SIG(conf.left_name)->connect(
  347. [this](auto name) { image_callback(name); });
  348. right_conn = OBJ_SIG(conf.right_name)->connect(
  349. [this](auto name) { image_callback(name); });
  350. }
  351. ~impl() {
  352. left_conn.disconnect();
  353. right_conn.disconnect();
  354. }
  355. };
  356. stereo_output_helper::stereo_output_helper(create_config conf)
  357. : pimpl(std::make_unique<impl>(conf)) {
  358. }
  359. stereo_output_helper::~stereo_output_helper() = default;