image_process.cpp 16 KB

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