PanoramaCamera.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // #include "../util.h"
  2. #include <csc_images.hpp.opencv.inl>
  3. using namespace CSC ;
  4. /*
  5. @info: 'VAL64'是浮点数'double'
  6. @info: 'LENGTH'和'INDEX'都是整形'int64'
  7. @info:'ARRAY2<T>是Array<T ,ARGC<2>>,既'T[2]'
  8. @info: 'Array'是动态定长容器
  9. @info: 'Queue'是动态变长容器
  10. @info: 'VAR_NONE'指总是无效的元素索引
  11. */
  12. namespace panorama_demo {
  13. /*
  14. @function: 相机在坐标原点上,返回空间点在球目图片上的投影点,和'panorama_xyz_from_txy'互逆
  15. @param {xyz}: 空间中的3D点,不需要先进行归一化
  16. @return {ret}: 纹理空间的2D点,范围为[0 ,1]
  17. */
  18. inline static ARRAY2<VAL64> panorama_txy_from_xyz (const ARRAY3<VAL64> &xyz) {
  19. ARRAY2<VAL64> ret ;
  20. //@info: 这一步是为了兼容我们所用的球目相机的正方向,并且进行归一化
  21. const auto r1x = Vector<VAL64> {-xyz[1] ,-xyz[0] ,xyz[2] ,0}.normalize () ;
  22. //@info: 分别计算投影点坐标
  23. //@info: 反三角函数,缩放,平移
  24. ret[0] = _ATAN_ (r1x[1] ,r1x[0]) / VAL64 (VALX_PI * 2) + VAL64 (0.5) ;
  25. ret[1] = _ACOS_ (r1x[2]) / VAL64 (VALX_PI * 2) * 2 ;
  26. return std::move (ret) ;
  27. }
  28. /*
  29. @function: 相机在坐标原点上,返回球目图片上的投影点所在单位球上的原点,和'panorama_txy_from_xyz'互逆
  30. @param {txy}: 纹理空间的2D点,范围为[0 ,1]
  31. @return {ret}: 空间中的3D点,在单位球上
  32. */
  33. inline static ARRAY3<VAL64> panorama_xyz_from_txy (const ARRAY2<VAL64> &txy) {
  34. ARRAY3<VAL64> ret ;
  35. //@info: 分别计算反投影点坐标
  36. //@info: 平移,缩放
  37. const auto r1x = txy[1] / 2 * VAL64 (VALX_PI * 2) ;
  38. const auto r2x = (txy[0] - VAL64 (0.5)) * VAL64 (VALX_PI * 2) ;
  39. //@info: 对应的三角函数
  40. ret[0] = -_SIN_ (r1x) * _SIN_ (r2x) ;
  41. ret[1] = -_SIN_ (r1x) * _COS_ (r2x) ;
  42. ret[2] = _COS_ (r1x) ;
  43. return std::move (ret) ;
  44. }
  45. /*
  46. @function: 计算栅格化所有线段后最大生成点的数量
  47. @param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
  48. @param {gap}: 栅格化所需的步长,既每多长生成一个点
  49. @return {ret}: 最大生成点的数量
  50. */
  51. inline static LENGTH projection_line_max_count (const Array<ARRAY2<ARRAY3<VAL64>>> &line ,VAL64 gap) {
  52. LENGTH ret = 0 ;
  53. //@info: 循环所有的线段
  54. for (auto &i : line) {
  55. //@info: 转成向量
  56. const auto r1x = Vector<VAL64> {i[0] ,1} ;
  57. const auto r2x = Vector<VAL64> {i[1] ,1} ;
  58. //@info: 计算每条线段需要栅格化多少个点
  59. //@info: 向量差,求模,除步长,向上取整
  60. ret += LENGTH ((r1x - r2x).magnitude () / gap + 0.5) ;
  61. }
  62. printf('ret',ret)
  63. return std::move (ret) ;
  64. }
  65. /*
  66. @function: 计算3D线段在球目图片上的栅格化投影
  67. @param {camera_pose_matrix}: 相机位置,外参矩阵
  68. @param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
  69. @param {gap}: 栅格化所需的步长,既每多长生成一个点
  70. @return {ret}: 栅格化投影投影后的2D线段,点坐标属于纹理空间,范围为[0 ,1]
  71. */
  72. inline static Queue<ARRAY2<ARRAY2<VAL64>>> panorama_projection_line (const Matrix<VAL64> &camera_pose_matrix ,const Array<ARRAY2<ARRAY3<VAL64>>> &line ,VAL64 gap) {
  73. _DEBUG_ASSERT_ (gap > 0) ;
  74. //@info: 计算栅格化所有线段后最大生成点的数量
  75. const auto r4x = projection_line_max_count (line ,gap) ;
  76. //@info: 创建容器
  77. Queue<ARRAY2<ARRAY2<VAL64>>> ret = Queue<ARRAY2<ARRAY2<VAL64>>> (r4x) ;
  78. //@info: 预分配元素索引,'ix'指当前线段,'iy'指上一条线段
  79. INDEX ix = ret.insert () ;
  80. INDEX iy = VAR_NONE ;
  81. const auto r10x = camera_pose_matrix.inverse () ;
  82. //@info: 循环所有的线段
  83. for (auto &i : line) {
  84. //@info: 转成向量,并应用相机外参矩阵的反变换
  85. const auto r1x = r10x * Vector<VAL64> {i[0] ,1} ;
  86. const auto r2x = r10x * Vector<VAL64> {i[1] ,1} ;
  87. //@info: 计算栅格化的点数量
  88. const auto r3x = (r2x - r1x).magnitude () / gap ;
  89. //@info: 计算栅格化的步长向量
  90. const auto r5x = (r2x - r1x).normalize () * gap ;
  91. for (VAL64 j = 0 ; j < r3x ; j += 1) {
  92. //@info: 计算循环栅格化的每一个3D点的坐标
  93. const auto r6x = r1x + r5x * j ;
  94. //@info: 计算3D点对应的2D投影
  95. const auto r7x = panorama_txy_from_xyz (r6x.xyz ()) ;
  96. //@info: 记录'ix'的第一个点和'iy'的第二个点
  97. ret[ix][0] = r7x ;
  98. if (iy != VAR_NONE)
  99. ret[iy][1] = r7x ;
  100. //@info: 预分配元素索引
  101. iy = ix ;
  102. ix = ret.insert () ;
  103. }
  104. //@info: 跳过一个点都没有的情况
  105. if (iy == VAR_NONE)
  106. continue ;
  107. //@info: 对栅格化向上取整,补上最后一条线段的第二个点
  108. const auto r8x = r1x + r5x * _CEIL_ (r3x ,VAL64 (1)) ;
  109. const auto r9x = panorama_txy_from_xyz (r8x.xyz ()) ;
  110. ret[iy][1] = r9x ;
  111. iy = VAR_NONE ;
  112. }
  113. //@info: 因为使用了预分配元素索引,最后需要弹出末尾未实际使用的元素
  114. ret.pop () ;
  115. return std::move (ret) ;
  116. }
  117. /*
  118. @function: 操作图片,需要根据平台自行实现
  119. */
  120. static const PhanRef<const AbstractImage<COLOR_BGR>::Engine> &ABSTRACTIMAGE_ENGINE = PhanRef<const AbstractImage<COLOR_BGR>::Engine>::make (_CACHE_ ([] () {
  121. return AutoRef<AbstractImage_Engine_OPENCV<COLOR_BGR>>::make () ;
  122. })) ;
  123. /*
  124. @function: 绘制线段,需要根据平台自行实现
  125. @warn: 一个很重要的点,球目图片是横向循环的,既'x(width)=x(0)',因此'[{w-2,0} ,{1 ,0}]'和'[{1 ,0} ,{w-2,0}]'不同,不能直接绘制,需要变换成'[{0 ,0} ,{1 ,0}]'和'[{w-2 ,0} ,{w-1 ,0}]'进行绘制
  126. */
  127. inline static void image_draw_line (AbstractImage<COLOR_BGR> &image ,const Queue<ARRAY2<ARRAY2<VAL64>>> &line ,LENGTH thickness ,const COLOR_BGR &color) {
  128. const auto r4x = image.native<cv::Mat> () ;
  129. const auto r7x = ARRAY2<VAL64> {VAL64 (_XVALUE_<cv::Mat &> (r4x).cols) ,VAL64 (_XVALUE_<cv::Mat &> (r4x).rows)} ;
  130. const auto r3x = cv::Scalar {cv::Scalar::value_type (color[0]) ,cv::Scalar::value_type (color[1]) ,cv::Scalar::value_type (color[2])} ;
  131. for (auto &i : line) {
  132. const auto r10x = i[0][0] * r7x[0] ;
  133. const auto r11x = i[0][1] * r7x[1] ;
  134. const auto r12x = i[1][0] * r7x[0] ;
  135. const auto r13x = i[1][1] * r7x[1] ;
  136. const auto r14x = _SQE_ (r12x - r10x) ;
  137. const auto r15x = _SQE_ (_ABS_ (r12x - r10x) - r7x[0]) ;
  138. if (r15x >= r14x) {
  139. const auto r1x = cv::Point {cv::Point::value_type (r10x) ,cv::Point::value_type (r11x)} ;
  140. const auto r2x = cv::Point {cv::Point::value_type (r12x) ,cv::Point::value_type (r13x)} ;
  141. cv::line (_XVALUE_<cv::Mat &> (r4x) ,r1x ,r2x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
  142. } else {
  143. const auto r16x = r10x < r12x ? ARRAY2<VAL64> {r12x - r7x[0] ,r13x} : ARRAY2<VAL64> {r10x - r7x[0] ,r11x} ;
  144. const auto r17x = r10x < r12x ? ARRAY2<VAL64> {r10x ,r11x} : ARRAY2<VAL64> {r12x ,r13x} ;
  145. const auto r18x = r10x < r12x ? ARRAY2<VAL64> {r12x ,r13x} : ARRAY2<VAL64> {r10x ,r11x} ;
  146. const auto r19x = r10x < r12x ? ARRAY2<VAL64> {r10x + r7x[0] ,r11x} : ARRAY2<VAL64> {r12x + r7x[0] ,r13x} ;
  147. const auto r20x = cv::Point {cv::Point::value_type (r16x[0]) ,cv::Point::value_type (r16x[1])} ;
  148. const auto r21x = cv::Point {cv::Point::value_type (r17x[0]) ,cv::Point::value_type (r17x[1])} ;
  149. const auto r22x = cv::Point {cv::Point::value_type (r18x[0]) ,cv::Point::value_type (r18x[1])} ;
  150. const auto r23x = cv::Point {cv::Point::value_type (r19x[0]) ,cv::Point::value_type (r19x[1])} ;
  151. cv::line (_XVALUE_<cv::Mat &> (r4x) ,r20x ,r21x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
  152. cv::line (_XVALUE_<cv::Mat &> (r4x) ,r22x ,r23x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
  153. }
  154. }
  155. }
  156. /*
  157. @function: 加载3D线段,需要根据平台自行实现
  158. @info: 这里生成一个3D单位CUBE的所有线段
  159. */
  160. inline static Array<ARRAY2<ARRAY3<VAL64>>> line_of_demo_cube () {
  161. const auto r1x = VAL64 (0.5) ;
  162. const auto r2x = ARRAY8<ARRAY3<VAL64>> ({
  163. {-r1x ,-r1x ,+r1x} ,
  164. {-r1x ,+r1x ,+r1x} ,
  165. {+r1x ,+r1x ,+r1x} ,
  166. {+r1x ,-r1x ,+r1x} ,
  167. {-r1x ,-r1x ,-r1x} ,
  168. {-r1x ,+r1x ,-r1x} ,
  169. {+r1x ,+r1x ,-r1x} ,
  170. {+r1x ,-r1x ,-r1x}}) ;
  171. const auto r3x = Array<ARRAY2<INDEX>> ({
  172. {0 ,1} ,
  173. {1 ,2} ,
  174. {2 ,3} ,
  175. {3 ,0} ,
  176. {4 ,5} ,
  177. {5 ,6} ,
  178. {6 ,7} ,
  179. {7 ,4} ,
  180. {0 ,4} ,
  181. {1 ,5} ,
  182. {2 ,6} ,
  183. {3 ,7}}) ;
  184. Array<ARRAY2<ARRAY3<VAL64>>> ret = Array<ARRAY2<ARRAY3<VAL64>>> (r3x.length ()) ;
  185. for (INDEX i = 0 ; i < r3x.length () ; i++) {
  186. ret[i][0] = r2x[r3x[i][0]] ;
  187. ret[i][1] = r2x[r3x[i][1]] ;
  188. }
  189. return std::move (ret) ;
  190. }
  191. } ;
  192. int main () {
  193. //@info: 加载3D线段
  194. const auto r1x = panorama_demo::line_of_demo_cube () ;
  195. //@info: 计算3D线段栅格化投影后的2D线段
  196. const auto r2x = panorama_demo::panorama_projection_line (Matrix<VAL64>::make_identity () ,r1x ,0.1) ;
  197. if (TRUE) {
  198. auto rax = AbstractImage<COLOR_BGR> (panorama_demo::ABSTRACTIMAGE_ENGINE) ;
  199. //@info: 加载图片,文件'panorama_demo.input.jpg'
  200. rax.load_file (_PCSTR_ ("panorama_demo.input.jpg")) ;
  201. //@info: 绘制线段
  202. panorama_demo::image_draw_line (rax ,r2x ,30 ,{0 ,0 ,255}) ;
  203. //@info: 保存图片,文件'panorama_demo.result.jpg'
  204. rax.save_file (_PCSTR_ ("panorama_demo.result.jpg") ,AnyRef<std::vector<int>>::make (std::vector<int> {cv::IMWRITE_PNG_COMPRESSION ,5})) ;
  205. }
  206. return 0 ;
  207. }