pyopenrivercam 0.8.5__py3-none-any.whl → 0.8.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.6.dist-info}/METADATA +1 -1
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.6.dist-info}/RECORD +12 -12
- pyorc/__init__.py +1 -1
- pyorc/api/cameraconfig.py +32 -43
- pyorc/api/cross_section.py +1 -1
- pyorc/cli/cli_elements.py +11 -2
- pyorc/cli/cli_utils.py +60 -10
- pyorc/cli/main.py +18 -1
- pyorc/cv.py +76 -25
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.6.dist-info}/WHEEL +0 -0
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.6.dist-info}/entry_points.txt +0 -0
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
pyorc/__init__.py,sha256=
|
|
1
|
+
pyorc/__init__.py,sha256=wbzL3QPC8KrCa1QKwZs-oKJ3yeUhfDa8wLQMsRoKwyk,523
|
|
2
2
|
pyorc/const.py,sha256=Ia0KRkm-E1lJk4NxQVPDIfN38EBB7BKvxmwIHJrGPUY,2597
|
|
3
|
-
pyorc/cv.py,sha256=
|
|
3
|
+
pyorc/cv.py,sha256=qhz0y03k3CwMxFChodGW8kIcJNvWYJ4vFvQLj16JAr0,45841
|
|
4
4
|
pyorc/helpers.py,sha256=2HN9_NQ5wp1xVtHFcFm0Ri7mwAKYT8jlmWLQ45Xs9GY,29871
|
|
5
5
|
pyorc/plot_helpers.py,sha256=i6pcZHfpGCMkPNHWSkoE0N9-nuKfqXR7V5wgdT184IY,1274
|
|
6
6
|
pyorc/project.py,sha256=CGKfICkQEpFRmh_ZeDEfbQ-wefJt7teWJd6B5IPF038,7747
|
|
7
7
|
pyorc/pyorc.sh,sha256=-xOSUNnMAwVbdNkjKNKMZMaBljWsGLhadG-j0DNlJP4,5
|
|
8
8
|
pyorc/sample_data.py,sha256=53NVnVmEksDw8ilbfhFFCiFJiGAIpxdgREbA_xt8P3o,2508
|
|
9
9
|
pyorc/api/__init__.py,sha256=k2OQQH4NrtXTuVm23d0g_SX6H5DhnKC9_kDyzJ4dWdk,428
|
|
10
|
-
pyorc/api/cameraconfig.py,sha256=
|
|
11
|
-
pyorc/api/cross_section.py,sha256=
|
|
10
|
+
pyorc/api/cameraconfig.py,sha256=IyhaX5IZHOwY_CMjVQiYTKUWLyzpGXFfY_tT3aV1y8c,60979
|
|
11
|
+
pyorc/api/cross_section.py,sha256=p8DMFIAuxMzF6_9CYOtN4Fn25L1XAwslKJj5ovxouuw,46935
|
|
12
12
|
pyorc/api/frames.py,sha256=u0ZUgs-DKdWTUTPMrpMrHtV5VMeCfL3h5SKTfn88wsk,25754
|
|
13
13
|
pyorc/api/mask.py,sha256=HVag3RkMu4ZYQg_pIZFhiJYkBGYLVBxeefdmWvFTR-4,14371
|
|
14
14
|
pyorc/api/orcbase.py,sha256=C23QTKOyxHUafyJsq_t7xn_BzAEvf4DDfzlYAopons8,4189
|
|
@@ -17,18 +17,18 @@ pyorc/api/transect.py,sha256=KU0ZW_0NqYD4jeDxvuWJi7X06KqrcgO9afP7QmWuixA,14162
|
|
|
17
17
|
pyorc/api/velocimetry.py,sha256=bfU_XPbUbrdBI2XGprzh_3YADbGHfy4OuS1oBlbLEEI,12047
|
|
18
18
|
pyorc/api/video.py,sha256=lGD6bcV6Uu2u3zuGF_m3KxX2Cyp9k-YHUiXA42TOE3E,22458
|
|
19
19
|
pyorc/cli/__init__.py,sha256=A7hOQV26vIccPnDc8L2KqoJOSpMpf2PiMOXS18pAsWg,32
|
|
20
|
-
pyorc/cli/cli_elements.py,sha256=
|
|
21
|
-
pyorc/cli/cli_utils.py,sha256=
|
|
20
|
+
pyorc/cli/cli_elements.py,sha256=zX9wv9-1KWC_E3cInGMm3g9jh4uXmT2NqooAMhhXR9s,22165
|
|
21
|
+
pyorc/cli/cli_utils.py,sha256=uQ0I8wRMSJBOTvxxxMXSizDR7Qmn1KeaQwUHHhaQxU0,15090
|
|
22
22
|
pyorc/cli/log.py,sha256=Vg8GznmrEPqijfW6wv4OCl8R00Ld_fVt-ULTitaDijY,2824
|
|
23
|
-
pyorc/cli/main.py,sha256=
|
|
23
|
+
pyorc/cli/main.py,sha256=YtS_st119N-SFpRJDPv2O4ttBGhKRfH_DQSl3OZkNlA,12358
|
|
24
24
|
pyorc/service/__init__.py,sha256=vPrzFlZ4e_GjnibwW6-k8KDz3b7WpgmGcwSDk0mr13Y,55
|
|
25
25
|
pyorc/service/camera_config.py,sha256=OsRLpe5jd-lu6HT4Vx5wEg554CMS-IKz-q62ir4VbPo,2375
|
|
26
26
|
pyorc/service/velocimetry.py,sha256=bPEuy6TOpC22OY1E8gNnKSL0diF5ja2FtwNsj3-NdFg,29268
|
|
27
27
|
pyorc/velocimetry/__init__.py,sha256=lYM7oJZWxgJ2SpE22xhy7pBYcgkKFHMBHYmDvvMbtZk,148
|
|
28
28
|
pyorc/velocimetry/ffpiv.py,sha256=MW_6fQ0vxRTA-HYwncgeWHGWiUQFSmM4unYxT7EfnEI,7372
|
|
29
29
|
pyorc/velocimetry/openpiv.py,sha256=6BxsbXLzT4iEq7v08G4sOhVlYFodUpY6sIm3jdCxNMs,13149
|
|
30
|
-
pyopenrivercam-0.8.
|
|
31
|
-
pyopenrivercam-0.8.
|
|
32
|
-
pyopenrivercam-0.8.
|
|
33
|
-
pyopenrivercam-0.8.
|
|
34
|
-
pyopenrivercam-0.8.
|
|
30
|
+
pyopenrivercam-0.8.6.dist-info/entry_points.txt,sha256=Cv_WI2Y6QLnPiNCXGli0gS4WAOAeMoprha1rAR3vdRE,44
|
|
31
|
+
pyopenrivercam-0.8.6.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
32
|
+
pyopenrivercam-0.8.6.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
33
|
+
pyopenrivercam-0.8.6.dist-info/METADATA,sha256=xwryCN_ptr7C_o59bbD8NiXqv3ob549hIlgUYWatCRc,11513
|
|
34
|
+
pyopenrivercam-0.8.6.dist-info/RECORD,,
|
pyorc/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""pyorc: free and open-source image-based surface velocity and discharge."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.8.
|
|
3
|
+
__version__ = "0.8.6"
|
|
4
4
|
|
|
5
5
|
from .api import CameraConfig, CrossSection, Frames, Transect, Velocimetry, Video, get_camera_config, load_camera_config # noqa
|
|
6
6
|
from .project import * # noqa
|
pyorc/api/cameraconfig.py
CHANGED
|
@@ -47,7 +47,6 @@ class CameraConfig:
|
|
|
47
47
|
lens_position: Optional[List[float]] = None,
|
|
48
48
|
corners: Optional[List[List[float]]] = None,
|
|
49
49
|
gcps: Optional[Dict[str, Union[List, float]]] = None,
|
|
50
|
-
lens_pars: Optional[Dict[str, float]] = None,
|
|
51
50
|
calibration_video: Optional[str] = None,
|
|
52
51
|
is_nadir: Optional[bool] = False,
|
|
53
52
|
stabilize: Optional[List[List]] = None,
|
|
@@ -90,10 +89,6 @@ class CameraConfig:
|
|
|
90
89
|
the same vertical reference as the measured bathymetry and other survey points,
|
|
91
90
|
"crs": int, str or CRS object, CRS in which "dst" points are measured. If None, a local coordinate system is
|
|
92
91
|
assumed (e.g. from spirit level).
|
|
93
|
-
lens_pars : dict, optional
|
|
94
|
-
Lens parameters, containing: "k1": float, barrel lens distortion parameter (default: 0.),
|
|
95
|
-
"c": float, optical center (default: 2.),
|
|
96
|
-
"focal_length": float, focal length (default: width of image frame)
|
|
97
92
|
calibration_video : str, optional
|
|
98
93
|
local path to video file containing a checkerboard pattern. Must be 9x6 if called directly, otherwise use
|
|
99
94
|
``.calibrate_camera`` explicitly and provide ``chessboard_size`` explicitly. When used, an automated camera
|
|
@@ -141,13 +136,13 @@ class CameraConfig:
|
|
|
141
136
|
if self.is_nadir:
|
|
142
137
|
# with nadir, no perspective can be constructed, hence, camera matrix and dist coeffs will be set
|
|
143
138
|
# to default values
|
|
144
|
-
self.camera_matrix = cv.
|
|
139
|
+
self.camera_matrix = cv.get_cam_mtx(self.height, self.width)
|
|
145
140
|
self.dist_coeffs = cv.DIST_COEFFS
|
|
146
141
|
# camera pars are incomplete and need to be derived
|
|
147
142
|
else:
|
|
148
|
-
self.set_intrinsic(camera_matrix=camera_matrix
|
|
143
|
+
self.set_intrinsic(camera_matrix=camera_matrix)
|
|
149
144
|
else:
|
|
150
|
-
# camera matrix and dist coeffs can also be set hard
|
|
145
|
+
# camera matrix and dist coeffs can also be set hard
|
|
151
146
|
self.camera_matrix = camera_matrix
|
|
152
147
|
self.dist_coeffs = dist_coeffs
|
|
153
148
|
if calibration_video is not None:
|
|
@@ -201,6 +196,27 @@ class CameraConfig:
|
|
|
201
196
|
def dist_coeffs(self, dist_coeffs):
|
|
202
197
|
self._dist_coeffs = dist_coeffs.tolist() if isinstance(dist_coeffs, np.ndarray) else dist_coeffs
|
|
203
198
|
|
|
199
|
+
@property
|
|
200
|
+
def focal_length(self):
|
|
201
|
+
"""Get focal length."""
|
|
202
|
+
if not self.camera_matrix:
|
|
203
|
+
return None
|
|
204
|
+
return self.camera_matrix[0][0]
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def k1(self):
|
|
208
|
+
"""Get first distortion coefficient."""
|
|
209
|
+
if not self.dist_coeffs:
|
|
210
|
+
return None
|
|
211
|
+
return self.dist_coeffs[0]
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def k2(self):
|
|
215
|
+
"""Get second distortion coefficient."""
|
|
216
|
+
if not self.dist_coeffs:
|
|
217
|
+
return None
|
|
218
|
+
return self.dist_coeffs[1]
|
|
219
|
+
|
|
204
220
|
@property
|
|
205
221
|
def gcps_dest(self):
|
|
206
222
|
"""Get destination coordinates of GCPs.
|
|
@@ -855,7 +871,6 @@ class CameraConfig:
|
|
|
855
871
|
self,
|
|
856
872
|
camera_matrix: Optional[List[List]] = None,
|
|
857
873
|
dist_coeffs: Optional[List[List]] = None,
|
|
858
|
-
lens_pars: Optional[Dict[str, float]] = None,
|
|
859
874
|
):
|
|
860
875
|
"""Set lens and distortion parameters.
|
|
861
876
|
|
|
@@ -871,12 +886,13 @@ class CameraConfig:
|
|
|
871
886
|
Distortion coefficients to be used for the camera. If not provided, it will use default values or those
|
|
872
887
|
derived from GCPs if available.
|
|
873
888
|
|
|
874
|
-
lens_pars : Optional[Dict[str, float]]
|
|
875
|
-
Lens parameters to be set. These will override any default settings or those derived from GCPs if provided.
|
|
876
|
-
|
|
877
889
|
"""
|
|
878
|
-
|
|
879
|
-
|
|
890
|
+
if camera_matrix is not None and dist_coeffs is not None:
|
|
891
|
+
# both are provided by user, so no fitting needed
|
|
892
|
+
self.camera_matrix = camera_matrix
|
|
893
|
+
self.dist_coeffs = dist_coeffs
|
|
894
|
+
return
|
|
895
|
+
|
|
880
896
|
if hasattr(self, "gcps"):
|
|
881
897
|
if len(self.gcps["src"]) >= 4:
|
|
882
898
|
self.camera_matrix, self.dist_coeffs, err = cv.optimize_intrinsic(
|
|
@@ -886,36 +902,9 @@ class CameraConfig:
|
|
|
886
902
|
self.height,
|
|
887
903
|
self.width,
|
|
888
904
|
lens_position=self.lens_position,
|
|
905
|
+
camera_matrix=camera_matrix,
|
|
906
|
+
dist_coeffs=dist_coeffs,
|
|
889
907
|
)
|
|
890
|
-
if lens_pars is not None:
|
|
891
|
-
# override with lens parameter set by user
|
|
892
|
-
self.set_lens_pars(**lens_pars)
|
|
893
|
-
if camera_matrix is not None and dist_coeffs is not None:
|
|
894
|
-
# override with
|
|
895
|
-
self.camera_matrix = camera_matrix
|
|
896
|
-
self.dist_coeffs = dist_coeffs
|
|
897
|
-
|
|
898
|
-
def set_lens_pars(self, k1: Optional[float] = 0.0, c: Optional[float] = 2.0, focal_length: Optional[float] = None):
|
|
899
|
-
"""Set the lens parameters of the given CameraConfig.
|
|
900
|
-
|
|
901
|
-
Parameters
|
|
902
|
-
----------
|
|
903
|
-
k1 : float, optional
|
|
904
|
-
lens curvature [-], zero (default) means no curvature
|
|
905
|
-
c : float, optional
|
|
906
|
-
optical centre [1/n], where n is the fraction of the lens diameter, 2.0 (default) means in the
|
|
907
|
-
centre.
|
|
908
|
-
focal_length : float, optional
|
|
909
|
-
focal length [mm], typical values could be 2.8, or 4 (default).
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
"""
|
|
913
|
-
assert isinstance(k1, (int, float)), "k1 must be a float"
|
|
914
|
-
assert isinstance(c, (int, float)), "c must be a float"
|
|
915
|
-
if focal_length is not None:
|
|
916
|
-
assert isinstance(focal_length, (int, float, None)), "f must be a float"
|
|
917
|
-
self.dist_coeffs = cv._get_dist_coefs(k1)
|
|
918
|
-
self.camera_matrix = cv._get_cam_mtx(self.height, self.width, c=c, focal_length=focal_length)
|
|
919
908
|
|
|
920
909
|
def set_gcps(
|
|
921
910
|
self, src: List[List], dst: List[List], z_0: float, h_ref: Optional[float] = None, crs: Optional[Any] = None
|
pyorc/api/cross_section.py
CHANGED
|
@@ -986,7 +986,7 @@ class CrossSection:
|
|
|
986
986
|
lens_position_xy = self.camera_config.estimate_lens_position()[0:2]
|
|
987
987
|
dists = [((p.x - lens_position_xy[0]) ** 2 + (p.y - lens_position_xy[1]) ** 2) ** 0.5 for p in points]
|
|
988
988
|
points = self.get_csl_point(h=h, camera=True, swap_y_coords=swap_y_coords) # find camera positions
|
|
989
|
-
x, y = points[np.
|
|
989
|
+
x, y = points[np.argmax(dists)].xy
|
|
990
990
|
x, y = float(x[0]), float(y[0])
|
|
991
991
|
|
|
992
992
|
# only plot text in 2D camera perspective at farthest point
|
pyorc/cli/cli_elements.py
CHANGED
|
@@ -360,7 +360,7 @@ class AoiSelect(BaseSelect):
|
|
|
360
360
|
class GcpSelect(BaseSelect):
|
|
361
361
|
"""Selector tool to provide source GCP coordinates to pyOpenRiverCam."""
|
|
362
362
|
|
|
363
|
-
def __init__(self, img, dst, crs=None, lens_position=None, logger=logging):
|
|
363
|
+
def __init__(self, img, dst, crs=None, camera_matrix=None, dist_coeffs=None, lens_position=None, logger=logging):
|
|
364
364
|
"""Set up interactive GCP selection plot."""
|
|
365
365
|
super(GcpSelect, self).__init__(img, dst, crs=crs, logger=logger)
|
|
366
366
|
# make empty plot
|
|
@@ -389,6 +389,8 @@ class GcpSelect(BaseSelect):
|
|
|
389
389
|
self.ax_geo.legend()
|
|
390
390
|
self.ax.legend()
|
|
391
391
|
self.lens_position = lens_position
|
|
392
|
+
self.camera_matrix_input = camera_matrix
|
|
393
|
+
self.dist_coeffs_input = dist_coeffs
|
|
392
394
|
# add dst coords in the intended CRS
|
|
393
395
|
if crs is not None and use_cartopy:
|
|
394
396
|
self.dst_crs = helpers.xyz_transform(self.dst, 4326, crs)
|
|
@@ -407,7 +409,14 @@ class GcpSelect(BaseSelect):
|
|
|
407
409
|
self.title.set_text("Fitting pose and camera parameters...")
|
|
408
410
|
self.ax.figure.canvas.draw()
|
|
409
411
|
src_fit, dst_fit, camera_matrix, dist_coeffs, rvec, tvec, err = cli_utils.get_gcps_optimized_fit(
|
|
410
|
-
self.src,
|
|
412
|
+
self.src,
|
|
413
|
+
self.dst_crs,
|
|
414
|
+
self.height,
|
|
415
|
+
self.width,
|
|
416
|
+
c=2.0,
|
|
417
|
+
camera_matrix=self.camera_matrix_input,
|
|
418
|
+
dist_coeffs=self.dist_coeffs_input,
|
|
419
|
+
lens_position=self.lens_position,
|
|
411
420
|
)
|
|
412
421
|
self.p_fit.set_data(*list(zip(*src_fit)))
|
|
413
422
|
self.camera_matrix = camera_matrix
|
pyorc/cli/cli_utils.py
CHANGED
|
@@ -4,6 +4,7 @@ import hashlib
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
from typing import Optional
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import cv2
|
|
@@ -57,15 +58,39 @@ def get_corners_interactive(
|
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def get_gcps_interactive(
|
|
60
|
-
fn,
|
|
61
|
+
fn,
|
|
62
|
+
dst,
|
|
63
|
+
crs=None,
|
|
64
|
+
crs_gcps=None,
|
|
65
|
+
frame_sample=0,
|
|
66
|
+
focal_length=None,
|
|
67
|
+
k1=None,
|
|
68
|
+
k2=None,
|
|
69
|
+
lens_position=None,
|
|
70
|
+
rotation=None,
|
|
71
|
+
logger=logging,
|
|
61
72
|
):
|
|
62
73
|
"""Select GCP points in interactive display using first selected video frame."""
|
|
63
74
|
vid = Video(fn, start_frame=frame_sample, end_frame=frame_sample + 1, rotation=rotation)
|
|
64
75
|
# get first frame
|
|
65
76
|
frame = vid.get_frame(0, method="rgb")
|
|
77
|
+
# construct camera matrix and distortion coefficients
|
|
78
|
+
# parse items
|
|
79
|
+
camera_matrix, dist_coeffs = parse_lens_params(
|
|
80
|
+
height=frame.shape[0], width=frame.shape[1], focal_length=focal_length, k1=k1, k2=k2
|
|
81
|
+
)
|
|
82
|
+
|
|
66
83
|
if crs_gcps is not None:
|
|
67
84
|
dst = helpers.xyz_transform(dst, crs_from=crs_gcps, crs_to=4326)
|
|
68
|
-
selector = GcpSelect(
|
|
85
|
+
selector = GcpSelect(
|
|
86
|
+
frame,
|
|
87
|
+
dst,
|
|
88
|
+
crs=crs,
|
|
89
|
+
camera_matrix=camera_matrix,
|
|
90
|
+
dist_coeffs=dist_coeffs,
|
|
91
|
+
lens_position=lens_position,
|
|
92
|
+
logger=logger,
|
|
93
|
+
)
|
|
69
94
|
plt.show(block=True)
|
|
70
95
|
return selector.src, selector.camera_matrix, selector.dist_coeffs
|
|
71
96
|
|
|
@@ -90,21 +115,16 @@ def get_file_hash(fn):
|
|
|
90
115
|
return hash256
|
|
91
116
|
|
|
92
117
|
|
|
93
|
-
def get_gcps_optimized_fit(src, dst, height, width, c=2.0, lens_position=None):
|
|
118
|
+
def get_gcps_optimized_fit(src, dst, height, width, c=2.0, camera_matrix=None, dist_coeffs=None, lens_position=None):
|
|
94
119
|
"""Fit intrinsic and extrinsic parameters on provided set of src and dst points."""
|
|
95
120
|
# optimize cam matrix and dist coeffs with provided control points
|
|
96
121
|
if np.array(dst).shape == (4, 2):
|
|
97
122
|
_dst = np.c_[np.array(dst), np.zeros(4)]
|
|
98
123
|
else:
|
|
99
124
|
_dst = np.array(dst)
|
|
125
|
+
print(camera_matrix)
|
|
100
126
|
camera_matrix, dist_coeffs, err = cv.optimize_intrinsic(
|
|
101
|
-
src,
|
|
102
|
-
_dst,
|
|
103
|
-
height,
|
|
104
|
-
width,
|
|
105
|
-
c=c,
|
|
106
|
-
lens_position=lens_position,
|
|
107
|
-
# dist_coeffs=cv.DIST_COEFFS
|
|
127
|
+
src, _dst, height, width, c=c, lens_position=lens_position, camera_matrix=camera_matrix, dist_coeffs=dist_coeffs
|
|
108
128
|
)
|
|
109
129
|
# once optimized, solve the perspective, and estimate the GCP locations with the perspective rot/trans
|
|
110
130
|
coord_mean = np.array(_dst).mean(axis=0)
|
|
@@ -152,6 +172,29 @@ def parse_corners(ctx, param, value):
|
|
|
152
172
|
return [[int(x), int(y)] for x, y in corners]
|
|
153
173
|
|
|
154
174
|
|
|
175
|
+
def parse_lens_params(
|
|
176
|
+
height: int,
|
|
177
|
+
width: int,
|
|
178
|
+
focal_length: Optional[float] = None,
|
|
179
|
+
k1: Optional[float] = None,
|
|
180
|
+
k2: Optional[float] = None,
|
|
181
|
+
):
|
|
182
|
+
"""Parse lens parameters to camera matrix and distortion coefficients vector."""
|
|
183
|
+
if focal_length is not None:
|
|
184
|
+
camera_matrix = cv.get_cam_mtx(height, width, c=2.0, focal_length=focal_length)
|
|
185
|
+
else:
|
|
186
|
+
camera_matrix = None
|
|
187
|
+
if k1 is not None or k2 is not None:
|
|
188
|
+
dist_coeffs = cv.DIST_COEFFS.copy()
|
|
189
|
+
if k1 is not None:
|
|
190
|
+
dist_coeffs[0][0] = k1
|
|
191
|
+
if k2 is not None:
|
|
192
|
+
dist_coeffs[1][0] = k2
|
|
193
|
+
else:
|
|
194
|
+
dist_coeffs = None
|
|
195
|
+
return camera_matrix, dist_coeffs
|
|
196
|
+
|
|
197
|
+
|
|
155
198
|
def validate_file(ctx, param, value):
|
|
156
199
|
"""Validate existence of file."""
|
|
157
200
|
if value is not None:
|
|
@@ -283,6 +326,13 @@ def read_shape_as_gdf(fn=None, geojson=None, gdf=None):
|
|
|
283
326
|
else:
|
|
284
327
|
gdf = gpd.read_file(fn)
|
|
285
328
|
crs = gdf.crs if hasattr(gdf, "crs") else None
|
|
329
|
+
# also read raw json, and check if crs attribute exists
|
|
330
|
+
with open(fn, "r") as f:
|
|
331
|
+
raw_json = json.load(f)
|
|
332
|
+
if "crs" not in raw_json:
|
|
333
|
+
# override the crs
|
|
334
|
+
crs = None
|
|
335
|
+
gdf = gdf.set_crs(None, allow_override=True)
|
|
286
336
|
# check if all geometries are points
|
|
287
337
|
assert all([isinstance(geom, Point) for geom in gdf.geometry]), (
|
|
288
338
|
"shapefile may only contain geometries of type " '"Point"'
|
pyorc/cli/main.py
CHANGED
|
@@ -99,6 +99,17 @@ def cli(ctx, info, license):
|
|
|
99
99
|
help="Coordinate reference system in which destination GCP points (--dst) are measured",
|
|
100
100
|
)
|
|
101
101
|
@click.option("--resolution", type=float, help="Target resolution [m] for ortho-projection.")
|
|
102
|
+
@click.option("--focal_length", type=float, help="Focal length [pix] of lens.")
|
|
103
|
+
@click.option(
|
|
104
|
+
"--k1",
|
|
105
|
+
type=float,
|
|
106
|
+
help="First lens radial distortion coefficient k1 [-]. See also https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html",
|
|
107
|
+
)
|
|
108
|
+
@click.option(
|
|
109
|
+
"--k2",
|
|
110
|
+
type=float,
|
|
111
|
+
help="Second lens radial distortion coefficient k2 [-]. See also https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html",
|
|
112
|
+
)
|
|
102
113
|
@click.option(
|
|
103
114
|
"--window_size", type=int, help="Target window size [px] for interrogation window for Particle Image Velocimetry"
|
|
104
115
|
)
|
|
@@ -148,6 +159,9 @@ def camera_config(
|
|
|
148
159
|
z_0: Optional[float],
|
|
149
160
|
h_ref: Optional[float],
|
|
150
161
|
crs_gcps: Optional[Union[str, int]],
|
|
162
|
+
focal_length: Optional[float],
|
|
163
|
+
k1: Optional[float],
|
|
164
|
+
k2: Optional[float],
|
|
151
165
|
resolution: Optional[float],
|
|
152
166
|
window_size: Optional[int],
|
|
153
167
|
lens_position: Optional[List[float]],
|
|
@@ -207,6 +221,9 @@ def camera_config(
|
|
|
207
221
|
crs=crs,
|
|
208
222
|
crs_gcps=crs_gcps,
|
|
209
223
|
frame_sample=frame_sample,
|
|
224
|
+
focal_length=focal_length,
|
|
225
|
+
k1=k1,
|
|
226
|
+
k2=k2,
|
|
210
227
|
lens_position=lens_position,
|
|
211
228
|
rotation=rotation,
|
|
212
229
|
logger=logger,
|
|
@@ -297,7 +314,7 @@ def camera_config(
|
|
|
297
314
|
type=click.Path(exists=True, resolve_path=True, dir_okay=False, file_okay=True),
|
|
298
315
|
help="Cross section file (*.geojson). If you provide this, you may add water level retrieval settings to the"
|
|
299
316
|
" recipe section video: water_level: For more information see"
|
|
300
|
-
" https://localdevices.github.io/pyorc/user-guide/video/index.html",
|
|
317
|
+
" [PyORC docs](https://localdevices.github.io/pyorc/user-guide/video/index.html)",
|
|
301
318
|
callback=cli_utils.parse_cross_section_gdf,
|
|
302
319
|
required=False,
|
|
303
320
|
)
|
pyorc/cv.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import os
|
|
5
|
+
import warnings
|
|
5
6
|
|
|
6
7
|
import cv2
|
|
7
8
|
import numpy as np
|
|
@@ -151,7 +152,7 @@ def _get_dist_coefs(k1):
|
|
|
151
152
|
return dist
|
|
152
153
|
|
|
153
154
|
|
|
154
|
-
def
|
|
155
|
+
def get_cam_mtx(height, width, c=2.0, focal_length=None):
|
|
155
156
|
"""Compute camera matrix based on the given parameters for height, width, scaling factor, and focal length.
|
|
156
157
|
|
|
157
158
|
Parameters
|
|
@@ -958,7 +959,7 @@ def get_polygon_pixels(img, pol, reverse_y=False):
|
|
|
958
959
|
return img[mask == 255]
|
|
959
960
|
|
|
960
961
|
|
|
961
|
-
def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
962
|
+
def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None, camera_matrix=None, dist_coeffs=None):
|
|
962
963
|
"""Optimize the intrinsic parameters of a camera model.
|
|
963
964
|
|
|
964
965
|
The function finds optimal intrinsic camera parameters, including focal length and distortion coefficients, by
|
|
@@ -980,6 +981,11 @@ def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
980
981
|
Center parameter of the camera matrix.
|
|
981
982
|
lens_position : array_like, optional
|
|
982
983
|
The assumed position of the lens in the 3D space.
|
|
984
|
+
camera_matrix : Optional[List[List]]
|
|
985
|
+
Predefined camera matrix. If not provided focal length will be fitted and camera matrix returned
|
|
986
|
+
dist_coeffs : Optional[List[List]]
|
|
987
|
+
Distortion coefficients to be used for the camera. If not provided, the first two (k1, k2)
|
|
988
|
+
distortion coefficients are fitted on data.
|
|
983
989
|
|
|
984
990
|
Returns
|
|
985
991
|
-------
|
|
@@ -989,7 +995,7 @@ def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
989
995
|
|
|
990
996
|
"""
|
|
991
997
|
|
|
992
|
-
def error_intrinsic(x, src, dst, height, width, c=2.0, lens_position=None, dist_coeffs=
|
|
998
|
+
def error_intrinsic(x, src, dst, height, width, c=2.0, lens_position=None, camera_matrix=None, dist_coeffs=None):
|
|
993
999
|
"""Compute the reprojection error for the intrinsic parameters of a camera model.
|
|
994
1000
|
|
|
995
1001
|
This function optimizes for the focal length and first two distortion coefficients based on the source and
|
|
@@ -1014,6 +1020,8 @@ def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
1014
1020
|
center parameter of camera matrix.
|
|
1015
1021
|
lens_position : array_like, optional
|
|
1016
1022
|
The assumed position of the lens in the 3D space.
|
|
1023
|
+
camera_matrix : array_like, optional
|
|
1024
|
+
camera matrix [3x3]
|
|
1017
1025
|
dist_coeffs : array_like, optional
|
|
1018
1026
|
Distortion coefficients.
|
|
1019
1027
|
|
|
@@ -1024,24 +1032,43 @@ def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
1024
1032
|
the camera position error if the lens position is provided.
|
|
1025
1033
|
|
|
1026
1034
|
"""
|
|
1035
|
+
param_nr = 0
|
|
1036
|
+
# set the parameters
|
|
1037
|
+
if camera_matrix is None:
|
|
1038
|
+
f = x[param_nr] * width
|
|
1039
|
+
camera_matrix_sample = get_cam_mtx(height, width, c=c, focal_length=f)
|
|
1040
|
+
param_nr += 1
|
|
1041
|
+
else:
|
|
1042
|
+
# take the existing camera matrix
|
|
1043
|
+
camera_matrix_sample = camera_matrix.copy()
|
|
1044
|
+
if dist_coeffs is None:
|
|
1045
|
+
dist_coeffs_sample = DIST_COEFFS.copy()
|
|
1046
|
+
k1 = x[param_nr]
|
|
1047
|
+
k2 = x[param_nr + 1]
|
|
1048
|
+
dist_coeffs_sample[0][0] = k1
|
|
1049
|
+
dist_coeffs_sample[1][0] = k2
|
|
1050
|
+
else:
|
|
1051
|
+
# take the existing distortion coefficients
|
|
1052
|
+
dist_coeffs_sample = dist_coeffs.copy()
|
|
1053
|
+
k1 = dist_coeffs_sample[0][0]
|
|
1054
|
+
k2 = dist_coeffs_sample[1][0]
|
|
1055
|
+
|
|
1056
|
+
# initialize error
|
|
1027
1057
|
err = 100
|
|
1028
1058
|
cam_err = None
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
dist_coeffs[1][0] = float(x[2])
|
|
1032
|
-
# dist_coeffs[4][0] = float(x[3])
|
|
1033
|
-
# dist_coeffs[3][0] = float(x[4])
|
|
1059
|
+
|
|
1060
|
+
# reduce problem space to centered around gcp average
|
|
1034
1061
|
coord_mean = np.array(dst).mean(axis=0)
|
|
1035
1062
|
_dst = np.float64(np.array(dst) - coord_mean)
|
|
1036
1063
|
zs = np.zeros(4) if len(_dst[0]) == 2 else np.array(_dst)[:, -1]
|
|
1037
1064
|
if lens_position is not None:
|
|
1038
1065
|
_lens_pos = np.array(lens_position) - coord_mean
|
|
1039
1066
|
|
|
1040
|
-
camera_matrix = _get_cam_mtx(height, width, c=c, focal_length=f)
|
|
1041
|
-
success, rvec, tvec = solvepnp(_dst, src,
|
|
1067
|
+
# camera_matrix = _get_cam_mtx(height, width, c=c, focal_length=f)
|
|
1068
|
+
success, rvec, tvec = solvepnp(_dst, src, camera_matrix_sample, dist_coeffs_sample)
|
|
1042
1069
|
if success:
|
|
1043
1070
|
# estimate destination locations from pose
|
|
1044
|
-
dst_est = unproject_points(src, zs, rvec, tvec,
|
|
1071
|
+
dst_est = unproject_points(src, zs, rvec, tvec, camera_matrix_sample, dist_coeffs_sample)
|
|
1045
1072
|
# src_est = np.array([list(point[0]) for point in src_est])
|
|
1046
1073
|
dist_xy = np.array(_dst)[:, 0:2] - np.array(dst_est)[:, 0:2]
|
|
1047
1074
|
dist = (dist_xy**2).sum(axis=1) ** 0.5
|
|
@@ -1054,27 +1081,51 @@ def optimize_intrinsic(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
1054
1081
|
err = float(0.1 * cam_err + gcp_err) if cam_err is not None else gcp_err
|
|
1055
1082
|
return err # assuming gcp pixel distance is about 5 cm
|
|
1056
1083
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1084
|
+
# determine optimization bounds
|
|
1085
|
+
bounds = []
|
|
1086
|
+
if camera_matrix is not None and dist_coeffs is not None:
|
|
1087
|
+
# both are already known, so nothing to do
|
|
1088
|
+
return camera_matrix, dist_coeffs, None
|
|
1089
|
+
if camera_matrix is None:
|
|
1090
|
+
bounds.append([float(0.25), float(2)])
|
|
1091
|
+
if len(dst) > 4 and dist_coeffs is None:
|
|
1092
|
+
bounds.append([-0.9, 0.9]) # k1
|
|
1093
|
+
bounds.append([-0.5, 0.5]) # k2
|
|
1060
1094
|
else:
|
|
1061
|
-
#
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1095
|
+
# set a warning if dist_coeffs is provided without sufficient ground control
|
|
1096
|
+
if dist_coeffs:
|
|
1097
|
+
warnings.warn(
|
|
1098
|
+
"You are trying to optimize distortion coefficients with only 4 GCPs. "
|
|
1099
|
+
"This would lead to overfitting, setting distortion coefficients to zero.",
|
|
1100
|
+
stacklevel=2,
|
|
1101
|
+
)
|
|
1102
|
+
dist_coeffs = DIST_COEFFS.copy()
|
|
1103
|
+
# if len(dst) == 4:
|
|
1104
|
+
# bnds_k1 = (-0.0, 0.0)
|
|
1105
|
+
# bnds_k2 = (-0.0, 0.0)
|
|
1106
|
+
# else:
|
|
1107
|
+
# # bnds_k1 = (-0.2501, -0.25)
|
|
1108
|
+
# bnds_k1 = (-0.9, 0.9)
|
|
1109
|
+
# bnds_k2 = (-0.5, 0.5)
|
|
1110
|
+
# bnds_k1 = (-0.0, 0.0)
|
|
1111
|
+
# bnds_k2 = (-0.0, 0.0)
|
|
1066
1112
|
opt = optimize.differential_evolution(
|
|
1067
1113
|
error_intrinsic,
|
|
1068
1114
|
# bounds=[(float(0.25), float(2)), bnds_k1],#, (-0.5, 0.5)],
|
|
1069
|
-
bounds=
|
|
1115
|
+
bounds=bounds,
|
|
1070
1116
|
# bounds=[(1710./width, 1714./width), bnds_k1, bnds_k2],
|
|
1071
|
-
args=(src, dst, height, width, c, lens_position,
|
|
1117
|
+
args=(src, dst, height, width, c, lens_position, camera_matrix, dist_coeffs),
|
|
1072
1118
|
atol=0.001, # one mm
|
|
1073
1119
|
)
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1120
|
+
param_nr = 0
|
|
1121
|
+
if camera_matrix is None:
|
|
1122
|
+
camera_matrix = get_cam_mtx(height, width, focal_length=opt.x[param_nr] * width)
|
|
1123
|
+
# move to next parameter
|
|
1124
|
+
param_nr += 1
|
|
1125
|
+
if dist_coeffs is None:
|
|
1126
|
+
dist_coeffs = DIST_COEFFS
|
|
1127
|
+
dist_coeffs[0][0] = opt.x[param_nr]
|
|
1128
|
+
dist_coeffs[1][0] = opt.x[param_nr + 1]
|
|
1078
1129
|
# dist_coeffs[4][0] = opt.x[3]
|
|
1079
1130
|
# dist_coeffs[3][0] = opt.x[4]
|
|
1080
1131
|
# print(f"CAMERA MATRIX: {camera_matrix}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|