|
| 1 | +# Optical Flow |
| 2 | + |
| 3 | +## Description |
| 4 | + |
| 5 | +This example implements the **Horn-Schunck variational optical flow** algorithm using HIP. It estimates the motion field (displacement vectors) between two consecutive image frames by minimizing a global energy functional that balances data fidelity and spatial smoothness. |
| 6 | + |
| 7 | +The algorithm operates on a Gaussian image pyramid: flow is computed coarse-to-fine, with each level refining the estimate from the level above. At each pyramid level, image warping aligns the target frame with the source, and a Jacobi iterative solver computes the incremental flow update. |
| 8 | + |
| 9 | +The program computes optical flow on both the CPU (`flowGold`) and GPU (`flowHIP`), compares the results via L1 norm, and writes two `.flo` files (Middlebury format) for inspection. |
| 10 | + |
| 11 | +## Application Flow |
| 12 | + |
| 13 | +1. Load two consecutive frames (`data/frame10.ppm`, `data/frame11.ppm`) as single-channel FP32 images. |
| 14 | +2. Build a Gaussian pyramid with `nLevels` levels by repeatedly downscaling with a 4-tap filter. |
| 15 | +3. At each pyramid level (coarse to fine): |
| 16 | + - Upscale the flow estimate from the coarser level. |
| 17 | + - Warp the target image toward the source using the current flow estimate. |
| 18 | + - Compute image derivatives (Ix, Iy, Iz) via finite differences. |
| 19 | + - Run `nSolverIters` Jacobi iterations to solve for the incremental flow update. |
| 20 | + - Repeat for `nWarpIters` warping passes. |
| 21 | +4. Copy GPU results to host and compare against the CPU reference (L1 norm per pixel). |
| 22 | +5. Write `FlowGPU.flo` and `FlowCPU.flo` to the working directory. |
| 23 | + |
| 24 | +## Key APIs and Concepts |
| 25 | + |
| 26 | +| Concept | HIP API | |
| 27 | +|---|---| |
| 28 | +| Texture objects with bilinear filtering | `hipCreateTextureObject`, `hipTextureObject_t` | |
| 29 | +| Pitched 2D texture resource | `hipResourceTypePitch2D`, `hipResourceDesc` | |
| 30 | +| Mirror address mode | `hipAddressModeMirror` | |
| 31 | +| Normalized texture coordinates | `texDescr.normalizedCoords = true` | |
| 32 | +| In-kernel texture fetch | `tex2D<float>(tex, x, y)` | |
| 33 | +| Block synchronization | `cg::this_thread_block()`, `cg::sync()` | |
| 34 | + |
| 35 | +### Pitch Alignment Requirement |
| 36 | + |
| 37 | +ROCm requires `pitchInBytes` for `hipResourceTypePitch2D` to be a multiple of **256 bytes** (64 floats × 4 bytes). The `STRIDE_ALIGNMENT` constant in `common.h` is set to `64` to satisfy this constraint. CUDA only requires 128 bytes (32 floats), so porting code that used `StrideAlignment = 32` will fail at texture creation. |
| 38 | + |
| 39 | +## Prerequisites |
| 40 | + |
| 41 | +- A ROCm-capable AMD GPU |
| 42 | +- ROCm SDK installed ([installation guide](https://rocm.docs.amd.com/en/latest/index.html) or [TheRock releases](https://github.com/ROCm/TheRock/blob/main/RELEASES.md)) |
| 43 | + |
| 44 | +## Building |
| 45 | + |
| 46 | +Set `ROCM_PATH` to your ROCm installation root before building. For a standard system install: |
| 47 | + |
| 48 | +```bash |
| 49 | +export ROCM_PATH=/opt/rocm |
| 50 | +``` |
| 51 | + |
| 52 | +For a Python venv-based install (e.g. TheRock): |
| 53 | + |
| 54 | +```bash |
| 55 | +export ROCM_PATH=/path/to/venv/lib/python3.12/site-packages/_rocm_sdk_devel |
| 56 | +``` |
| 57 | + |
| 58 | +### Make |
| 59 | + |
| 60 | +```bash |
| 61 | +cd Applications/optical_flow |
| 62 | +make ROCM_PATH=$ROCM_PATH |
| 63 | +``` |
| 64 | + |
| 65 | +If your ROCm device libraries are not found automatically, pass their path explicitly: |
| 66 | + |
| 67 | +```bash |
| 68 | +make ROCM_PATH=$ROCM_PATH \ |
| 69 | + CXXFLAGS="--rocm-device-lib-path=$ROCM_PATH/lib/llvm/amdgcn/bitcode" |
| 70 | +``` |
| 71 | + |
| 72 | +### CMake |
| 73 | + |
| 74 | +CMake 3.28 and later require passing `clang++` directly rather than the `hipcc` wrapper script: |
| 75 | + |
| 76 | +```bash |
| 77 | +cd Applications/optical_flow |
| 78 | +cmake -B build \ |
| 79 | + -DROCM_PATH=$ROCM_PATH \ |
| 80 | + -DCMAKE_HIP_COMPILER=$ROCM_PATH/lib/llvm/bin/clang++ |
| 81 | +cmake --build build -j$(nproc) |
| 82 | +``` |
| 83 | + |
| 84 | +## Running |
| 85 | + |
| 86 | +If ROCm is not installed to a standard system path, set `LD_LIBRARY_PATH` so the runtime libraries can be found: |
| 87 | + |
| 88 | +```bash |
| 89 | +export LD_LIBRARY_PATH=$ROCM_PATH/lib:$LD_LIBRARY_PATH |
| 90 | +``` |
| 91 | + |
| 92 | +The binary locates the sample frames (`data/frame10.ppm`, `data/frame11.ppm`) relative to the source directory automatically, so it can be run from any working directory: |
| 93 | + |
| 94 | +```bash |
| 95 | +# Make build |
| 96 | +./applications_optical_flow |
| 97 | + |
| 98 | +# CMake build |
| 99 | +./build/applications_optical_flow |
| 100 | +``` |
| 101 | + |
| 102 | +### Expected Output |
| 103 | + |
| 104 | +```text |
| 105 | +HSOpticalFlow Starting... |
| 106 | +
|
| 107 | +Using device: <GPU name> |
| 108 | +Loading "<source-dir>/data/frame10.ppm" ... |
| 109 | +Loading "<source-dir>/data/frame11.ppm" ... |
| 110 | +Computing optical flow on CPU... |
| 111 | +Computing optical flow on GPU... |
| 112 | +L1 error : 0.000xxx |
| 113 | +``` |
| 114 | + |
| 115 | +The program exits with `EXIT_SUCCESS` when the L1 error between the GPU and CPU results is below `0.05`. Two output files are written to the current working directory: |
| 116 | + |
| 117 | +- `FlowGPU.flo` — GPU optical flow result |
| 118 | +- `FlowCPU.flo` — CPU reference result |
| 119 | + |
| 120 | +Both files use the [Middlebury `.flo` format](http://vision.middlebury.edu/flow/code/flow-code/README.txt) and can be visualized with tools such as `flowiz` or the Middlebury flow utilities. |
| 121 | + |
| 122 | +## Key Notes |
| 123 | + |
| 124 | +- `STRIDE_ALIGNMENT` is 64 because `hipResourceTypePitch2D` requires `pitchInBytes` to be a multiple of 256 bytes (4 bytes × 64 floats = 256 bytes). |
| 125 | +- CMake 3.28+ does not accept the `hipcc` wrapper as `CMAKE_HIP_COMPILER`. Pass the `clang++` binary from `$ROCM_PATH/lib/llvm/bin/clang++` instead. |
| 126 | +- The binary resolves the input image paths relative to the source file location at compile time (via `__FILE__`/`/proc/self/exe`), so no specific working directory is required at runtime. |
0 commit comments