@@ -440,6 +440,232 @@ def compute_camera_rays_pinhole_from_aperture_kernel(
440440 out_rays [output_camera_index , py , px , 1 ] = wp .normalize (ray_direction_camera_space )
441441
442442
443+ _PINHOLE_OPENCV_INVERSION_ITERATIONS = 10
444+ _PINHOLE_OPENCV_LINE_SEARCH_ITERATIONS = 6
445+ # Residuals are in normalized image coordinates; convergence ~1e-6, acceptance ~2e-5 RMS.
446+ _PINHOLE_OPENCV_CONVERGENCE_TOLERANCE_SQUARED = 1.0e-12
447+ _PINHOLE_OPENCV_ACCEPTANCE_TOLERANCE_SQUARED = 4.0e-10
448+ _PINHOLE_OPENCV_SOLVER_EPSILON = 1.0e-8
449+
450+
451+ @wp .func
452+ def _distort_pinhole_opencv (
453+ x : wp .float32 ,
454+ y : wp .float32 ,
455+ k1 : wp .float32 ,
456+ k2 : wp .float32 ,
457+ k3 : wp .float32 ,
458+ k4 : wp .float32 ,
459+ k5 : wp .float32 ,
460+ k6 : wp .float32 ,
461+ p1 : wp .float32 ,
462+ p2 : wp .float32 ,
463+ s1 : wp .float32 ,
464+ s2 : wp .float32 ,
465+ s3 : wp .float32 ,
466+ s4 : wp .float32 ,
467+ ) -> wp .vec2f :
468+ radius_squared = x * x + y * y
469+ radius_fourth = radius_squared * radius_squared
470+ radius_sixth = radius_fourth * radius_squared
471+ radial = (1.0 + k1 * radius_squared + k2 * radius_fourth + k3 * radius_sixth ) / (
472+ 1.0 + k4 * radius_squared + k5 * radius_fourth + k6 * radius_sixth
473+ )
474+ tangential_prism_x = (
475+ 2.0 * p1 * x * y + p2 * (radius_squared + 2.0 * x * x ) + s1 * radius_squared + s2 * radius_fourth
476+ )
477+ tangential_prism_y = (
478+ p1 * (radius_squared + 2.0 * y * y ) + 2.0 * p2 * x * y + s3 * radius_squared + s4 * radius_fourth
479+ )
480+ return wp .vec2f (x * radial + tangential_prism_x , y * radial + tangential_prism_y )
481+
482+
483+ @wp .func
484+ def _invert_pinhole_opencv (
485+ x_distorted : wp .float32 ,
486+ y_distorted : wp .float32 ,
487+ k1 : wp .float32 ,
488+ k2 : wp .float32 ,
489+ k3 : wp .float32 ,
490+ k4 : wp .float32 ,
491+ k5 : wp .float32 ,
492+ k6 : wp .float32 ,
493+ p1 : wp .float32 ,
494+ p2 : wp .float32 ,
495+ s1 : wp .float32 ,
496+ s2 : wp .float32 ,
497+ s3 : wp .float32 ,
498+ s4 : wp .float32 ,
499+ ) -> wp .vec3f :
500+ # Damped Newton inversion; unverifiable pixels return the zero sentinel.
501+ x = x_distorted
502+ y = y_distorted
503+ valid = True
504+ done = False
505+ for _ in range (_PINHOLE_OPENCV_INVERSION_ITERATIONS ):
506+ if valid and not done :
507+ distorted = _distort_pinhole_opencv (x , y , k1 , k2 , k3 , k4 , k5 , k6 , p1 , p2 , s1 , s2 , s3 , s4 )
508+ residual_x = distorted [0 ] - x_distorted
509+ residual_y = distorted [1 ] - y_distorted
510+ residual_squared = residual_x * residual_x + residual_y * residual_y
511+
512+ if not wp .isfinite (residual_squared ):
513+ valid = False
514+ elif residual_squared <= _PINHOLE_OPENCV_CONVERGENCE_TOLERANCE_SQUARED :
515+ done = True
516+ else :
517+ radius_squared = x * x + y * y
518+ radius_fourth = radius_squared * radius_squared
519+ radius_sixth = radius_fourth * radius_squared
520+ numerator = 1.0 + k1 * radius_squared + k2 * radius_fourth + k3 * radius_sixth
521+ denominator = 1.0 + k4 * radius_squared + k5 * radius_fourth + k6 * radius_sixth
522+
523+ if wp .abs (denominator ) <= _PINHOLE_OPENCV_SOLVER_EPSILON :
524+ valid = False
525+ else :
526+ radial = numerator / denominator
527+ numerator_derivative = k1 + 2.0 * k2 * radius_squared + 3.0 * k3 * radius_fourth
528+ denominator_derivative = k4 + 2.0 * k5 * radius_squared + 3.0 * k6 * radius_fourth
529+ radial_derivative = (numerator_derivative * denominator - numerator * denominator_derivative ) / (
530+ denominator * denominator
531+ )
532+ radial_derivative_x = 2.0 * x * radial_derivative
533+ radial_derivative_y = 2.0 * y * radial_derivative
534+
535+ jacobian_00 = (
536+ radial
537+ + x * radial_derivative_x
538+ + 2.0 * p1 * y
539+ + 6.0 * p2 * x
540+ + 2.0 * s1 * x
541+ + 4.0 * s2 * radius_squared * x
542+ )
543+ jacobian_01 = (
544+ x * radial_derivative_y
545+ + 2.0 * p1 * x
546+ + 2.0 * p2 * y
547+ + 2.0 * s1 * y
548+ + 4.0 * s2 * radius_squared * y
549+ )
550+ jacobian_10 = (
551+ y * radial_derivative_x
552+ + 2.0 * p1 * x
553+ + 2.0 * p2 * y
554+ + 2.0 * s3 * x
555+ + 4.0 * s4 * radius_squared * x
556+ )
557+ jacobian_11 = (
558+ radial
559+ + y * radial_derivative_y
560+ + 6.0 * p1 * y
561+ + 2.0 * p2 * x
562+ + 2.0 * s3 * y
563+ + 4.0 * s4 * radius_squared * y
564+ )
565+ determinant = jacobian_00 * jacobian_11 - jacobian_01 * jacobian_10
566+
567+ if not wp .isfinite (determinant ) or wp .abs (determinant ) <= _PINHOLE_OPENCV_SOLVER_EPSILON :
568+ valid = False
569+ else :
570+ delta_x = (jacobian_11 * residual_x - jacobian_01 * residual_y ) / determinant
571+ delta_y = (jacobian_00 * residual_y - jacobian_10 * residual_x ) / determinant
572+ if not wp .isfinite (delta_x ) or not wp .isfinite (delta_y ):
573+ valid = False
574+ else :
575+ base_x = x
576+ base_y = y
577+ step = 1.0
578+ accepted = False
579+ for _line_search_iteration in range (_PINHOLE_OPENCV_LINE_SEARCH_ITERATIONS ):
580+ candidate_x = base_x - step * delta_x
581+ candidate_y = base_y - step * delta_y
582+ candidate_distorted = _distort_pinhole_opencv (
583+ candidate_x ,
584+ candidate_y ,
585+ k1 ,
586+ k2 ,
587+ k3 ,
588+ k4 ,
589+ k5 ,
590+ k6 ,
591+ p1 ,
592+ p2 ,
593+ s1 ,
594+ s2 ,
595+ s3 ,
596+ s4 ,
597+ )
598+ candidate_residual_x = candidate_distorted [0 ] - x_distorted
599+ candidate_residual_y = candidate_distorted [1 ] - y_distorted
600+ candidate_residual_squared = (
601+ candidate_residual_x * candidate_residual_x
602+ + candidate_residual_y * candidate_residual_y
603+ )
604+ if (
605+ not accepted
606+ and wp .isfinite (candidate_residual_squared )
607+ and candidate_residual_squared < residual_squared
608+ ):
609+ x = candidate_x
610+ y = candidate_y
611+ accepted = True
612+ step *= 0.5
613+ # Stalled: no step reduced the residual; stop iterating.
614+ if not accepted :
615+ done = True
616+
617+ # Accept only if the forward model reproduces the target within tolerance.
618+ if valid :
619+ distorted = _distort_pinhole_opencv (x , y , k1 , k2 , k3 , k4 , k5 , k6 , p1 , p2 , s1 , s2 , s3 , s4 )
620+ residual_x = distorted [0 ] - x_distorted
621+ residual_y = distorted [1 ] - y_distorted
622+ residual_squared = residual_x * residual_x + residual_y * residual_y
623+ valid = wp .isfinite (residual_squared ) and residual_squared <= _PINHOLE_OPENCV_ACCEPTANCE_TOLERANCE_SQUARED
624+
625+ if valid :
626+ return wp .normalize (wp .vec3f (x , - y , - 1.0 ))
627+ return wp .vec3f (0.0 )
628+
629+
630+ @wp .kernel (enable_backward = False )
631+ def compute_camera_rays_pinhole_opencv_kernel (
632+ width : int ,
633+ height : int ,
634+ image_width : wp .float32 ,
635+ image_height : wp .float32 ,
636+ fx : wp .float32 ,
637+ fy : wp .float32 ,
638+ cx : wp .float32 ,
639+ cy : wp .float32 ,
640+ k1 : wp .float32 ,
641+ k2 : wp .float32 ,
642+ k3 : wp .float32 ,
643+ k4 : wp .float32 ,
644+ k5 : wp .float32 ,
645+ k6 : wp .float32 ,
646+ p1 : wp .float32 ,
647+ p2 : wp .float32 ,
648+ s1 : wp .float32 ,
649+ s2 : wp .float32 ,
650+ s3 : wp .float32 ,
651+ s4 : wp .float32 ,
652+ camera_index : int ,
653+ out_rays : wp .array4d [wp .vec3f ],
654+ ):
655+ py , px = wp .tid ()
656+ u = ((float (px ) + 0.5 ) / float (width )) * image_width
657+ v = ((float (py ) + 0.5 ) / float (height )) * image_height
658+ x_distorted = (u - cx ) / fx
659+ y_distorted = (v - cy ) / fy
660+
661+ ray_direction_camera_space = _invert_pinhole_opencv (
662+ x_distorted , y_distorted , k1 , k2 , k3 , k4 , k5 , k6 , p1 , p2 , s1 , s2 , s3 , s4
663+ )
664+
665+ out_rays [camera_index , py , px , 0 ] = wp .vec3f (0.0 )
666+ out_rays [camera_index , py , px , 1 ] = ray_direction_camera_space
667+
668+
443669@wp .kernel (enable_backward = False )
444670def compute_camera_rays_fisheye_opencv_kernel (
445671 width : int ,
0 commit comments