Skip to content

Instantly share code, notes, and snippets.

@Hermann-SW
Last active March 28, 2025 13:54
Show Gist options
  • Save Hermann-SW/e6049fe1a24fc2b5a53c654e0e9f6b9c to your computer and use it in GitHub Desktop.
Save Hermann-SW/e6049fe1a24fc2b5a53c654e0e9f6b9c to your computer and use it in GitHub Desktop.
tool for playing with Raspberry Pi Global Shutter Camera crop values
#!/bin/bash
# shellcheck disable=SC2154
# (silence shellcheck wrt $cam1 environment variable)
if [[ $# -lt 4 ]]; then echo "Format: [narrow=1] [cam1=1] $0 width height framerate ms [us]"; exit; fi
if [[ "$(( $1 % 2 ))" -eq 1 ]]; then echo "width has to be even"; exit; fi
if [[ "$(( $2 % 2 ))" -eq 1 ]]; then echo "height has to be even"; exit; fi
export SHTR=""; if [[ $# -gt 4 ]]; then SHTR="--shutter"; fi
export workaround=""; if [[ "" != "$(grep '=bookworm' /etc/os-release)" ]]; then workaround="--no-raw"; fi
export d=10; if [[ "" != "$(grep "Revision.*: ...17.$" /proc/cpuinfo)" ]]; then if [[ "$cam1" == "" ]]; then d=10; else d=11; fi; fi
for((m=0; m<=5; ++m))
do
media-ctl -d /dev/media$m --set-v4l2 "'imx296 $d-001a':0 [fmt:SBGGR10_1X10/${1}x${2} crop:($(( (1440 - $1) / 2 )),$(( (1088 - $2) / 2 )))/${1}x$2]" -v; [ $? -eq 0 ] && break
done
libcamera-hello --list-cameras ;echo
rm -f /dev/shm/tst.pts
if [[ "" != "$(grep "Revision.*: ...17.$" /proc/cpuinfo)" ]]
then
rpicam-vid "$workaround" ${cam1:+--camera 1} --width "$1" --height "$2" --denoise cdn_off --framerate "$3" -t "$4" "$SHTR" "$5" -o /dev/shm/tst${cam1:+1}.mp4 -n ;echo
~/venv/bin/python ~/rpicam-apps/utils/timestamp.py --plot ${narrow:+--narrow} /dev/shm/tst${cam1:+1}.mp4
else
libcamera-vid "$workaround" --width "$1" --height "$2" --denoise cdn_off --framerate "$3" --save-pts /dev/shm/tst.pts -t "$4" "$SHTR" "$5" -o /dev/shm/tst.h264 -n ;echo
rm -f tstamps.csv && ptsanalyze /dev/shm/tst.pts
fi
@Hermann-SW
Copy link
Author

Hermann-SW commented Jan 6, 2024

Pull request got accepted, then I did delete my fork.
So tool with "--narrow" option can be found in official repo:
https://github.com/raspberrypi/rpicam-apps/blob/main/utils/timestamp.py

"--narrow" help text got improved in the process:

pi@raspberrypi5:~ $ ~/venv/bin/python ~/rpicam-apps/utils/timestamp.py -h 
usage: timestamp.py [-h] [--plot] [--narrow] filename

rpicam-apps timestamp analysis tool
...
options:
  -h, --help  show this help message and exit
  --plot      Plot timestamp graph
  --narrow    Narrow the y-axis by hiding outliers
pi@raspberrypi5:~ $ 

 
 
For a 60s duration 688x136@400fps "center of analog clock" recording ...
https://stamm-wilbrandt.de/videos/688x136.400fps_60s.mp4
https://forums.raspberrypi.com/download/file.php?id=64737
 
... with a single frameskip ...

pi@raspberrypi5:~ $ wget 2>/dev/null https://stamm-wilbrandt.de/videos/688x136.400fps_60s.mp4
pi@raspberrypi5:~ $ ~/venv/bin/python ~/rpicam-apps/utils/timestamp.py --plot --narrow 688x136.400fps_60s.mp4
Total: 24092 frames (24091 samples)
Average: 2.489 ms / 401.768 fps
Minimum: 2.475 ms at frame 11943
Maximum: 4.977 ms at frame 14847
Outliers: 0 (100%) 1 (10.0%) 1 (1.0%) 369 (0.1%)

 
...the average line is now in vertical middle:
https://forums.raspberrypi.com/viewtopic.php?p=2175722#p2175722
https://forums.raspberrypi.com/download/file.php?id=64736

@Hermann-SW
Copy link
Author

Hermann-SW commented Jan 19, 2024

The longer Pi5 is up, the more frameskips are likely to happen.
Simple countermeasure:
reboot before final recording

In order to get better timestamp.py analysis with "--narrow" option in case frameskips are likely, I added "narrow" environment variable handling to GScrop:

pi@raspberrypi5:~ $ uptime
 21:11:06 up 4 days, 19:22,  2 users,  load average: 0.60, 0.57, 0.38
pi@raspberrypi5:~ $ 
pi@raspberrypi5:~ $ narrow=1 GScrop 224 96 536 2000
/dev/media2
...
[libx264 @ 0x555666219830] kb/s:607.83

Total: 1054 frames (1053 samples)
Average: 1.870 ms / 534.697 fps
Minimum: 1.861 ms at frame 146
Maximum: 3.737 ms at frame 129
Outliers: 0 (100%) 2 (10.0%) 2 (1.0%) 1022 (0.1%)

@ddbaron
Copy link

ddbaron commented Feb 26, 2024

I'd like to suggest incorporating the detection of the bus used by the camera before calling media-ctl.

I use this command to parse out the definition:
media-ctl -d /dev/media0 --print-dot | grep -o "imx296 [0-9]-001a" | uniq

@Hermann-SW
Copy link
Author

Hermann-SW commented Feb 26, 2024

Hi,
I will receive my 2nd GS camera tomorrow ;-)
Then I can verify and will incorporate your suggestion.
I want to see whether Pi5 can record both cameras @536fps concurrently — have you already tested that?

@ddbaron
Copy link

ddbaron commented Feb 26, 2024

Updated 28/feb/2024: A bit more testing. I have two GS cameras but they're wired in synchronous mode so I needed to hack gscrop to handle it.

@liyangyuan
Copy link

This is a fantastic tool! Thank you.
By the way, I'm not sure if I'm an outlier or not, but I want to provide some information. I've discovered that I need to use d=6. Additionally, the conditional statement for determination of d doesn't seem to work for my Raspberry 5 8GB.
image
image

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 9, 2024

Latest revision added dual camera handling for Pi5:

pi@raspberrypi5:~ $ GScrop 
Format: [narrow=1] [cam1=1] /usr/local/bin/GScrop width height framerate ms [us]
pi@raspberrypi5:~ $ 

Default is to use camera 0:

pi@raspberrypi5:~ $ narrow=1 GScrop 688 136 402 1000
/dev/media0
...
[libx264 @ 0x5556652764a0] kb/s:1641.32

Total: 383 frames (382 samples)
Average: 2.500 ms / 400.004 fps
Minimum: 2.469 ms at frame 125
Maximum: 9.895 ms at frame 246
Outliers: 1 (100%) 2 (10.0%) 314 (1.0%) 382 (0.1%)
pi@raspberrypi5:~ $

Here camera 1 is used:

pi@raspberrypi5:~ $ cam1=foobar GScrop 688 136 402 1000
/dev/media1
...
[libx264 @ 0x5555dc266710] kb/s:1369.74

Total: 390 frames (389 samples)
Average: 2.474 ms / 404.186 fps
Minimum: 2.468 ms at frame 139
Maximum: 2.481 ms at frame 140
Outliers: 0 (100%) 0 (10.0%) 0 (1.0%) 5 (0.1%)
pi@raspberrypi5:~ $ 

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 9, 2024

Revision 11 clean again:

pi@raspberrypi5:~ $ shellcheck /usr/local/bin/GScrop 
pi@raspberrypi5:~ $ 

@Hermann-SW
Copy link
Author

Hermann-SW commented Apr 29, 2024

@Hermann-SW
Copy link
Author

GScrop revision 12 errors out on odd width or odd height.
Reason is that recording with those values would error out later anyway:

pi@raspberrypi5:~ $ shellcheck /usr/local/bin/GScrop 
pi@raspberrypi5:~ $ GScrop 689 137 400 2000
width has to be even
pi@raspberrypi5:~ $ GScrop 688 137 400 2000
height has to be even
pi@raspberrypi5:~ $ GScrop 688 136 400 2000
/dev/media3
...
[libx264 @ 0x5555eb9feda0] kb/s:1342.94

Total: 790 frames (789 samples)
Average: 2.489 ms / 401.784 fps
Minimum: 2.480 ms at frame 696
Maximum: 2.498 ms at frame 695
Outliers: 0 (100%) 0 (10.0%) 0 (1.0%) 53 (0.1%)
pi@raspberrypi5:~ $ 

@Hermann-SW
Copy link
Author

Latest revision fixed two problems (device numbering for cam1, media-ctl command to be used) to make GScrop work again:
https://forums.raspberrypi.com/viewtopic.php?p=2301063#p2301063

@paprikodlak
Copy link

paprikodlak commented Mar 13, 2025

Hi,
thank you for this work.

First, I was not able to capture anything, because the camera would not set up. Then I studied the script and the "no raw workaround" pointed me to the right direction. To capture using picamera2, the raw stream needs to be disabled when configuring.

Copilot was able to convert the script to python, for those interested.
https://gist.github.com/paprikodlak/0f55438ec000157a43e1d3612a00b919

It also includes a script I am using for square cropped capture.

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 25, 2025

@paprikodlak Please create a gist named "GScrop.py" and replace your comment above with a short description and link to that gist.
Then it can be found via this gist, but is separate.

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 25, 2025

Two big news for using GScrop.

  1. running GScrop to set crop, and then directly rpicam-vid with --codec yuv420 and same width+height allows to process frames on the fly (at 527fps for a simple example) while being captured:
    https://forums.raspberrypi.com/viewtopic.php?p=2305225#p2305109
  2. much bigger frames can be captured @536fps than 224x96 — frameskipless capturing of 1456x96@536fps is possible (yes, full sensor width)! This is 50% reduced in size screenshot of mplayer playing video recorded with GScrop:
    https://forums.raspberrypi.com/viewtopic.php?p=2305225#p2305205
    image

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 25, 2025

  1. passing optional parameter 100 to GScrop allows to set shutter speed to 100µs:
    GScrop 1456 96 536 2000 100
    That allows to capture >40m/s inflight airsoft pistol pellet without too much distortion.
    This can be seen in two of the 5 frames of below animated .gif (only left frame half, with 1.87ms between frames):
    https://forums.raspberrypi.com/viewtopic.php?p=2305225&sid=c396d0ec32f4c37dfd1aa56aa2758dbd#p2305225
    pellet anim
    The animated .gif only has 256 colors, so makes the frames look uglier than real.
    This is the left frame half of last of the 5 frames only as captured for comparison.
    A bit dark because of 100µs shutter time, but everything but the >40m/s inflight pellet is sharp:
    4

@Hermann-SW
Copy link
Author

Hermann-SW commented Mar 28, 2025

GScrop can also be used as simple tool to set crop and framerate for other processing.

This posting describes a processing pipeline with all the details:
https://forums.raspberrypi.com/viewtopic.php?p=2306078#p2306078

In short it does this:

  1. captures 224x96@536fps yuv420 video and pipes to next command
  2. determines dark/bright for each frame while it is streamed and writes to file "err"
  3. ffmpeg converts the yuv420 video to .mp4

image

Despite the quite concurrent processing, recorded framerate only dropped to 527fps from 536fps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment