Skip to content

About

OpenCV · scikit-image · NumPy · SciPy — Measured comparisons of classical computer vision algorithms - no deep learning, no training, no GPU. Every finding is a number the code produced.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

60 Commits

Folders and files

Repository files navigation

Classical Computer Vision

We built 57 measured comparisons of classical computer vision algorithms: each project takes one problem (document scanning, dehazing, denoising, segmentation, stitching, ...), runs 3-6 classical methods on the same images, and reports a comparison table, a side-by-side figure, pixel metrics, wall-clock time and one finding with a number in it. It is for engineers and students who want to know which OpenCV / scikit-image method to reach for, and by how much it wins, before reaching for a neural network. No deep learning, no training, CPU only.

3-D reconstruction projects live in 3d-computer-vision.

Projects

Every project folder has its own README with the full tables and figures, and run.py reproduces every number in it.

# Project Methods compared Headline finding Status
01 Document scanner 6 page detectors · 4 binarisers · 2 aspect estimators Otsu's failure on shadowed pages is not because a global threshold is impossible — the best global cut scores 0.890 where Otsu scores 0.678. Also: the detector that wins on the benchmark (Otsu + contour, 1.07 px) fails on a real photograph ✅
02 Portrait mode 6 matting methods · 4 aperture shapes · 2 compositors Runs on one real photograph of one real person. Naive compositing looks fine and is 6.2× worse (12.5 vs 2.04) in the ring outside the subject. A Gaussian blur renders highlights at peak/mean 2.94 where a real aperture is 1.00 ✅
03 Low-light enhancement 8 methods: fixed/auto gamma, HE, CLAHE, SSR/MSR/MSRCR, LIME The ceiling is not the algorithms: at gamma 3 only 158 of 256 tone levels survive, so even an exact inverse reaches 22.31 dB. And an adaptive method beats a fixed constant by +5.46 dB where its assumption holds, loses by −7.70 dB where it does not — averaging to a number that describes neither ✅
04 Dehazing dark channel prior, guided refine, CLAHE, Retinex, gamma A more accurate airlight and transmission map produce a worse image — 19.50 dB falls to 18.50 dB when the airlight error is cut from 0.063 to 0.051; the two errors cancel. And CLAHE wins the contrast column (0.181 vs 0.156) while losing by 6.6 dB ✅
05 Old photo restoration Telea, Navier–Stokes, masked mean, harmonic diffusion, top-hat/black-hat, median residual, per-channel stretch Choosing the best inpainting method is worth 1.1 dB; knowing where the damage is is worth 14.0 dB. Ranking detectors by IoU gets it backwards — the best-IoU detector restores to 9.88 dB, a worse-IoU one to 12.87 dB. And gray-world drives the no-reference cast to 0.04° while landing further from the truth (14.28°) than the faded input (7.72°) ✅
06 Lane detection colour/Lab masks, Canny, Sobel-x, Hough · ROI-only and fixed-guess controls The region of interest is the algorithm. Keep only the trapezoid and fit lines to its brightest pixels — that control lands a median 0.32% of frame width from the full five-step pipeline on one camera. Removing the ROI costs 23× more than removing any other step; removing Canny costs 0.016%. And vanishing-point consistency, the natural annotation-free score, ranks a constant guess first at 0.0 px — only a recorded camera yaw exposes it ✅
07 Copy-move forgery block matching, SIFT/ORB self-match, RANSAC similarity, dense verification The best method on an exact copy is the worst at every other setting: block matching scores 0.9925 IoU unrotated and 0.0000 at 2°. The decisive choice is the verifier's hypothesis, not the descriptor — identical SIFT matches score 0.794 vs 0.677 at 0° and 0.000 vs 0.499 at 90°. And rotation-robustness is paid for in false accusations: 6.3% of an untampered photo flagged, vs 0.0% for block matching ✅
08 Video stabilisation features+LK, phase correlation, ECC, block matching · 3 smoothers · 2 controls The estimator is not the bottleneck. The best recovers the camera path to 0.035 px/frame, 62× finer than the shake; swapping estimators moves the result 0.076, swapping the smoother 0.639 — 8.4× more. Pointed at footage that never moved, ECC invents 25.3 px of drift. And σ 2→32 is 13× steadier for 5× more crop ✅
09 Coin counting & measurement Otsu, top-hat illumination flattening, distance transform, local-maxima watershed, Hough circles The OpenCV tutorial's seed rule (a fraction of the global distance maximum) counts 1 coin of 24 when a lighting artefact merges the mask; local-maxima seeding counts 24 of 24 on the same broken mask. And three methods count 24 while only one measures them plausibly — watershed's smallest basin implies a 5.75 mm coin next to a 24.25 mm reference ✅
10 Seam carving dynamic programming, 4 energy functions, integral-image ROI, plain-rescale control 11.6 points more of the high-energy region retained than cv2.resize, for 2,844× the compute. The four energy functions span 0.5 points — the one-line choice write-ups argue about is 23× smaller than the choice they skip. And it wins its own objective (retained energy) on 4 images of 4 while winning region retention on only 3 ✅
11 HDR exposure fusion Debevec + Reinhard/Drago/Mantiuk, Mertens fusion, naive mean, single-exposure controls Every real fusion method loses to averaging the frames — the naive mean scores 0.928 SSIM against Mertens' 0.848 and Debevec+Reinhard's 0.634, and runs in 9 ms against 1.8 s. And taking one photograph and doing nothing beats all three Debevec pipelines: 0.890 SSIM in 0.002 ms ✅
12 Stereo → depth naive SAD, StereoBM, StereoSGBM, disparity → depth, occlusion handling Block matching is at once the most accurate method here and the worst — 1.3% of the pixels it answers are wrong, and 23.6% if its refusals count as misses. Nothing changed but the convention for scoring a blank. Stripping BM's uniqueness, left-right and speckle checks costs 3.8× the error rate, so most of the quality is not the matching cost ✅
13 Denoising shootout box, Gaussian, median, bilateral, non-local means, adaptive Wiener, do-nothing control Three noise models, three different winners — and the worst filter rotates too. Median wins impulse noise by 6.80 dB and is the worst on Gaussian. Tuning transfers for one filter of six (bilateral +4.13 dB on held-out images; box −0.81). And below sigma 10 every filter scores worse than leaving the image alone ✅
14 Edge detectors Roberts, Prewitt, Sobel, Scharr, LoG, Canny Normalising a gradient by its own maximum turned float rounding into a constant 0.119 response on a flat image — found by a test, not by looking. The operators separate on thin structure and nowhere else ✅
15 Thresholding family fixed, Otsu, triangle, multi-Otsu, 2 adaptive, Niblack, Sauvola, + exhaustive-search oracle "Use adaptive thresholding when the light is uneven" is half a sentence. On the same bad lighting Sauvola beats Otsu by 0.75 IoU on thin strokes and by 0.13 on solid shapes — it depends on the shape of the foreground, not the light. The oracle separates "Otsu picked the wrong cut" (1.000 available) from "no cut exists" (0.741) ✅
16 Sharpening Laplacian (both signs + a deliberate sign error), unsharp, high-boost, + Wiener oracle Acutance cannot tell a sharpener from a sign error — the wrong-signed Laplacian raises it (0.302 vs 0.245) while SSIM collapses to 0.149. High-boost goes from last to first, +16.61 dB, purely by matching brightness. And what a sharpener recovers collapses 12× with the blur while what was recoverable falls only 3.5× ✅
17 Histogram equalisation HE, unclipped AHE, CLAHE, histogram matching, gamma, + match-the-true-histogram oracle Entropy is blind to a 9 dB improvement. The oracle gains +9.18 dB while entropy moves −0.019 bits — and entropy's favourite method is the worst in the table. On 5 of 11 photographs nothing beats doing nothing, and CLAHE's clip limit has an optimum entropy points away from ✅
18 Optical flow dense LK, pyramidal LK, Horn–Schunck, Farnebäck, DIS, + predict-zero control "LK fails for large motion" — the number is 1 pixel, and each pyramid level roughly doubles it (1→4→8→16→32). Two harness bugs got there first: a negated truth made every method score worse than predicting zero, and an unnormalised Sobel made LK recover exactly ⅛ of the motion ✅
19 Keypoint detectors Harris, Shi-Tomasi, FAST, SIFT, ORB, AKAZE, BRISK Harris (1988) is the most repeatable detector, and ORB is both faster and more repeatable than SIFT — the folklore is wrong about detection. But Harris covers 33% of the frame against SIFT's 81%, so the column that wins it the ranking is the one that makes it a poor choice. Right-angle rotations test nothing ✅
20 Deblurring inverse, Wiener, Richardson–Lucy, regularised LS, unsharp control, + blind angle estimation Handed the true kernel, the exact inverse scores 17 dB below doing nothing. Only Richardson–Lucy clearly beats the control, and it diverges past an optimum PSNR and SSIM disagree about. The blind angle estimator was wrong twice over, the two errors cancelling at exactly 90° ✅
21 Single-image super-resolution nearest, bilinear, bicubic, Lanczos, edge-directed, back-projection, + band-limited reference The entire nearest-to-Lanczos argument is worth 0.49 dB; modelling the degradation is worth +1.62. And the choice of downsampler moves the score by 2.12 dB — more than the spread between all six methods. Nearest scores the highest "high-frequency energy" and is the worst method ✅
22 Morphology erosion → black-hat, 3 structuring elements, skeletons, hit-or-miss On a binarised photograph no operation at any kernel size beats doing nothing — a real binarisation has genuine single-pixel structure. The generated test scene says the opposite, which is why both are reported. A cross keeps 360× more diagonal structure than a rectangle ✅
23 FFT filtering ideal/Butterworth/Gaussian low-pass, notch rejection, homomorphic The one thing the spatial domain cannot do: a blind notch removes periodic interference for +14.62 dB where a median filter manages +1.73 — and it matches its own oracle to 0.0 px. Ringing had to be measured on a step edge, because on photographs the obvious metric ranks Gaussian as the worst ringer ✅
24 Region segmentation watershed ±markers, region growing, mean-shift, SLIC, GrabCut, + a grid of rectangles A grid of rectangles that never looked at the image beats five of six real methods on IoU. Scored against human boundaries instead, the grid comes last where it belongs and the best method reaches less than half the human ceiling (0.433 against 0.904) ✅
25 Matching + RANSAC SIFT/ORB/AKAZE, ratio test, cross-check, least squares, RANSAC, LMEDS, MAGSAC++ LMEDS breaks at exactly its theoretical 50% — 0.222 px at 40%, 190.9 px at 60%. RANSAC holds to 80% and costs 46,000 iterations at 90%. And SIFT wins here on descriptor accuracy (0.222 px vs ORB's 0.927) having lost to ORB on detection in project 19 ✅
26 Do quality metrics agree? MSE, PSNR, SSIM, MS-SSIM, GMSD, VIF — six damages bisected to one PSNR At an identical 28 dB, SSIM spans 0.692 to 0.955. A one-pixel shift cannot be made mild enough to reach 28 dB while being nearly invisible. VIF was scoring the damage as an improvement, climbing to 40.7 as contrast was destroyed ✅
27 JPEG from scratch DCT, standard tables, 4:2:0, zig-zag, RLE, entropy estimate — every stage switchable Quantising pixels instead of DCT coefficients costs 9.33 dB at 2.3× the bitrate — the transform is what makes the quantisation affordable. Chroma subsampling costs 0.03 dB for 24% of the bits. Below quality 75 the top-frequency coefficient survives in no block of any image ✅
28 Canny parameter sensitivity σ × low × ratio grid, on shapes and on human-annotated photographs Twelve settings score a perfect 1.000 on the generated shapes scene — a saturated benchmark cannot choose between them, and picking the wrong one costs 19% on photographs. σ explains 30% of the variance; the high:low ratio every tutorial discusses explains 0.03% ✅
29 Object tracking template, mean-shift, CamShift, LK, LK+Kalman, MOSSE from scratch Three metrics, three winners — template on mean IoU (0.608), MOSSE on survival (0.827), LK on centre error (22.5 px). A box nailed to frame 1 beats CamShift (0.248 vs 0.148). Adding a Kalman filter to the flow tracker costs 0.22 of survival: smoothing a confident error carries it further ✅
30 Background subtraction frame diff, running average, median, MOG2, KNN + 2 controls The truth threshold picks the winner. At 1.5× the measured noise floor the median background wins (0.450) and KNN is 4th; at 4× KNN wins (0.604) and the median is last. Pixel accuracy is beaten by doing nothing (0.962). And a better background model gives a worse mask: recall 0.74→0.98 while IoU falls 0.45→0.25 ✅
31 The G&W 8-stage pipeline Laplacian, Sobel, smoothing, mask, sum, power-law — ablated one stage at a time A single unsharp mask beats all eight stages on every column, including SSIM. Stage (e) contributes nothing (+0.010 when removed), and the mask stage restrains rather than adds. Both Laplacian signs raise acutance; the wrong one scores SSIM −0.055 ✅
32 Hough transforms standard and probabilistic lines, circles, on shapes and human-annotated photographs Recall stays at 1.000 out to noise σ45 — voting is the strongest robustness result here. On photographs it is less precise than the Canny it votes on (0.117 vs 0.194) and marks 55% of the frame as line ✅
33 Texture descriptors GLCM, LBP, Gabor, Laws + raw-histogram control A descriptor is an invariance, not a quality. LBP is the worst on clean patches (0.701, below a plain histogram) and the only one above chance after a relight (0.640 vs 0.083 = chance). Laws loses nothing to a 90° turn; GLCM computes four angles and still drops to 0.615. At 45° the do-nothing control beats all four ✅
34 RGB → grayscale BT.601, BT.709, linear-light, value, contrast-preserving Every fixed weighting has a blind plane, including the correct one. A scene on BT.709's plane gives it 0.39 grey levels where BT.601 sees 14.17. The whole 601-vs-709 argument is worth 2.18 levels; contrast-preserving costs 109× the time for 0.0011 of edge recall. Across 55,596 real colour edges the catastrophe is essentially absent ✅
35 Camera calibration & distortion 7 distortion models · view-count sweep · stereo · straightness check The reported RMS gets better as the calibration gets worse, twice over. Three views report 0.205 px (the best here) and score 0.557 on unseen ones; ten report 0.412 and score 0.311. The 14-coefficient model has the lowest error and puts the principal point 67 px out. Meanwhile the stereo baseline is stable to 0.38% ✅
36 Shape descriptors Hu moments, Fourier descriptors, chain codes The log-Hu recipe in every tutorial is broken at zero — np.sign(0) is 0, so an exactly-zero moment logs to 0 not −12; flooring takes rotation error 5.059 → 0.150. Against 12 human-traced silhouettes, Hu's rotation invariance is 0.001 where two people tracing the same object differ by 0.753 ✅
37 Template matching SSD, NCC, ZNCC, multi-scale Raw cross-correlation localises 1 template in 9 on undegraded photographs — it finds the brightest patch, not the matching one. Only ZNCC survives an offset, and it takes a negative one to show it. ZNCC and NCC localise identically on a clean scene and differ 357× in peak-to-mean: that is confidence, not accuracy ✅
38 Panorama stitching 5 blenders incl. multi-band · exposure sweep · one real pair PSNR ranks the blenders backwards. The winner is the control that does not stitch (23.85 dB) and it comes last of five on the seam (29.9 grey levels against multi-band's 19.5) — a panorama cannot be scored by fidelity to one of its own inputs. And at matched exposure every method lands within 0.42 grey levels: a blender comparison without an exposure difference measures nothing ✅
39 White balance grey-world, white-patch, shades-of-grey, grey-edge One clipped highlight reduces white-patch to exactly the do-nothing control — 15.949° both. Grey-world hallucinates 23.0° out of a neutrally-lit scene where doing nothing scores 0.000. On an almost-uncast image all five methods make it worse. The usual Minkowski p=6 costs grey-edge 43% more error than p=2 ✅
40 Multi-frame super-resolution shift-and-add, iterative back-projection, n=1 control Frames break project 21's plateau: +2.26 dB against 0.49 for the whole nearest-to-Lanczos argument — but +0.68 dB of it is available from one frame, so the n=1 control is what separates deblurring from fusion. Naive averaging is worse than a single frame. Bug: a negated ECC translation reported it 11× worse when it is 6.3× better ✅
41 Point transforms log, power-law, piecewise-linear, bit-plane A LUT and the arithmetic it replaces are bit-identical only if they round the same way — truncating instead of rounding disagreed on 136 of 256 levels, every one by exactly one, with no visible symptom. Fixed, identical at 43× the speed. The textbook claim that brightening bands because it duplicates levels is backwards ✅
42 Image registration phase correlation, ECC, mutual information Mutual information is exact (0.000 px) on every intensity relationship including a non-monotonic remap, and costs 426×. ECC returns nothing on inverted intensities. The Hanning-window advice is backwards: worth −0.024 px on the case it is for, and on inverted images it causes the failure — 9 of 12 go 30–635 px out with it, all 12 under 0.13 px without ✅
43 Grayscale → colour Levin scribbles, Welsh transfer, pseudo-colour, luminance lookup + 2 controls The ceiling was knowable before anything ran: the selection axis predicts an oracle's residual at r = 0.995. Two of four colourisers score worse than returning the grey image. Welsh transfer's score is the reference, not the method — the spread across eleven references is 39.1 where the method is worth 6.6 ✅
44 Poisson blending copy-paste, feather, Poisson, mixed gradients The method that works moves the pasted pixels most — Poisson changes the region by 51 grey levels, copy-paste by 0, and copy-paste has the worst seam. Any fidelity-to-source metric ranks them exactly backwards. Bug: the from-scratch solver was a complete no-op, seeding its boundary from the source so f = source is a fixed point ✅
45 Wavelet denoising soft/hard × VisuShrink/BayesShrink + spatial competitors The sparsity premise holds and the method still loses — signal carries 0.69–0.99 of its detail energy in the top 10% of coefficients against noise's 0.44, and a bilateral filter beats every wavelet variant by 2.1 dB for a quarter of the time. At σ 5 five of seven methods are worse than doing nothing. Soft vs hard flips with the threshold rule ✅
46 Epipolar geometry 8-point raw/normalised, 7-point, LMedS, RANSAC + 3 controls The best estimate of F here has one parameter. A control that assumes a rectified rig and fits a single vertical offset by a median scores 0.74 px against RANSAC's 1.46. RANSAC reports an 81% inlier rate on a coplanar configuration that cannot determine F. And the inlier rate improves as the real error doubles ✅
47 Colour space robustness RGB, HSV, Lab, YCrCb, normalised RGB "HSV is lighting robust" is exactly half true. HSV is exactly brightness-invariant (0.487 → 0.487) and the least accurate space undegraded (Lab 0.784, YCrCb 0.790) — and a warm cast costs it 37%. At cast level 0.5 every space scores 0.000, and a white balance restores four of five: a colour space is not a substitute for correcting the illuminant ✅
48 Licence-plate localisation Sobel+morphology, top-hat, MSER, contour+aspect, 2 Haar cascades · whole-frame and fixed-box controls The repository's only human annotation — a drawn box and the typed plate text. Two defensible metrics, two opposite orders: IoU >= 0.5 puts Sobel first at 8/14 and OpenCV's plate cascade at 5; coverage of the plate reverses it to 9 and 5. The metric introduced to fix IoU is the worse one — the character count, the only measure using the text, sides with IoU (8 vs 4), and coverage is won outright by returning the whole photograph (14/14, IoU 0.01). Plate size (67× range) does not predict difficulty (r=+0.29), and the winner is the same at every threshold from 0.3 to 0.7 ✅
49 Face detection 6 OpenCV cascades (4 Haar, 2 LBP) · nothing / one-centre-box / every-box controls Retitled from face recognition — see below. A cascade cannot see below its own training window: lbpcascade_frontalface_improved is 45×45 and finds 3 faces where its 24×24 predecessor finds 90; upscaling recovers it 28.7× against a next best of 1.54×, and minSize 12 vs 24 is byte-identical for all six because minSize is a floor, not a resampling. At 20° the best cascade keeps 48% of its own detections, at 30° 3% — and that score alone ranks a fixed centre box first at 1.00. Six cascades agree unanimously on 2 of 104 boxes ✅
50 Demosaicing / camera ISP nearest, bilinear, Malvar, VNG, edge-aware Every method is 2.1–2.8 dB worse on edge pixels than over the whole frame — whole-image PSNR averages the failure away. Cross-channel interpolation is the whole gain (+4.86 dB). OpenCV's edge-aware flag produces genuinely different pixels and scores identically to its own bilinear on edges, the pixels it is named for ✅
51 Focus stacking / depth from focus 5 focus measures × 7 pooling windows + 3 controls The pooling window matters 2.3× more than the focus measure (3.34 dB against 1.44), and the best measure is 0.034 dB from an oracle handed the answer. On flat regions no measure can be right: agreement 0.982 → 0.496, and the five disagree on 58% of those pixels. Detail predicts the ceiling at r = −0.969 ✅
52 Barcode / QR detection gradient+morphology, variance, QR finder The generated background was hiding a useless localiser — 1.000 on generated clutter, 0.021 on twelve real photographs, a 48× difference from changing nothing but the background. And found is not decoded: at 6.7 px per module the code is located 1.000 and decoded 0.000 ✅
53 Industrial defect detection 6 residual/texture/spectral detectors · 4 defect kinds · clean-surface arm The detectors are complementary, not competing. A smear — a local loss of texture at unchanged brightness — is found by the local standard deviation (0.58) and by 4 of the other 5 exactly never. And on twelve surfaces with nothing wrong with them they mark 3.7% to 14.3%; on brick paving one marks 76%. Pixel accuracy is unusable: a defect covers 1.07%, so flagging nothing is right 98.9% of the time ✅
54 Motion-triggered security alert 6 decision rules (area, persistence, cooldown) · always / never / rate-matched-random controls The mask is not the system, the decision is. Given the oracle mask — exact per-pixel truth — "alert on any motion in the zone" fires 244 times for 7 intrusions, 35 calls per intruder. A cooldown takes it to 8, still 7/7, for 0.7 s of latency; one second of it removes 88% of alerts at no cost at all. Swapped for a causal mask a camera could compute live, the tuned rule is identical — 8 alerts, 7/7 — so the mask quality project 30 optimises barely moves the alarm. A rate-matched random control fires 8 times and catches 3 of 7 ✅
55 Hand gesture recognition 7 segmenters (YCrCb/HSV/Lab skin, adaptive, Otsu, GrabCut) · oracle-mask, ellipse, whole-frame and nothing controls Settle the second stage first: it does not work. Given a perfect mask, two classical finger rules over ten settings manage 3 of 5. Then the first stage: over 324 composites the spread across backgrounds is 0.831 IoU against 0.425 across hands — the background moves the answer 2.0× further than which hand it is, and how skin-coloured it is predicts the score at r = −0.70 before anything runs. An ellipse drawn without reading the image scores 0.441 and beats 2 of the 7 ✅
56 Pedestrian detection HOG + linear SVM, real footage vs drawn silhouettes Drawn silhouettes are not a benchmark. HOG's SVM margin is 0.51 on drawings against 1.59 on real people, and recall on the composited scenes never exceeds 1 in 12 at any setting — so the project was rebuilt on real footage, checked against motion evidence that shares no information with it ✅
57 Red-eye removal colour, +shape, +face, +eye constraints + empty-truth control Pupil PSNR rates the naive detector within 0.124 dB of the best while it marks 217× more of the frame. On six photographs with no red-eye at all, colour-only marks 86,294 px and the face constraint 98. And zeroing the red channel scores below doing nothing on half the portraits ✅

What it does

  • Runs 3-6 classical methods per problem on the same inputs, with the same metrics and a timing column (warm-up discarded, median of N runs).
  • Scores against exact ground truth where a degradation is applied on purpose (darken, haze, blur, paste a region), against human annotations where they exist (BSDS500 segmentations, with the one-annotator-vs-the-rest ceiling), and shows unannotated photographs without a score.
  • Uses 457 real photographs (BSDS500, USC-SIPI, OpenCV samples), with no image reused across projects (checked by perceptual hash).
  • infer.py in each project runs its methods on your own image.

Sample input / output

Each project opens with the same figure: input on the left, one column per method, four different subjects down the rows (Sr 1-4), the score in each cell.

01 Document scanner - six page detectors, corner error in pixels (green = detected, red = ground truth):

Six page detectors on four documents

13 Denoising - Gaussian noise at sigma 25, PSNR against the clean photograph:

Seven denoisers on four photographs

24 Region segmentation - boundaries scored against BSDS500 human annotations (F, precision, recall); the grid of rectangles is a control that never looks at the image:

Six segmenters against human boundaries

44 Poisson blending - a pasted region, seam visibility per method (1.0 means the edge cannot be told from its surroundings):

Five blenders on four composites

Results

From each project's README and results/results.json:

  • Document scanner (01): the tutorial Canny + contour detector lands at 2.70 px mean corner error; plain Otsu on brightness gets 1.07 px and HSV saturation 0.90 px. Page aspect ratio is recovered to 0.07% in closed form from four corners, against 8.01% for the edge-length heuristic.
  • Low-light (03): at gamma 3 only 158 of 256 tone levels survive, so even an exact inverse reaches 22.31 dB.
  • Copy-move forgery (07): block matching scores 0.9925 IoU on an exact copy and 0.0000 at 2 degrees of rotation.
  • Video stabilisation (08): swapping the smoother moves the result 8.4x more than swapping the motion estimator.

The headline finding of all 57 projects is in the table above.

Tech stack

  • Python 3.10+
  • OpenCV (opencv-python-headless, pinned below 5.0 because projects 02 and 57 load the Haar cascade files that OpenCV 5 no longer bundles)
  • scikit-image, NumPy, SciPy, pandas
  • matplotlib for every figure
  • pytest; GitHub Actions CI on Python 3.10 and 3.12

Run it

git clone https://github.com/hammasbuilds/classical-computer-vision.git
cd classical-computer-vision
python -m venv .venv
.venv/Scripts/activate            # Windows; macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"

cd projects/01_document_scanner
python run.py                          # reproduces every number and figure
python infer.py path/to/your/image.jpg # run the methods on your own image

Tests, from the repo root:

python -m pytest -q

There are no model weights and no datasets to download; the photographs are in assets/real/.

Licence

MIT - see LICENSE. Photographs come from the Berkeley Segmentation Dataset (BSDS500), the USC-SIPI image database and OpenCV's sample data; their provenance is listed in assets/real/README.md.

About

OpenCV · scikit-image · NumPy · SciPy — Measured comparisons of classical computer vision algorithms - no deep learning, no training, no GPU. Every finding is a number the code produced.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages