Skip to content

Commit 03ce8bb

Browse files
author
Thimoté Dupuch
committed
Refactor + test for radialDistortion!
1 parent 8d1b564 commit 03ce8bb

3 files changed

Lines changed: 134 additions & 91 deletions

File tree

src/services/Utils.jl

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -61,38 +61,59 @@ function radialDistortion!(
6161
src::AbstractMatrix
6262
) where {N}
6363

64-
center = SVector{2}(cc.center)
65-
k = ntuple(i -> cc.kc[i], N)
66-
H, W = size(src)
67-
c₁, c₂ = center
68-
69-
# pre-allocate buffers for the radius powers
70-
buf = Vector{T}(undef, max(H, W))
71-
72-
@tturbo for h_d in 1:H, w_d in 1:W
73-
h_ = h_d - c₁
74-
w_ = w_d - c₂
75-
= h_ * h_ + w_ * w_
76-
77-
num = one(T)
78-
rⁿ =
79-
for i in 1:N
80-
num += k[i] * rⁿ
81-
rⁿ *=
64+
h, w = size(dest)
65+
@assert size(src) == (h, w) "Sizes of src and dest must be the same"
66+
@assert (cc.height, cc.width) == (h, w) "Calibration size mismatches image size"
67+
inv_fx = inv(cc.K[1, 1])
68+
inv_fy = inv(cc.K[2, 2])
69+
cx = cc.K[1, 3]
70+
cy = cc.K[2, 3]
71+
k1, k2, p1, p2, k3 = cc.kc
72+
@tturbo for u in 1:w
73+
# Calculate x_norm once per column
74+
x_norm = (u - cx) * inv_fx
75+
x_sq = x_norm^2
76+
77+
for v in 1:h
78+
y_norm = (v - cy) * inv_fy
79+
y_sq = y_norm^2
80+
81+
# --- Distortion Logic (Brown-Conrady) ---
82+
r_sq = x_sq + y_sq
83+
84+
# Radial component: (1 + k1*r^2 + k2*r^4 + k3*r^6)
85+
radial_term = 1.0 + r_sq * (k1 + r_sq * (k2 + r_sq * k3))
86+
#radial_term = evalpoly(r_sq, (1, k1, k2, k3))
87+
88+
# Tangential component
89+
xy_2 = 2.0 * x_norm * y_norm
90+
x_tangential = p1 * xy_2 + p2 * (r_sq + 2.0 * x_sq)
91+
y_tangential = p1 * (r_sq + 2.0 * y_sq) + p2 * xy_2
92+
93+
# Distorted normalized coordinates
94+
x_dist = x_norm * radial_term + x_tangential
95+
y_dist = y_norm * radial_term + y_tangential
96+
97+
# --- Project back to pixel coordinates ---
98+
# Nearest neighbor interpolation
99+
u_src = round(Int, (x_dist * cc.K[1, 1]) + cx)
100+
v_src = round(Int, (y_dist * cc.K[2, 2]) + cy)
101+
102+
# no bounds check -> not ideal
103+
104+
@inbounds dest[v, u] = src[v_src, u_src]
105+
106+
# with bounds check :
107+
#if 1 <= u_src <= w && 1 <= v_src <= h
108+
#else
109+
# # Pixel is out of bounds (black border)
110+
# @inbounds dest[v, u] = 0
111+
#end
82112
end
83-
84-
h_u = c₁ + h_ / num
85-
w_u = c₂ + w_ / num
86-
87-
# nearest-neighbour lookup (round + clamp)
88-
hi = clamp(round(Int, h_u), 1, H)
89-
wi = clamp(round(Int, w_u), 1, W)
90-
dest[h_d, w_d] = src[hi, wi]
91113
end
92114
return nothing
93115
end
94116

95-
96117
"""
97118
intersectLineToPlane3D(planenorm, planepnt, raydir, raypnt) -> point
98119
@@ -130,15 +151,15 @@ end
130151
"""
131152
$SIGNATURES
132153
133-
Ray trace from pixel coords to a floor in local level reference which is assumed
154+
Ray trace from pixel coords to a floor in local level reference which is assumed
134155
aligned with gravity. Returns intersect in local level frame (coordinates).
135156
136157
Notes
137158
- Implemented against local level to allow easier local or world reference usage,
138159
- Just assume world is local level, i.e. `l_nFL = w_nFL` and `l_FL = w_FL`.
139160
- User must provide (assumed dynamic) local level transform via `l_T_ex` -- see example below!
140161
- Coordinate chain used is from ( pixel-array (a) --> camera (c) --> extrinsic (ex) --> level (l) )
141-
- `c_H_a` from pixel-array to camera (as homography matrix)
162+
- `c_H_a` from pixel-array to camera (as homography matrix)
142163
- `a_F` feature in array pixel frame
143164
- `l_T_ex` extrinsic in body (or extrinsic to local level), SE3 Manifold element using ArrayPartition
144165
- `ex_T_c` camera in extrinsic (or camera to extrinsic)
@@ -155,7 +176,7 @@ ci,cj = 360,640 # assuming 720x1280 image
155176
c_H_a = [0 1 -cj; 1 0 -ci; 0 0 f] # camera matrix
156177
157178
# body to extrinsic of camera -- e.g. camera looking down 0.2 and left 0.2
158-
# local level to body to extrinsic transform
179+
# local level to body to extrinsic transform
159180
l_T_b = ArrayPartition([0;0;0.], R0)
160181
b_T_ex = ArrayPartition([0;0;0.], exp_lie(Mr, hat(Mr, R0, [0;0.2;0.2])))
161182
l_T_ex = compose(M, l_T_b, b_T_ex) # this is where body reference is folded in.
@@ -177,14 +198,14 @@ l_Forb = intersectRayToPlane(
177198
See also: `CameraModels.intersectLineToPlane3D`
178199
"""
179200
function intersectRayToPlane(
180-
c_H_a::AbstractMatrix{<:Real},
181-
a_F::AbstractVector{<:Real},
182-
l_nFL::AbstractVector{<:Real},
183-
l_FL::AbstractVector{<:Real};
184-
M = SpecialEuclideanGroup(3; variant = :right),
185-
l_T_ex = ArrayPartition([0;0;0.], exp(SpecialOrthogonalGroup(3), hat(LieAlgebra(SpecialOrthogonalGroup(3)), [0;0.2;0.]))),
186-
ex_T_c = ArrayPartition([0;0;0.], [0 0 1; -1 0 0; 0 -1 0.]),
187-
)
201+
c_H_a::AbstractMatrix{<:Real},
202+
a_F::AbstractVector{<:Real},
203+
l_nFL::AbstractVector{<:Real},
204+
l_FL::AbstractVector{<:Real};
205+
M = SpecialEuclideanGroup(3; variant = :right),
206+
l_T_ex = ArrayPartition([0;0;0.0], exp(SpecialOrthogonalGroup(3), hat(LieAlgebra(SpecialOrthogonalGroup(3)), [0;0.2;0.0]))),
207+
ex_T_c = ArrayPartition([0;0;0.0], [0 0 1; -1 0 0; 0 -1 0.0]),
208+
)
188209
# camera in level (or camera to level) manifold element as ArrayPartition
189210
l_T_c = compose(M, l_T_ex, ex_T_c)
190211

test/runtests.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ CameraModels.height(m::SomeTestModel) = 11
1111
CameraModels.width(m::SomeTestModel) = 22
1212

1313
@testset "Test sensorsize using rows and columns." begin
14-
14+
1515
model = SomeTestModel()
16-
@test sensorsize(model) == SVector{2}(22,11)
16+
@test sensorsize(model) == SVector{2}(22, 11)
1717
end
1818

1919

20-
2120
include("testutils.jl")
2221
include("multiview_manifolds.jl")
2322
include("CameraTestBench.jl")
2423
include("Pinhole.jl")
2524
include("SkewDistortion.jl")
26-

test/testutils.jl

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,87 @@ using Test
22
using CameraModels
33
import LieGroups
44
using LieGroups:
5-
SpecialEuclideanGroup,
6-
SpecialOrthogonalGroup,
7-
hat,
8-
exp,
9-
compose,
10-
LieAlgebra
5+
SpecialEuclideanGroup,
6+
SpecialOrthogonalGroup,
7+
hat,
8+
exp,
9+
compose,
10+
LieAlgebra
1111

1212

1313
@testset "Test intersect of line and plane" begin
1414

15-
# Define plane
16-
floornorm = [0;0;1.]
17-
floorcenter = [0;0;5.]
18-
# Define ray
19-
raydir = [0;-1;-1.]
20-
raypnt = [0; 0;10.]
15+
# Define plane
16+
floornorm = [0;0;1.0]
17+
floorcenter = [0;0;5.0]
18+
# Define ray
19+
raydir = [0;-1;-1.0]
20+
raypnt = [0; 0;10.0]
2121

22-
pt = intersectLineToPlane3D(floornorm, floorcenter, raydir, raypnt)
23-
@test isapprox([0;-5;5.], pt)
22+
pt = intersectLineToPlane3D(floornorm, floorcenter, raydir, raypnt)
23+
@test isapprox([0;-5;5.0], pt)
2424

2525
end
2626

2727

2828
@testset "Test raytracing to plane" begin
2929

30-
M = SpecialEuclideanGroup(3; variant = :right)
31-
Mr = SpecialOrthogonalGroup(3)
32-
R0 = [1 0 0; 0 1 0; 0 0 1.]
33-
34-
35-
## Camera setup
36-
f = 800.0 # pixels
37-
ci,cj = 360,640 # assuming 720x1280 image
38-
# going from imaging array to camera frame
39-
c_H_a = [0 1 -cj; 1 0 -ci; 0 0 f] # camera matrix
40-
a_Forb = [360; 640; 1.0]
41-
l_nFL = [0; -0.05; 1.]
42-
l_FL = [0; 0; -2.]
43-
44-
# local level to body to extrinsic transform
45-
l_T_b = ArrayPartition([0;0;0.], R0)
46-
b_T_ex = ArrayPartition([0;0;0.], exp(Mr, hat(LieAlgebra(Mr), [0;0.2;0.2])))
47-
l_T_ex = compose(M, l_T_b, b_T_ex)
48-
49-
# Ray trace
50-
l_Forb = intersectRayToPlane(
51-
c_H_a,
52-
a_Forb,
53-
l_nFL,
54-
l_FL;
55-
l_T_ex
56-
)
57-
58-
59-
## Place the body somewhere in the world
60-
w_T_b = ArrayPartition([0.;0.;2.], LieGroups.exp(Mr, LieGroups.hat(LieAlgebra(Mr), [0;0;0.])))
61-
# find feature points in the world frame
62-
# _w_Forb = MJL.affine_matrix(M, w_T_b)*[l_Forb; 1.]
63-
64-
end
30+
M = SpecialEuclideanGroup(3; variant = :right)
31+
Mr = SpecialOrthogonalGroup(3)
32+
R0 = [1 0 0; 0 1 0; 0 0 1.0]
33+
34+
35+
## Camera setup
36+
f = 800.0 # pixels
37+
ci, cj = 360, 640 # assuming 720x1280 image
38+
# going from imaging array to camera frame
39+
c_H_a = [0 1 -cj; 1 0 -ci; 0 0 f] # camera matrix
40+
a_Forb = [360; 640; 1.0]
41+
l_nFL = [0; -0.05; 1.0]
42+
l_FL = [0; 0; -2.0]
43+
44+
# local level to body to extrinsic transform
45+
l_T_b = ArrayPartition([0;0;0.0], R0)
46+
b_T_ex = ArrayPartition([0;0;0.0], exp(Mr, hat(LieAlgebra(Mr), [0;0.2;0.2])))
47+
l_T_ex = compose(M, l_T_b, b_T_ex)
48+
49+
# Ray trace
50+
l_Forb = intersectRayToPlane(
51+
c_H_a,
52+
a_Forb,
53+
l_nFL,
54+
l_FL;
55+
l_T_ex
56+
)
57+
58+
59+
## Place the body somewhere in the world
60+
w_T_b = ArrayPartition([0.0;0.0;2.0], LieGroups.exp(Mr, LieGroups.hat(LieAlgebra(Mr), [0;0;0.0])))
61+
# find feature points in the world frame
62+
# _w_Forb = MJL.affine_matrix(M, w_T_b)*[l_Forb; 1.]
63+
64+
end
65+
66+
67+
@testset "radialDistortion! function test" begin
68+
69+
test_camera = CameraCalibration(
70+
height = 720,
71+
width = 1280,
72+
kc = SVector(-0.12, 0.03, 0.0008, -0.0006, -0.004),
73+
K = SMatrix{3, 3}(
74+
[
75+
800.0 0.0 360.0;
76+
0.0 800.0 640.0;
77+
0.0 0.0 1.0
78+
]
79+
)
80+
)
81+
82+
83+
src_mat = rand(Float32, 720, 1280)
84+
dst_mat = similar(src_mat)
85+
86+
CameraModels.radialDistortion!(test_camera, dst_mat, src_mat)
87+
88+
end

0 commit comments

Comments
 (0)