Pipeline for turning a portrait photo into plotter-ready G-code:
- take a photo,
- preprocess it by cropping around all relevant faces and suppressing the photo background,
- generate a black-line portrait drawing with the OpenAI API,
- trace that bitmap line drawing with
bitmaptracer.py, - write G-code to a file for the plotter controller.
Printer connection, Raspberry Pi UI, and job sending are intentionally outside this repository.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
$env:OPENAI_API_KEY = "sk-..."On Linux/Raspberry Pi:
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
export OPENAI_API_KEY="sk-..."python plotter_pipeline.py photo.jpg output.gcode `
--style-reference style_reference.png `
--preprocessed-photo photo_preprocessed.png `
--line-drawing output_line.png `
--width-mm 100 `
--height-mm 125 `
--lift-height 5 `
--speed 1500The preprocessed photo and generated line drawing are saved separately so they can be inspected before plotting.
Capture a still image only:
python webcam_capture.py captured_photo.jpg --camera-index 0Request a specific capture resolution:
python webcam_capture.py captured_photo.jpg `
--camera-index 2 `
--backend dshow `
--width 2560 `
--height 1440If index 0 does not open, probe available indices:
python webcam_capture.py --list-cameras --max-index 10On Windows, trying a specific backend can help:
python webcam_capture.py --list-cameras --backend dshow
python webcam_capture.py captured_photo.jpg --camera-index 0 --backend dshowOn Raspberry Pi/Linux, the backend is usually v4l2:
python webcam_capture.py --list-cameras --backend v4l2
python webcam_capture.py captured_photo.jpg --camera-index 0 --backend v4l2Run the full pipeline using the webcam as input:
python plotter_pipeline.py output.gcode `
--capture-webcam `
--captured-photo captured_photo.jpg `
--camera-index 2 `
--camera-width 2560 `
--camera-height 1440 `
--style-reference style_reference.png `
--width-mm 100 `
--height-mm 125Preprocessing is enabled by default. It corrects image orientation, detects relevant faces, crops around the face group, replaces/suppresses the background with a light color, resizes to the model input size, and applies mild luminance normalization. This is the preferred mode when the input may contain multiple people.
To keep the group crop but skip background removal:
python plotter_pipeline.py photo.jpg output.gcode `
--preprocess-mode crop `
--style-reference style_reference.png `
--width-mm 100 `
--height-mm 125To send the original photo directly to the image model:
python plotter_pipeline.py photo.jpg output.gcode `
--skip-preprocess `
--style-reference style_reference.png `
--width-mm 100 `
--height-mm 125For debugging the portrait crop:
python plotter_pipeline.py photo.jpg output.gcode `
--style-reference style_reference.png `
--preprocess-debug preprocess_debug.png `
--preprocess-meta preprocess_meta.json `
--width-mm 100 `
--height-mm 125You can run preprocessing by itself:
python preprocess_portrait.py photo.jpg preprocessed_photo.png `
--size 1024x1536 `
--debug preprocess_debug.png `
--meta preprocess_meta.jsonThe default mode crops around all relevant faces and suppresses the background. The debug image shows detected face boxes and the selected group crop; the metadata JSON records which detector was used and any warnings.
To crop around the face group without background removal:
python preprocess_portrait.py photo.jpg preprocessed_photo.png `
--mode crop `
--size 1024x1536 `
--debug preprocess_debug.png `
--meta preprocess_meta.jsonUse this when you already have a generated bitmap line drawing and only want G-code:
python plotter_pipeline.py output.gcode `
--skip-generation `
--line-drawing output_line.png `
--width-mm 100 `
--height-mm 125--simplify: higher values make fewer, straighter G-code moves.--min-size: removes small bitmap components before skeleton tracing.--prune-spurs: removes short dangling skeleton branches.--min-path-length: omits dot-like traced paths.--threshold: manually controls black/white thresholding; by default Otsu thresholding is used.
The defaults in plotter_pipeline.py are more aggressive than raw bitmaptracer.py because GPT-generated line drawings often contain small texture marks that become tiny plotter moves.
First do a dry run. This reads the G-code and prints the commands that would be sent, without opening the serial port:
python serial_gcode_sender.py output.gcode --port COM3 --dry-runStream to the printer:
python serial_gcode_sender.py output.gcode --port COM3 --baud 115200On Raspberry Pi the port is often /dev/ttyUSB0 or /dev/ttyACM0:
python serial_gcode_sender.py output.gcode --port /dev/ttyUSB0 --baud 115200You can also send directly after generating:
python plotter_pipeline.py photo.jpg output.gcode `
--style-reference style_reference.png `
--width-mm 100 `
--height-mm 125 `
--send-to-printer `
--serial-port COM3For a full webcam-to-printer run:
python plotter_pipeline.py output.gcode `
--capture-webcam `
--captured-photo captured_photo.jpg `
--style-reference style_reference.png `
--width-mm 100 `
--height-mm 125 `
--send-to-printer `
--serial-port COM3Serial streaming is intentionally opt-in because it can move the printer. Confirm the pen is mounted, the bed is clear, the drawing origin and Z heights are safe, and the correct serial port is selected before using --send-to-printer.
For local development, keep your OpenAI API key out of the repository.
Recommended local setup:
- Copy
.env.exampleto.env. - Put your real key in
.env. - Load it into your shell before running the pipeline.
PowerShell:
$env:OPENAI_API_KEY = "sk-..."Linux/Raspberry Pi:
export OPENAI_API_KEY="sk-...".env and .venv are ignored by git in this repo. .env.example is safe to commit because it contains only a placeholder.
Use GitHub Secrets only for GitHub Actions or other GitHub-hosted automation. A GitHub Secret does not automatically help your Raspberry Pi or your local shell; for the Pi, store the key as an environment variable, a local .env file, or a systemd environment file that is not committed.
Before pushing, run:
git status
git diff -- .gitignore README.md requirements.txt .env.exampleDo not commit screenshots, logs, shell history, config files, or test scripts that contain a real OPENAI_API_KEY. If a key is ever committed, revoke it in the OpenAI dashboard and create a new one; deleting it from git later is not enough because it remains in history.