Skip to content

Commit 54ecbbc

Browse files
committed
[panorama] added basic image compensation and blending CLI options
1 parent 2897480 commit 54ecbbc

File tree

2 files changed

+73
-19
lines changed

2 files changed

+73
-19
lines changed

src/instrumentman/panorama/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ def cli_measure(**kwargs: Any) -> None:
174174
help="Axis-aligned camera offset from the instrument center",
175175
type=(float, float, float)
176176
)
177+
@option(
178+
"--compensation",
179+
help="Basic exposure compensation method",
180+
type=Choice(
181+
("none", "channels", "gain"),
182+
case_sensitive=False
183+
),
184+
default="channels"
185+
)
186+
@option(
187+
"--blending",
188+
help="Overlap blending method",
189+
type=Choice(
190+
("none", "multiband", "feather"),
191+
case_sensitive=False
192+
),
193+
default="multiband"
194+
)
177195
@option_group(
178196
"Output size options",
179197
(

src/instrumentman/panorama/process.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ def run_annotate(
166166
scale: float | None = None,
167167
points: list[tuple[str, Coordinate, str]] = [],
168168
camera_offset: Coordinate | None = None,
169+
compenstation_mode: int = cv.detail.EXPOSURE_COMPENSATOR_GAIN,
170+
blending_mode: int = cv.detail.BLENDER_MULTI_BAND,
169171
color: tuple[int, int, int] = (0, 0, 0),
170172
font: int = cv.FONT_HERSHEY_PLAIN,
171173
fontscale: float = 1,
@@ -195,8 +197,10 @@ def run_annotate(
195197
BarColumn(),
196198
MofNCompleteColumn(),
197199
TimeRemainingColumn(),
198-
console=console
200+
console=console,
201+
transient=True
199202
) as progress:
203+
warper: cv.PyRotationWarper | None = None
200204
for data in progress.track(
201205
meta["images"],
202206
description="Preprocessing images"
@@ -219,10 +223,13 @@ def run_annotate(
219223
height, width, _ = img.shape
220224
f_w: float = width / 2 / np.tan(fov_w / 2)
221225
f_h: float = height / 2 / np.tan(fov_h / 2)
222-
if scale is None:
223-
scale = (f_w + f_h) / 2
224226

225-
scale = min(scale, _MAX_SCALE)
227+
if warper is None:
228+
if scale is None:
229+
scale = (f_w + f_h) / 2
230+
231+
scale = min(scale, _MAX_SCALE)
232+
warper = cv.PyRotationWarper("spherical", scale)
226233

227234
instrinsics: npt.NDArray[np.float32] = np.array(
228235
(
@@ -236,7 +243,6 @@ def run_annotate(
236243
@ rot_x(np.pi / 2 - float(v))
237244
).astype("float32")
238245

239-
warper = cv.PyRotationWarper("spherical", scale)
240246
corner, image_warped = warper.warp(
241247
img,
242248
instrinsics,
@@ -278,26 +284,37 @@ def run_annotate(
278284
apply_rotation(pos - center, np.linalg.inv(offset_rot))
279285
)
280286

287+
console.print("Preprocessed images")
288+
281289
with Progress(transient=True) as progress:
282290
progress.add_task(description="Merging images...", total=None)
291+
compensator = cv.detail.ExposureCompensator.createDefault(
292+
compenstation_mode
293+
)
294+
if compenstation_mode != cv.detail.EXPOSURE_COMPENSATOR_NO:
295+
compensator.feed(
296+
corners,
297+
images_warped, # type: ignore[arg-type]
298+
masks_warped # type: ignore[arg-type]
299+
)
283300

284-
blender = cv.detail.Blender.createDefault(cv.detail.BLENDER_MULTI_BAND)
301+
blender = cv.detail.Blender.createDefault(blending_mode)
285302
blender.prepare(
286303
corners,
287304
[(i.shape[1], i.shape[0]) for i in images_warped]
288305
)
289-
for corner, img, msk in zip(corners, images_warped, masks_warped):
290-
dilated_mask = cv.dilate(msk, None) # type: ignore[call-overload]
291-
seam_mask = cv.resize(
292-
dilated_mask,
293-
(msk.shape[1], msk.shape[0]),
294-
None,
295-
0,
296-
0,
297-
cv.INTER_LINEAR_EXACT
298-
)
299-
msk_warped = cv.bitwise_and(seam_mask, msk)
300-
blender.feed(img.astype("int16"), msk_warped, corner)
306+
for i, (corner, img, msk) in enumerate(
307+
zip(corners, images_warped, masks_warped)
308+
):
309+
if compenstation_mode != cv.detail.EXPOSURE_COMPENSATOR_NO:
310+
img = compensator.apply(
311+
i,
312+
corner,
313+
img,
314+
msk
315+
)
316+
317+
blender.feed(img.astype("int16"), msk, corner)
301318

302319
result: cvt.MatLike
303320
result, _ = blender.blend(
@@ -395,7 +412,7 @@ def run_annotate(
395412
bottomLeftOrigin=False
396413
)
397414

398-
console.print("Annotated points")
415+
console.print("Annotated points")
399416

400417
with Progress(transient=True) as progress:
401418
progress.add_task("Saving final image...", total=None)
@@ -432,11 +449,27 @@ def run_annotate(
432449
}
433450

434451

452+
_COMP_MAP = {
453+
"none": cv.detail.EXPOSURE_COMPENSATOR_NO,
454+
"channels": cv.detail.EXPOSURE_COMPENSATOR_CHANNELS,
455+
"gain": cv.detail.EXPOSURE_COMPENSATOR_GAIN
456+
}
457+
458+
459+
_BLEND_MAP = {
460+
"none": cv.detail.BLENDER_NO,
461+
"multiband": cv.detail.BLENDER_MULTI_BAND,
462+
"feather": cv.detail.BLENDER_FEATHER
463+
}
464+
465+
435466
def main(
436467
metadata: Path,
437468
output: Path,
438469
image: tuple[Path],
439470
camera_offset: tuple[float, float, float] | None = None,
471+
compensation: str = "channel",
472+
blending: str = "multiband",
440473
scale: float | None = None,
441474
width: int | None = None,
442475
height: int | None = None,
@@ -529,6 +562,8 @@ def main(
529562
scale,
530563
points,
531564
cam_offset,
565+
_COMP_MAP[compensation],
566+
_BLEND_MAP[blending],
532567
color,
533568
_FONT_MAP[font],
534569
fontscale,
@@ -546,3 +581,4 @@ def main(
546581
)
547582
except cv.error as cve:
548583
echo_red(f"The process failed due to an OpenCV error ({cve.code})")
584+
echo_red(cve.err)

0 commit comments

Comments
 (0)