Skip to content

Implementing Optical flow LK#755

Open
Incharajayaram wants to merge 14 commits intokornia:mainfrom
Incharajayaram:optical-flow
Open

Implementing Optical flow LK#755
Incharajayaram wants to merge 14 commits intokornia:mainfrom
Incharajayaram:optical-flow

Conversation

@Incharajayaram
Copy link
Copy Markdown
Contributor

@Incharajayaram Incharajayaram commented Feb 24, 2026

📝 Description

implements #46

Important:

  • Ensure you are assigned to the linked issue before submitting this PR
  • This PR should strictly implement what the linked issue describes
  • Do not include changes beyond the scope of the linked issue

🛠️ Changes Made

  • Implemented sparse pyramidal LK API with result/status/error outputs.
  • Implemented scalar 2x2 solve (no external matrix/LA abstractions).
  • Implemented minimum eigenvalue rejection for feature validity.
  • Implemented Gaussian pyramid + per-level gradient reuse.
  • Added precompute API to avoid rebuilding pyramids/gradients across repeated calls.
  • Added per-feature parallel execution using Rayon.
  • Reduced inner-loop overhead by caching per-level patch/gradient terms used across Gauss–Newton iterations.
  • Added/updated tests in LK module (synthetic motion, edge cases, determinism, parity-oriented checks).

🧪 How Was This Tested?

  • Unit Tests: (List new/updated tests)
  • Manual Verification:
    Ran LK benchmark with OpenCV feature and verified benchmark groups execute.
  • Performance/Edge Cases: (How does this handle nulls, large data, etc.?)
    Compared Rust full path, Rust precomputed path, and OpenCV on identical benchmark setup.

Benchmarking snippets:

Benchmarking OpticalFlowPyrLK/kornia_cpu/opencv_compare/512x512/800: Collecting 100 samples in estimated 
OpticalFlowPyrLK/kornia_cpu/opencv_compare/512x512/800
                        time:   [15.520 ms 16.145 ms 17.007 ms]
                        thrpt:  [47.038 Kelem/s 49.550 Kelem/s 51.547 Kelem/s]
                 change:
                        time:   [−19.644% −15.411% −9.8629%] (p = 0.00 < 0.05)
                        thrpt:  [+10.942% +18.219% +24.446%]
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  1 (1.00%) high mild
  3 (3.00%) high severe
Benchmarking OpticalFlowPyrLK/kornia_cpu/opencv_compare_precomputed/512x512/800: Collecting 100 samples i
OpticalFlowPyrLK/kornia_cpu/opencv_compare_precomputed/512x512/800
                        time:   [6.6491 ms 6.7239 ms 6.8051 ms]
                        thrpt:  [117.56 Kelem/s 118.98 Kelem/s 120.32 Kelem/s]
                 change:
                        time:   [−8.9713% −7.3796% −5.7741%] (p = 0.00 < 0.05)
                        thrpt:  [+6.1279% +7.9676% +9.8554%]
                        Performance has improved.
Found 13 outliers among 100 measurements (13.00%)
  9 (9.00%) high mild
  4 (4.00%) high severe
Benchmarking OpticalFlowPyrLK/opencv/opencv_compare/512x512/800: Collecting 100 samples in estimated 9.71
OpticalFlowPyrLK/opencv/opencv_compare/512x512/800
                        time:   [5.4043 ms 7.0739 ms 9.0273 ms]
                        thrpt:  [88.620 Kelem/s 113.09 Kelem/s 148.03 Kelem/s]
                 change:
                        time:   [−42.744% −20.420% +4.8620%] (p = 0.14 > 0.05)
                        thrpt:  [−4.6366% +25.659% +74.655%]
                        No change in performance detected.
Found 14 outliers among 100 measurements (14.00%)
  6 (6.00%) high mild
  8 (8.00%) high severe

Rust (full) median: 16.145 ms
Rust (precomputed) median: 6.7239 ms
OpenCV median: 7.0739 ms

Rust precomputed is slightly lower median than OpenCV in this run, but OpenCV variance is high.


🕵️ AI Usage Disclosure

Check one of the following:

  • 🟢 No AI used.
  • 🟡 AI-assisted: I used AI for boilerplate/refactoring but have manually reviewed and tested every line.
  • 🔴 AI-generated: (Note: These PRs may be subject to stricter scrutiny or immediate closure if the logic is not explained).

🚦 Checklist

  • I am assigned to the linked issue (required before PR submission)
  • The linked issue has been approved by a maintainer
  • This PR strictly implements what the linked issue describes (no scope creep)
  • I have performed a self-review of my code (no "ghost" variables or hallucinations).
  • My code follows the existing style guidelines of this project.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • (Optional) I have attached screenshots/recordings for UI changes.

💭 Additional Context

Add any other context or screenshots about the pull request here.

@Incharajayaram Incharajayaram changed the title Optical flow Implementing Optical flow LK Feb 24, 2026
@Incharajayaram
Copy link
Copy Markdown
Contributor Author

@cjpurackal @sidd-27 Please review

@namanguptagit
Copy link
Copy Markdown
Contributor

Hello ,

I've run the official benchmarks from
crates/kornia-imgproc/benches/bench_optical_flow.rs
using Criterion. Here are the pure Rust native kornia_cpu benchmark numbers for the Lucas-Kanade optical flow, as well as an OpenCV comparison metric:

Hardware/Environment Environment: Local CPU (Apple Silicon/Mac)

  1. Scaling Over Feature Points (256x256 image, default L3 pyramid)
Points Execution Time Throughput
50 851.98 µs 58.687 Kelem/s
100 832.99 µs 120.05 Kelem/s
200 986.32 µs 202.77 Kelem/s
500 1.5222 ms 328.47 Kelem/s
  1. Scaling Over Maximum Pyramid Levels (256x256 image, 100 points)
Level Depth Execution Time Throughput
L0 283.76 µs 352.40 Kelem/s
L1 586.20 µs 170.59 Kelem/s
L2 760.43 µs 131.50 Kelem/s
L3 846.62 µs 118.12 Kelem/s
  1. OpenCV Comparison (512x512 image, 800 points)
Implementation Execution Time (Median) Variance Range
OpenCV cv::calcOpticalFlowPyrLK 7.0739 ms [5.40 ms - 9.02 ms] (High Variance)
Kornia CPU (Precomputed Pyramids) 6.7239 ms [6.64 ms - 6.80 ms] (Low Variance)
Kornia CPU (End-to-End) 16.145 ms [15.52 ms - 17.00 ms] (Low Variance)

@Incharajayaram
Copy link
Copy Markdown
Contributor Author

Hello ,

I've run the official benchmarks from
crates/kornia-imgproc/benches/bench_optical_flow.rs
using Criterion. Here are the pure Rust native kornia_cpu benchmark numbers for the Lucas-Kanade optical flow, as well as an OpenCV comparison metric:

Hardware/Environment Environment: Local CPU (Apple Silicon/Mac)

  1. Scaling Over Feature Points (256x256 image, default L3 pyramid)
Points Execution Time Throughput
50 851.98 µs 58.687 Kelem/s
100 832.99 µs 120.05 Kelem/s
200 986.32 µs 202.77 Kelem/s
500 1.5222 ms 328.47 Kelem/s
  1. Scaling Over Maximum Pyramid Levels (256x256 image, 100 points)
Level Depth Execution Time Throughput
L0 283.76 µs 352.40 Kelem/s
L1 586.20 µs 170.59 Kelem/s
L2 760.43 µs 131.50 Kelem/s
L3 846.62 µs 118.12 Kelem/s
  1. OpenCV Comparison (512x512 image, 800 points)
Implementation Execution Time (Median) Variance Range
OpenCV cv::calcOpticalFlowPyrLK 7.0739 ms [5.40 ms - 9.02 ms] (High Variance)
Kornia CPU (Precomputed Pyramids) 6.7239 ms [6.64 ms - 6.80 ms] (Low Variance)
Kornia CPU (End-to-End) 16.145 ms [15.52 ms - 17.00 ms] (Low Variance)

Can you try optimizing it more? the performance is same as before when it was run without scharr kernels

- Fix massive threading overhead in scharr spatial gradient by replacing inner per-pixel  with
- Speed up both scharr and sobel 3x3 convolutions by extracting an interior fast-path that skips expensive per-pixel bounds checking bounds
- Runs Formatting C++ files...
Done! to ensure formatting passes CI
@namanguptagit
Copy link
Copy Markdown
Contributor

You are completely right that swapping Sobel for Scharr didn't naturally change performance. Since they are both mathematically similar 3x3 convolutions, their computational cost is identical!

However, while profiling this based on your comment, I discovered two major pre-existing bottlenecks in the original spatial gradient code:

It was heavily doing .min().max() bounds-checking on every single pixel during the 3x3 convolution loop.
(When I ported it to Scharr) I accidentally called par_chunks_mut() on individual pixels inside the innermost loop, causing massive Rayon threading overhead.
I just pushed a new optimization that fixes the Rayon chunking to properly parallelize by row, and extracts an interior fast-path to skip bounds-checking for all non-border pixels.

Local benchmarks show this improved overall optical flow throughput by ~15% up to 20% across workloads compared to the original code without Scharr kernels!

Benchmark Results (New Optimized Scharr vs Original Unoptimized Sobel):
OpticalFlowPyrLK/kornia_cpu/synthetic/256x256
time: [781.37 µs 791.53 µs 809.18 µs]
change:
time: [−10.899% −8.7452% −6.8823%] (p = 0.00 < 0.05)
thrpt: [+7.3910% +9.5833% +12.232%]
Performance has improved.

OpticalFlowPyrLK/kornia_cpu/points/256x256/50
time: [788.76 µs 808.13 µs 834.48 µs]
change:
time: [−7.9525% −6.0880% −3.5329%] (p = 0.00 < 0.05)
thrpt: [+3.6623% +6.4826% +8.6396%]
Performance has improved.

OpticalFlowPyrLK/kornia_cpu/points/256x256/100
time: [778.07 µs 788.94 µs 807.30 µs]
change:
time: [−6.3292% −4.7255% −2.8280%] (p = 0.00 < 0.05)
thrpt: [+2.9103% +4.9599% +6.7568%]
Performance has improved.

OpticalFlowPyrLK/kornia_cpu/points/256x256/200
time: [1.0028 ms 1.0061 ms 1.0098 ms]
change:
time: [−16.769% −15.028% −13.370%] (p = 0.00 < 0.05)
thrpt: [+15.433% +17.686% +20.147%]
Performance has improved.

OpticalFlowPyrLK/kornia_cpu/points/256x256/500
time: [1.4321 ms 1.4594 ms 1.4963 ms]
change:
time: [−5.2475% −3.9602% −2.4912%] (p = 0.00 < 0.05)
thrpt: [+2.5548% +4.1235% +5.5381%]
Performance has improved.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new sparse pyramidal Lucas–Kanade (PyrLK) optical flow implementation to kornia-imgproc, exposing a public API (including a precompute path) and providing benchmarks/tests to validate correctness and performance.

Changes:

  • Introduces optical_flow_pyr_lk module with PyrLK parameters/results, precompute builder, and main optical flow entrypoints.
  • Exposes the new module from crates/kornia-imgproc/src/lib.rs and adds a Criterion benchmark for optical flow.
  • Updates bench configuration and refreshes Cargo.lock (including broad workspace version churn).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
crates/kornia-imgproc/src/optical_flow_pyr_lk.rs New PyrLK implementation, precompute API, and unit tests.
crates/kornia-imgproc/src/lib.rs Exports the new optical_flow_pyr_lk module.
crates/kornia-imgproc/benches/bench_optical_flow.rs Adds Criterion benchmarks (plus optional OpenCV comparison via feature).
crates/kornia-imgproc/Cargo.toml Registers the new benchmark target.
Cargo.lock Updates lockfile with wide-ranging workspace/dependency version changes.

Comment on lines +421 to +430
pub fn build_lk_precomputed<A: ImageAllocator>(
prev_img: &Image<f32, 1, A>,
next_img: &Image<f32, 1, A>,
max_level: usize,
) -> Result<PyrLKPrecomputed<A>, PyrLKError> {
let mut prev_pyr = Vec::with_capacity(max_level + 1);
let mut next_pyr = Vec::with_capacity(max_level + 1);
prev_pyr.push(prev_img.clone());
next_pyr.push(next_img.clone());

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_lk_precomputed is a public function but doesn’t validate that prev_img.size() == next_img.size(). If sizes differ, the function will currently fail later with a lower-level ImageError::InvalidImageSize, which is less actionable than the dedicated PyrLKError::ImageSizeMismatch. Consider adding an upfront size check and returning PyrLKError::ImageSizeMismatch for consistency with calc_optical_flow_pyr_lk.

Copilot uses AI. Check for mistakes.
}

for lvl in (0..=params.max_level).rev() {
let scale = 1.0 / (1 << lvl) as f32;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scale is computed via (1 << lvl) as f32. For large max_level values, 1 << lvl can overflow/wrap (and can panic in debug builds), leading to incorrect scaling or division by zero/inf. Consider using 2.0_f32.powi(lvl as i32) (or checked_shl with a validated max_level upper bound) to make the computation robust for all user inputs.

Suggested change
let scale = 1.0 / (1 << lvl) as f32;
let scale = 1.0 / 2.0_f32.powi(lvl as i32);

Copilot uses AI. Check for mistakes.
Comment on lines +200 to +202
if params.win_size != WIN_SIZE {
return (pt, 0, 0.0);
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

track_feature is hard-coded to a 21x21 window (WIN_SIZE/HALF_WIN) and silently returns status=0 when params.win_size != 21. This conflicts with the public API contract (any positive odd win_size is accepted) and makes callers think tracking failed rather than that parameters are unsupported. Either implement runtime-sized windows (derive half_win/win_pixels from params.win_size) or return a PyrLKError::InvalidWindowSize error instead of an Ok result with status=0.

Suggested change
if params.win_size != WIN_SIZE {
return (pt, 0, 0.0);
}

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +212
let mut dx = initial_flow.map_or(0.0, |d| d[0]);
let mut dy = initial_flow.map_or(0.0, |d| d[1]);
let mut valid = true;
let mut tracking_error = 0.0f32;
if params.win_size != WIN_SIZE {
return (pt, 0, 0.0);
}

for lvl in (0..=params.max_level).rev() {
let scale = 1.0 / (1 << lvl) as f32;
let xc = pt[0] * scale;
let yc = pt[1] * scale;

if lvl < params.max_level {
dx *= 2.0;
dy *= 2.0;
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When use_initial_flow is enabled, the initial (dx,dy) is computed in full-resolution pixel units, but track_feature applies it directly at the coarsest pyramid level (lvl = max_level) without scaling by 1/(2^max_level). In a pyramidal LK solver the initial flow should be scaled to the current level (and then doubled when moving to finer levels), otherwise the coarse-level search starts from a grossly incorrect offset.

Copilot uses AI. Check for mistakes.
grad_y_levels: precomputed.grad_y_pyr.len(),
});
}

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calc_optical_flow_pyr_lk_with_precomputed only validates the number of pyramid levels. Since this is a public API that can be called with arbitrary PyrLKPrecomputed, it should also validate (at least) that prev/next/grad pyramids have matching per-level sizes, and that params.win_size is a supported/valid value (odd, >0, and consistent with the internal window implementation). Without these checks, malformed precomputed inputs can lead to out-of-bounds indexing/panics during sampling.

Suggested change
// Validate that per-level image sizes match across the precomputed pyramids.
for level in 0..expected_levels {
let prev_size: ImageSize = precomputed.prev_pyr[level].size();
if precomputed.next_pyr[level].size() != prev_size
|| precomputed.grad_x_pyr[level].size() != prev_size
|| precomputed.grad_y_pyr[level].size() != prev_size
{
// Reuse the existing error variant to signal invalid precomputed data.
return Err(PyrLKError::InvalidPrecomputedLevels {
expected_levels,
prev_levels: precomputed.prev_pyr.len(),
next_levels: precomputed.next_pyr.len(),
grad_x_levels: precomputed.grad_x_pyr.len(),
grad_y_levels: precomputed.grad_y_pyr.len(),
});
}
}
// Validate window size: must be positive and odd to match the internal kernel assumptions.
if params.win_size <= 0 || params.win_size % 2 == 0 {
return Err(PyrLKError::InvalidWindowSize {
win_size: params.win_size,
});
}

Copilot uses AI. Check for mistakes.
Comment on lines +426 to +430
let mut prev_pyr = Vec::with_capacity(max_level + 1);
let mut next_pyr = Vec::with_capacity(max_level + 1);
prev_pyr.push(prev_img.clone());
next_pyr.push(next_img.clone());

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_lk_precomputed pushes prev_img.clone() / next_img.clone() into the pyramids. In this codebase, Image cloning performs a deep copy of the underlying tensor storage, so this duplicates the full input images in memory before even building lower pyramid levels. Consider either taking ownership of the input images (so level-0 can be moved instead of cloned), or documenting this cost clearly to avoid surprising allocations for large images.

Copilot uses AI. Check for mistakes.
@cjpurackal
Copy link
Copy Markdown
Member

@Incharajayaram @namanguptagit LGTM, address the sensible copilot comments. Also can you try this on:
http://robotics.ethz.ch/~asl-datasets/ijrr_euroc_mav_dataset/machine_hall/MH_01_easy/MH_01_easy.zip

Curious to see how it performs.

@namanguptagit
Copy link
Copy Markdown
Contributor

@Incharajayaram @namanguptagit LGTM, address the sensible copilot comments. Also can you try this on:
http://robotics.ethz.ch/~asl-datasets/ijrr_euroc_mav_dataset/machine_hall/MH_01_easy/MH_01_easy.zip

Curious to see how it performs.

Sure @cjpurackal will do it shortly and share the benchmarks once its done

@namanguptagit
Copy link
Copy Markdown
Contributor

@Incharajayaram @namanguptagit LGTM, address the sensible copilot comments. Also can you try this on: http://robotics.ethz.ch/~asl-datasets/ijrr_euroc_mav_dataset/machine_hall/MH_01_easy/MH_01_easy.zip

Curious to see how it performs.

I have addressed the copilot comments in this pr : Incharajayaram#1
I tried downloading this zip but its not getting downloaded for me .

}

#[inline]
fn bilinear_sample_from_slice(data: &[f32], width: usize, height: usize, x: f32, y: f32) -> f32 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are internal routines for that

let mut valid = true;
let mut tracking_error = 0.0f32;
if params.win_size != WIN_SIZE {
return (pt, 0, 0.0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return None ?

&& !(xc >= hw && yc >= hw && xc < (width as f32 - hw) && yc < (height as f32 - hw))
{
valid = false;
break;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should return something more meaningful

match params.border_mode {
BorderMode::Clamp => {
let mut idx = 0usize;
for wy in -HALF_WIN..=HALF_WIN {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too much boilerplate code;block codes are practically the same below

@edgarriba edgarriba requested a review from cjpurackal March 2, 2026 10:11
@Incharajayaram
Copy link
Copy Markdown
Contributor Author

@namanguptagit thanks for showing the 256x256 results, can you also try running benchmarks for 512x512? with 800 points?

@namanguptagit
Copy link
Copy Markdown
Contributor

Sure will share results for that as well

@namanguptagit
Copy link
Copy Markdown
Contributor

OpticalFlowPyrLK/kornia_cpu/points/512x512/800
time: [2.3836 ms 2.3925 ms 2.4033 ms]
thrpt: [332.88 Kelem/s 334.38 Kelem/s 335.63 Kelem/s]
I have updated benchmarks code for this as well and adressed copilot comments as well please check @Incharajayaram

@Incharajayaram
Copy link
Copy Markdown
Contributor Author

@namanguptagit nice, thats a huge improvement

@edgarriba
Copy link
Copy Markdown
Member

please start reporting visual results of this, not only tests numbers. we need photometric and reprojection errors

@namanguptagit
Copy link
Copy Markdown
Contributor

Here are the photometric and reprojection errors from local validation:

Real image (dog.jpeg, 258×195):

Displacement Tracked Reproj Error (median) Photometric Error (mean)
dx=3, dy=2 100% 0.0013 px 0.000032
dx=7.5, dy=-4.2 100% 0.0299 px 0.004598
dx=15, dy=-10 100% 0.0012 px 0.000033

Synthetic scenes (256×256):

Scenario Tracked Reproj Error (mean) Photometric Error (mean)
Integer (3, 2) 42% 0.0000 px 0.000000
Subpixel (2.5, -1.3) 50% 0.0284 px 0.000420
Large motion (10, -8) 32% 0.0000 px 0.000000

Sub-pixel reprojection accuracy across all scenarios. Photometric residuals are near-zero, confirming the tracker converges on the correct intensity patches.

validation_output validation_real_image

@edgarriba
Copy link
Copy Markdown
Member

For real images I'd use real outdoor datasets eg for VO that usually have more textures

@Incharajayaram
Copy link
Copy Markdown
Contributor Author

I tested on the EuRoC Machine Hall dataset (MH_01_easy link), and extended to all Machine Hall sequences.

Configuration:
frame step: 1 (adjacent frames)
metrics: track rate, photometric error, flow magnitude

EuRoC Machine Hall Optical Flow Validation (step=1, max_pairs=200)

Sequence Track Rate (mean) Track Rate (min) Photometric Error (mean) Photometric Error (median) Flow Magnitude (mean) Flow Magnitude (median)
MH_01_easy 99.8% 96.4% 0.018874 0.012094 15.30 px 9.19 px
MH_02_easy 99.7% 98.2% 0.018963 0.012282 15.45 px 9.12 px
MH_03_medium 99.7% 97.3% 0.019708 0.010434 17.32 px 5.86 px
MH_04_difficult 97.4% 93.5% 0.018479 0.008010 18.62 px 4.64 px
MH_05_difficult 98.6% 90.5% 0.013530 0.006082 15.39 px 2.91 px

@edgarriba @cjpurackal

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +427 to +440
pub fn build_pyramid<const C: usize>(
src: &Image<f32, C, kornia_tensor::CpuAllocator>,
max_level: usize,
) -> Result<Vec<Image<f32, C, kornia_tensor::CpuAllocator>>, ImageError> {
let mut pyramid = Vec::with_capacity(max_level + 1);
pyramid.push(src.clone());

for l in 0..max_level {
let current_img = &pyramid[l];
let new_size = ImageSize {
width: current_img.cols().div_ceil(2),
height: current_img.rows().div_ceil(2),
};
let mut downsampled = Image::from_size_val(new_size, 0.0, kornia_tensor::CpuAllocator)?;
Comment on lines +71 to +76
pub fn scharr_kernel_1d(kernel_size: usize) -> (Vec<f32>, Vec<f32>) {
let (kernel_x, kernel_y) = match kernel_size {
3 => (vec![-1.0, 0.0, 1.0], vec![3.0, 10.0, 3.0]),
_ => panic!("Invalid kernel size for scharr kernel"),
};
(kernel_x, kernel_y)
Comment on lines +138 to +147
pub fn scharr<const C: usize, A1: ImageAllocator, A2: ImageAllocator>(
src: &Image<f32, C, A1>,
dst: &mut Image<f32, C, A2>,
kernel_size: usize,
) -> Result<(), ImageError> {
let (kernel_x, kernel_y) = kernels::scharr_kernel_1d(kernel_size);

let mut gx = Image::<f32, C, _>::from_size_val(src.size(), 0.0, CpuAllocator)?;
separable_filter(src, &mut gx, &kernel_x, &kernel_y)?;

Comment on lines +129 to +131
/// Computer scharr filter
///
/// # Arguments
Comment on lines +403 to +411
if params.use_initial_flow
&& next_pts_in
.as_ref()
.is_some_and(|pts| pts.len() != prev_pts.len())
{
return Err(PyrLKError::InitialFlowLengthMismatch {
expected: prev_pts.len(),
provided: next_pts_in.as_ref().map_or(0, |pts| pts.len()),
});
Comment on lines +460 to +469
if params.use_initial_flow
&& next_pts_in
.as_ref()
.is_some_and(|pts| pts.len() != prev_pts.len())
{
return Err(PyrLKError::InitialFlowLengthMismatch {
expected: prev_pts.len(),
provided: next_pts_in.as_ref().map_or(0, |pts| pts.len()),
});
}
@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs within 7 days. Thank you for your contributions!

@github-actions github-actions Bot added the stale label Mar 29, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 5, 2026

This pull request has been automatically closed due to inactivity. Feel free to reopen it if you would like to continue working on it.

@github-actions github-actions Bot closed this Apr 5, 2026
@cjpurackal cjpurackal reopened this Apr 30, 2026
@qodo-code-review
Copy link
Copy Markdown
Contributor

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

Copy link
Copy Markdown
Member

@edgarriba edgarriba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add benchmarks against opencv and vpi and match/improve using neon/avx

@cjpurackal
Copy link
Copy Markdown
Member

We should add benchmarks against opencv and vpi and match/improve using neon/avx

Yes

@github-actions github-actions Bot removed the stale label May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants