sycl-opencv-interop.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*
  2. * The example of interoperability between SYCL/OpenCL and OpenCV.
  3. * - SYCL: https://www.khronos.org/sycl/
  4. * - SYCL runtime parameters: https://github.com/intel/llvm/blob/sycl/sycl/doc/EnvironmentVariables.md
  5. */
  6. #include <CL/sycl.hpp>
  7. #include <opencv2/core.hpp>
  8. #include <opencv2/highgui.hpp>
  9. #include <opencv2/videoio.hpp>
  10. #include <opencv2/imgproc.hpp>
  11. #include <opencv2/core/ocl.hpp>
  12. class sycl_inverse_kernel; // can be omitted - modern SYCL versions doesn't require this
  13. using namespace cv;
  14. class App
  15. {
  16. public:
  17. App(const CommandLineParser& cmd);
  18. ~App();
  19. void initVideoSource();
  20. void initSYCL();
  21. void process_frame(cv::Mat& frame);
  22. /// to check result with CPU-only reference code
  23. Mat process_frame_reference(const cv::Mat& frame);
  24. int run();
  25. bool isRunning() { return m_running; }
  26. bool doProcess() { return m_process; }
  27. void setRunning(bool running) { m_running = running; }
  28. void setDoProcess(bool process) { m_process = process; }
  29. protected:
  30. void handleKey(char key);
  31. private:
  32. bool m_running;
  33. bool m_process;
  34. bool m_show_ui;
  35. int64 m_t0;
  36. int64 m_t1;
  37. float m_time;
  38. float m_frequency;
  39. std::string m_file_name;
  40. int m_camera_id;
  41. cv::VideoCapture m_cap;
  42. cv::Mat m_frame;
  43. cl::sycl::queue sycl_queue;
  44. };
  45. App::App(const CommandLineParser& cmd)
  46. {
  47. m_camera_id = cmd.get<int>("camera");
  48. m_file_name = cmd.get<std::string>("video");
  49. m_running = false;
  50. m_process = false;
  51. } // ctor
  52. App::~App()
  53. {
  54. // nothing
  55. }
  56. void App::initSYCL()
  57. {
  58. using namespace cl::sycl;
  59. // Configuration details: https://github.com/intel/llvm/blob/sycl/sycl/doc/EnvironmentVariables.md
  60. cl::sycl::default_selector selector;
  61. sycl_queue = cl::sycl::queue(selector, [](cl::sycl::exception_list l)
  62. {
  63. // exception_handler
  64. for (auto ep : l)
  65. {
  66. try
  67. {
  68. std::rethrow_exception(ep);
  69. }
  70. catch (const cl::sycl::exception& e)
  71. {
  72. std::cerr << "SYCL exception: " << e.what() << std::endl;
  73. }
  74. }
  75. });
  76. auto device = sycl_queue.get_device();
  77. auto platform = device.get_platform();
  78. std::cout << "SYCL device: " << device.get_info<info::device::name>()
  79. << " @ " << device.get_info<info::device::driver_version>()
  80. << " (platform: " << platform.get_info<info::platform::name>() << ")" << std::endl;
  81. if (device.is_host())
  82. {
  83. std::cerr << "SYCL can't select OpenCL device. Host is used for computations, interoperability is not available" << std::endl;
  84. }
  85. else
  86. {
  87. // bind OpenCL context/device/queue from SYCL to OpenCV
  88. try
  89. {
  90. auto ctx = cv::ocl::OpenCLExecutionContext::create(
  91. platform.get_info<info::platform::name>(),
  92. platform.get(),
  93. sycl_queue.get_context().get(),
  94. device.get()
  95. );
  96. ctx.bind();
  97. }
  98. catch (const cv::Exception& e)
  99. {
  100. std::cerr << "OpenCV: Can't bind SYCL OpenCL context/device/queue: " << e.what() << std::endl;
  101. }
  102. std::cout << "OpenCV uses OpenCL: " << (cv::ocl::useOpenCL() ? "True" : "False") << std::endl;
  103. }
  104. } // initSYCL()
  105. void App::initVideoSource()
  106. {
  107. if (!m_file_name.empty() && m_camera_id == -1)
  108. {
  109. m_cap.open(samples::findFileOrKeep(m_file_name));
  110. if (!m_cap.isOpened())
  111. throw std::runtime_error(std::string("can't open video stream: ") + m_file_name);
  112. }
  113. else if (m_camera_id != -1)
  114. {
  115. m_cap.open(m_camera_id);
  116. if (!m_cap.isOpened())
  117. throw std::runtime_error(std::string("can't open camera: ") + std::to_string(m_camera_id));
  118. }
  119. else
  120. throw std::runtime_error(std::string("specify video source"));
  121. } // initVideoSource()
  122. void App::process_frame(cv::Mat& frame)
  123. {
  124. using namespace cl::sycl;
  125. // cv::Mat => cl::sycl::buffer
  126. {
  127. CV_Assert(frame.isContinuous());
  128. CV_CheckTypeEQ(frame.type(), CV_8UC1, "");
  129. buffer<uint8_t, 2> frame_buffer(frame.data, range<2>(frame.rows, frame.cols));
  130. // done automatically: frame_buffer.set_write_back(true);
  131. sycl_queue.submit([&](handler& cgh) {
  132. auto pixels = frame_buffer.get_access<access::mode::read_write>(cgh);
  133. cgh.parallel_for<class sycl_inverse_kernel>(range<2>(frame.rows, frame.cols), [=](item<2> item) {
  134. uint8_t v = pixels[item];
  135. pixels[item] = ~v;
  136. });
  137. });
  138. sycl_queue.wait_and_throw();
  139. }
  140. // No way to extract cl_mem from cl::sycl::buffer (ref: 3.6.11 "Interfacing with OpenCL" of SYCL 1.2.1)
  141. // We just reusing OpenCL context/device/queue from SYCL here (see initSYCL() bind part) and call UMat processing
  142. {
  143. UMat blurResult;
  144. {
  145. UMat umat_buffer = frame.getUMat(ACCESS_RW);
  146. cv::blur(umat_buffer, blurResult, Size(3, 3)); // UMat doesn't support inplace
  147. }
  148. Mat result;
  149. blurResult.copyTo(result);
  150. swap(result, frame);
  151. }
  152. }
  153. Mat App::process_frame_reference(const cv::Mat& frame)
  154. {
  155. Mat result;
  156. cv::bitwise_not(frame, result);
  157. Mat blurResult;
  158. cv::blur(result, blurResult, Size(3, 3)); // avoid inplace
  159. blurResult.copyTo(result);
  160. return result;
  161. }
  162. int App::run()
  163. {
  164. std::cout << "Initializing..." << std::endl;
  165. initSYCL();
  166. initVideoSource();
  167. std::cout << "Press ESC to exit" << std::endl;
  168. std::cout << " 'p' to toggle ON/OFF processing" << std::endl;
  169. m_running = true;
  170. m_process = true;
  171. m_show_ui = true;
  172. int processedFrames = 0;
  173. cv::TickMeter timer;
  174. // Iterate over all frames
  175. while (isRunning() && m_cap.read(m_frame))
  176. {
  177. Mat m_frameGray;
  178. cvtColor(m_frame, m_frameGray, COLOR_BGR2GRAY);
  179. bool checkWithReference = (processedFrames == 0);
  180. Mat reference_result;
  181. if (checkWithReference)
  182. {
  183. reference_result = process_frame_reference(m_frameGray);
  184. }
  185. timer.reset();
  186. timer.start();
  187. if (m_process)
  188. {
  189. process_frame(m_frameGray);
  190. }
  191. timer.stop();
  192. if (checkWithReference)
  193. {
  194. double diffInf = cv::norm(reference_result, m_frameGray, NORM_INF);
  195. if (diffInf > 0)
  196. {
  197. std::cerr << "Result is not accurate. diffInf=" << diffInf << std::endl;
  198. imwrite("reference.png", reference_result);
  199. imwrite("actual.png", m_frameGray);
  200. }
  201. }
  202. Mat img_to_show = m_frameGray;
  203. std::ostringstream msg;
  204. msg << "Frame " << processedFrames << " (" << m_frame.size
  205. << ") Time: " << cv::format("%.2f", timer.getTimeMilli()) << " msec"
  206. << " (process: " << (m_process ? "True" : "False") << ")";
  207. std::cout << msg.str() << std::endl;
  208. putText(img_to_show, msg.str(), Point(5, 150), FONT_HERSHEY_SIMPLEX, 1., Scalar(255, 100, 0), 2);
  209. if (m_show_ui)
  210. {
  211. try
  212. {
  213. imshow("sycl_interop", img_to_show);
  214. int key = waitKey(1);
  215. switch (key)
  216. {
  217. case 27: // ESC
  218. m_running = false;
  219. break;
  220. case 'p': // fallthru
  221. case 'P':
  222. m_process = !m_process;
  223. break;
  224. default:
  225. break;
  226. }
  227. }
  228. catch (const std::exception& e)
  229. {
  230. std::cerr << "ERROR(OpenCV UI): " << e.what() << std::endl;
  231. if (processedFrames > 0)
  232. throw;
  233. m_show_ui = false; // UI is not available
  234. }
  235. }
  236. processedFrames++;
  237. if (!m_show_ui)
  238. {
  239. if (processedFrames > 100)
  240. m_running = false;
  241. }
  242. }
  243. return 0;
  244. }
  245. int main(int argc, char** argv)
  246. {
  247. const char* keys =
  248. "{ help h ? | | print help message }"
  249. "{ camera c | -1 | use camera as input }"
  250. "{ video v | | use video as input }";
  251. CommandLineParser cmd(argc, argv, keys);
  252. if (cmd.has("help"))
  253. {
  254. cmd.printMessage();
  255. return EXIT_SUCCESS;
  256. }
  257. try
  258. {
  259. App app(cmd);
  260. if (!cmd.check())
  261. {
  262. cmd.printErrors();
  263. return 1;
  264. }
  265. app.run();
  266. }
  267. catch (const cv::Exception& e)
  268. {
  269. std::cout << "FATAL: OpenCV error: " << e.what() << std::endl;
  270. return 1;
  271. }
  272. catch (const std::exception& e)
  273. {
  274. std::cout << "FATAL: C++ error: " << e.what() << std::endl;
  275. return 1;
  276. }
  277. catch (...)
  278. {
  279. std::cout << "FATAL: unknown C++ exception" << std::endl;
  280. return 1;
  281. }
  282. return EXIT_SUCCESS;
  283. } // main()