Python
Cross-Correlation
Normalized Cross-Correlation
Signal Processing
Data Analysis

Normalized Cross-Correlation in Python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Normalized cross-correlation measures similarity between two signals while correcting for scale and offset. That normalization matters because raw cross-correlation can be dominated by amplitude rather than by shape, which makes it less useful for template matching, time-series alignment, and image patch comparison.

Why Normalization Changes the Result

Plain cross-correlation answers a question like "where do these signals overlap strongly". But if one signal is just a scaled version of the other, the raw correlation magnitude grows with amplitude.

Normalized cross-correlation instead compares centered and scaled values, so the score is easier to interpret:

  • '1 means perfect similarity'
  • '0 means no linear similarity'
  • '-1 means perfect inverse similarity'

That makes NCC much more suitable when the signal magnitude may vary but the pattern shape is what matters.

Compute NCC for Two 1D Arrays with NumPy

For two equal-length vectors, a simple implementation is just the cosine similarity of the mean-centered arrays.

python
1import numpy as np
2
3def normalized_cross_correlation(x, y):
4    x = np.asarray(x, dtype=float)
5    y = np.asarray(y, dtype=float)
6
7    if x.shape != y.shape:
8        raise ValueError("x and y must have the same shape")
9
10    x_centered = x - x.mean()
11    y_centered = y - y.mean()
12
13    denominator = np.linalg.norm(x_centered) * np.linalg.norm(y_centered)
14    if denominator == 0:
15        raise ValueError("cannot normalize a constant signal")
16
17    return np.dot(x_centered, y_centered) / denominator
18
19x = [1, 2, 3, 4]
20y = [2, 4, 6, 8]
21
22print(normalized_cross_correlation(x, y))

This returns a value very close to 1.0, because the two signals have the same shape even though one is scaled.

Sliding NCC with SciPy-Style Logic

If you want to compare a short template against every valid position in a longer signal, compute NCC over sliding windows.

python
1import numpy as np
2
3def sliding_ncc(signal, template):
4    signal = np.asarray(signal, dtype=float)
5    template = np.asarray(template, dtype=float)
6
7    window = len(template)
8    scores = []
9
10    for i in range(len(signal) - window + 1):
11        chunk = signal[i:i + window]
12        scores.append(normalized_cross_correlation(chunk, template))
13
14    return np.array(scores)
15
16signal = [0, 1, 2, 3, 4, 1, 2, 3]
17template = [1, 2, 3]
18
19print(sliding_ncc(signal, template))

This is the basic idea behind template matching in one-dimensional signals. The highest score indicates the best-aligned region.

Relation to scipy.signal.correlate

scipy.signal.correlate computes correlation efficiently, but it does not automatically give you normalized cross-correlation in the strict window-by-window sense. If you need true NCC, you still have to normalize by local mean and local norm.

That distinction matters because people often compute:

python
from scipy.signal import correlate

and assume the result is normalized similarity. It is not. It is raw correlation unless you add the normalization step yourself.

Image Matching and OpenCV

For image template matching, OpenCV already implements normalized methods such as TM_CCOEFF_NORMED.

python
1import cv2
2import numpy as np
3
4image = np.random.randint(0, 255, (50, 50), dtype=np.uint8)
5template = image[10:20, 10:20]
6
7result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
8_, max_val, _, max_loc = cv2.minMaxLoc(result)
9
10print(max_val)
11print(max_loc)

This is often the right tool when the problem is actual image template matching rather than generic numeric vectors.

Watch the Constant-Signal Edge Case

Normalization divides by standard deviation or vector norm. If one of the inputs is constant, the denominator becomes zero and NCC is undefined.

That means code should either:

  • raise an error
  • return a sentinel value
  • define special handling for constant inputs

Ignoring this case leads to divide-by-zero warnings and invalid results.

Common Pitfalls

  • Treating raw cross-correlation as if it were normalized.
  • Forgetting to subtract the mean before normalizing.
  • Comparing signals of different lengths without defining the alignment rule.
  • Ignoring zero-variance signals, which make NCC undefined.
  • Using a signal-processing implementation when the real problem is image template matching, where OpenCV may be simpler.

Summary

  • Normalized cross-correlation compares signal shape while compensating for scale and offset.
  • For equal-length vectors, it is easy to implement with NumPy by centering and normalizing.
  • Sliding NCC is useful for finding the best template position in a longer signal.
  • 'scipy.signal.correlate gives raw correlation unless you add normalization yourself.'
  • Handle constant-signal edge cases explicitly to avoid invalid results.

Course illustration
Course illustration

All Rights Reserved.