This repo contains the cleaned version of our camera-and-flag wind-speed sensor project.
Built as a group project by Riyaadh Gani, Omar Sayed, and Adrian Pailler.
- Omar Sayed: https://github.com/osayed11
- Adrian Pailler: https://github.com/AP-03
Wind is invisible, so the sensor watches something the wind can move.
The pipeline is:
camera frames
-> Lucas-Kanade point tracking
-> RMS motion feature
-> calibration against reference sensor
-> Kalman smoothing
-> wind-speed estimate
The important bit is that the camera is not trying to understand the exact flag shape. It is measuring how energetic the flag motion is over time, then using calibration to translate that visual motion into metres per second.
- Reproducible calibration and validation code.
- Raw logs copied from the original coursework repo.
- A timestamp merge step that pairs Pi sensor output with the reference sensor.
- Chronological train/holdout validation.
- Generated CSV, JSON, and plot outputs.
- Optional realtime Lucas-Kanade camera code for the Raspberry Pi/camera setup.
The old repo also had empty calibration CSVs, generated caches, debug scripts, and rolling-shutter experiments. Those were intentionally left out because they made the final result harder to inspect.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .python scripts/run_validation.pyThis writes:
results/paired_validation.csvresults/validation_predictions.csvresults/metrics.jsonresults/calibration_scatter.pngresults/time_series_validation.pngresults/error_over_time.png
You can also run the module directly:
python -m wind_sensor.calibration \
--pi-log data/raw/pi_log.csv \
--gt-log data/raw/gt_log.csv \
--output-dir resultsThe included validation reconstructs 1,676 paired rows by aligning:
data/raw/pi_log.csvdata/raw/gt_log.csv
The split is chronological: first 70% for calibration, last 30% for holdout. No random shuffling.
| Model | Holdout RMSE | Holdout MAE | Holdout R2 |
|---|---|---|---|
| Linear + Kalman | 1.639 m/s | 1.057 m/s | 0.682 |
| Quadratic + Kalman | 0.958 m/s | 0.854 m/s | 0.891 |
| Cubic + Kalman | 1.254 m/s | 1.048 m/s | 0.814 |
| Logarithmic + Kalman | 1.195 m/s | 0.799 m/s | 0.831 |
On this included holdout, the quadratic + Kalman model gives the lowest RMSE. The logarithmic + Kalman model is also included because it captures saturation with a simpler curve and is useful when you want a more conservative model.
The full-dataset fits reproduce the coefficients used in the original code comments:
cubic: 0.0186*rms^3 - 0.5154*rms^2 + 3.9319*rms - 0.4072
logarithmic: 2.3969*ln(rms) + 3.9180
The live sensor needs a camera and OpenCV GUI support. On the Raspberry Pi, Picamera2 is used when available; otherwise the script falls back to an OpenCV camera.
python -m wind_sensor.realtime --method log_kalman --output-dir resultsUse an OpenCV camera explicitly:
python -m wind_sensor.realtime --opencv-camera --method log_kalmanWith a serial reference sensor:
python -m wind_sensor.realtime --gt-port /dev/ttyACM0This is controlled validation, not proof that the sensor works everywhere. Lighting, camera angle, marker quality, flag material, low wind speeds, and high wind saturation all matter.
The useful claim is narrower:
in a controlled setup, a cheap camera and marked flag can produce a meaningful wind-speed estimate when motion is tracked, calibrated, and filtered properly.
The methodology write-up is in docs/methodology.md.
Credits are in CREDITS.md.