Why is the first DCT square black here for the DCT basis images?

I'm trying to understand the DCT, for this purpose I wanted to generate the image of all basis images of the DCT. And given the formula by matlab:

$$B_{pq} = \alpha_p \alpha_q \sum_{m=0}^{M-1} \sum_{n=0}^{N-1} A_{mn} \cos \left( \frac{\pi(2m+1)p}{2M} \right) \cos \left( \frac{\pi(2n+1)q}{2N} \right)$$

where $$0 \leq p \leq M-1$$, $$0 \leq q \leq N-1$$, and the coefficients $$\alpha_p$$ and $$\alpha_q$$ are defined as:

$$\alpha_p = \begin{cases} \frac{1}{\sqrt{M}}, & \text{if } p = 0 \\ \sqrt{\frac{2}{M}}, & \text{if } 1 \leq p \leq M-1 \end{cases}$$

$$\alpha_q = \begin{cases} \frac{1}{\sqrt{N}}, & \text{if } q = 0 \\ \sqrt{\frac{2}{N}}, & \text{if } 1 \leq q \leq N-1 \end{cases}$$

This represents the two-dimensional discrete cosine transform (DCT) of an $$M \times N$$ matrix $$A$$.

I implemented the following.

import numpy as np
import matplotlib.pyplot as plt

# Define the DCT basis function
def dct_basis_function(p, q, m, n, M, N):
alpha_p = 1 / np.sqrt(M) if p == 0 else np.sqrt(2 / M)
alpha_q = 1 / np.sqrt(N) if q == 0 else np.sqrt(2 / N)
return alpha_p * alpha_q * np.cos((np.pi * (2 * m + 1) * p) / (2 * M)) * np.cos((np.pi * (2 * n + 1) * q) / (2 * N))

# Generate all 64 basis images
M = N = 8
basis_images = np.zeros((64, 8, 8))
for p in range(M):
for q in range(N):
for m in range(M):
for n in range(N):
basis_images[p * N + q, m, n] = dct_basis_function(p, q, m, n, M, N)

# Display the basis images in an 8x8 grid
fig, axs = plt.subplots(8, 8, figsize=(10, 10))
for i in range(64):
row = i // 8
col = i % 8
axs[row, col].imshow(basis_images[i], cmap='gray')
axs[row, col].axis('off')

plt.show()


And the output is As you can see the first quadrant is pitch black, but in their demo, it is gray and in some other google searches, you can see it it is white. What am I doing here wrong?

For displaying purposes it is best to normalize your images to be between $$0$$ and $$1$$. Add the following line of code before plotting to normalize:

basis_images_normal = (basis_images - np.min(basis_images)) / (np.max(basis_images) - np.min(basis_images))


and then change the plotting code line as follows:

axs[row, col].imshow(basis_images_normal[i], cmap='gray', vmin=0, vmax=1)


This should give you your desired result!

If the basis_images[0] entry is printed, it looks correct:

[[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
[0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]]


The problem is that all entries are the same, and the image display shows everything as black, even though it could be considered to be grey.

• Is this only for displaying purposes or is this the actual basis? Commented Mar 18 at 18:10
• Only for displaying purpose as for computations you would use the coefficients $\alpha_p$ and $\alpha_q$ to ensure that the transform is orthonormal. This orthonormality is crucial because it means that the inverse DCT (IDCT) will perfectly reconstruct the original image from the DCT coefficients. So, to summarize: 1) For display: normalize the basis images to the range [0, 1] so that they map correctly to a displayable grayscale image. 2) For computation: use the actual mathematical definitions of the DCT basis functions without this type of normalization. Commented Mar 18 at 18:13
• You may find this other answer to a related question useful. Commented Mar 18 at 18:23