Skip to content

Commit

Permalink
Add support for HDR on the Camera Module 3
Browse files Browse the repository at this point in the history
  • Loading branch information
jwillikers committed Feb 25, 2024
1 parent 0bc7a14 commit 77ab615
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"randr",
"raspberrypi",
"rclone",
"subdev",
"subdevice",
"subdevices",
"Tailscale",
"tflite",
"Twidec",
Expand Down
35 changes: 35 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@ ln --force --relative --symbolic systemd/user/* ~/.config/systemd/user/
systemctl --user enable --now detectionator.service
----

== HDR

The Raspberry Pi Camera Module 3 supports HDR, but only at a lower resolution.
HDR support has to toggled when `detectionator.py` isn't running and requires passing a special flag to `detectionator.py`.

. Show the available V4L subdevices.
+
[,sh]
----
ls /dev/v4l-subdev*
/dev/v4l-subdev0 /dev/v4l-subdev1 /dev/v4l-subdev2 /dev/v4l-subdev3
----

. To enable HDR support for the Raspberry Pi Camera Module 3, use the following command on one of the V4L subdevices.
In my case, this ended up being `/dev/v4l-subdev2`.
+
[,sh]
----
v4l2-ctl --set-ctrl wide_dynamic_range=1 --device /dev/v4l-subdev2
----

. Now that the Camera Module 3 is configured, just run `detectionator.py` with the `--hdr` flag to default to the proper resolution sizes when HDR is enabled.
+
[,sh]
----
./detectionator.py --hdr
----

. To disable HDR support for the Raspberry Pi Camera Module 3, use this command with the corresponding V4L subdevice.
+
[,sh]
----
v4l2-ctl --set-ctrl wide_dynamic_range=0 --device /dev/v4l-subdev2
----

== Development

It's recommended to use the provided {pre-commit} checks when developing.
Expand Down
60 changes: 41 additions & 19 deletions detectionator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,14 @@


# todo Figure out the best values for the normal and low resolution sizes.
# normal_size = (640, 480)
# low_resolution_size = (320, 240)

# Camera Module 3:
# HDR disabled: 4608 x 2592
# HDR enabled: 2304 x 1296
#
# Enable:
# v4l2-ctl --set-ctrl wide_dynamic_range=1 -d /dev/v4l-subdev2
#
# Disable:
# v4l2-ctl --set-ctrl wide_dynamic_range=0 -d /dev/v4l-subdev2
#
normal_size = (4608, 2592)
# low_resolution_size = (576, 324)
# low_resolution_size = (1152, 648)
default_normal_resolution_size = (4608, 2592)
default_low_resolution_size = (2304, 1296)
default_normal_resolution_size_hdr = (2304, 1296)
default_low_resolution_size_hdr = (1152, 648)


def ReadLabelFile(file_path):
Expand Down Expand Up @@ -99,16 +90,27 @@ def main():
help="The device path for the GPS serial device.",
default="/dev/ttyUSBAdafruitUltimateGps",
)
parser.add_argument(
"--hdr",
action=argparse.BooleanOptionalAction,
help="This option uses the default resolution for the Camera Module 3 when in HDR mode.",
)
parser.add_argument("--label", help="Path of the labels file.")
parser.add_argument(
"--low-resolution-width",
help="The width to use for the low resolution size.",
default=default_low_resolution_size[0],
)
parser.add_argument(
"--low-resolution-height",
help="The height to use for the low resolution size.",
default=default_low_resolution_size[1],
)
parser.add_argument(
"--normal-resolution-width",
help="The width to use for the normal resolution size.",
)
parser.add_argument(
"--normal-resolution-height",
help="The height to use for the normal resolution size.",
)
parser.add_argument(
"--match", help="The labels for which to capture photographs", nargs="*"
Expand Down Expand Up @@ -136,12 +138,32 @@ def main():
if label_file:
labels = ReadLabelFile(label_file)

normal_resolution_size = default_normal_resolution_size
low_resolution_size = default_low_resolution_size
if args.hdr:
normal_resolution_size = default_normal_resolution_size_hdr
low_resolution_size = default_low_resolution_size_hdr
normal_resolution_width = normal_resolution_size[0]
normal_resolution_height = normal_resolution_size[1]
low_resolution_width = low_resolution_size[0]
low_resolution_height = low_resolution_size[1]

if args.normal_resolution_width:
normal_resolution_width = args.normal_resolution_width
if args.normal_resolution_height:
normal_resolution_height = args.normal_resolution_height

if args.low_resolution_width:
low_resolution_width = args.low_resolution_width
if args.low_resolution_height:
low_resolution_height = args.low_resolution_height

if (
normal_size[0] / args.low_resolution_width
!= normal_size[1] / args.low_resolution_height
normal_resolution_width / low_resolution_width
!= normal_resolution_height / low_resolution_height
):
print(
f"The low resolution width, '{args.low_resolution_width}', and low resolution height, '{args.low_resolution_height}' must be a fraction of the normal size, '{normal_size[0]}x{normal_size[1]}'"
f"The low resolution width, '{low_resolution_width}', and low resolution height, '{low_resolution_height}' must be a fraction of the normal size, '{normal_resolution_width}x{normal_resolution_height}'"
)
sys.exit(1)

Expand Down Expand Up @@ -175,12 +197,12 @@ def main():
picam2.options["quality"] = 95
picam2.options["compress_level"] = 9
config = picam2.create_still_configuration(
main={"size": normal_size},
main={"size": (normal_resolution_width, normal_resolution_height)},
lores={
# Only Pi 5 and newer can use formats besides YUV here.
# This avoids having to convert the image format for OpenCV later.
"format": "RGB888",
"size": (args.low_resolution_width, args.low_resolution_height),
"size": (low_resolution_width, low_resolution_height),
},
buffer_count=4,
# Don't display anything in the preview window since the system is running headless.
Expand Down

0 comments on commit 77ab615

Please sign in to comment.