<jupyter_start><jupyter_text>Gradients and Histograms GradientsIn vector calculus, the gradient of a scalar-valued differentiable function $f$, of several variables $f:\mathbf{R}^n \rightarrow \mathbf{R}$, is the vector field, whose value at point $p$ is a the vector whose components are the partial derivatives of $f$ at $p$.For a 1D function $f$ depending on variable $x$, the gradient is simply the derivative of the function. At a given point, the derivative gives the local slope of the function.For example, let's consider the function $f(x) = x$. The derivative of this function is $f'(x) = 1$. So this function is a straight line, and its derivative is a constant, which means that the slope is a constant.<jupyter_code>import matplotlib.pyplot as plt
import numpy as np
import cv2

%matplotlib inline
x_values = np.arange(-4,4,0.1)

f_x = np.array([x for x in x_values])
g_x = np.array([1 for _ in x_values])

plt.plot(x_values, f_x)
plt.plot(x_values, g_x)

plt.xlabel("f(x) = x and its derivative, f'(x) = 1")

plt.show()<jupyter_output><empty_output><jupyter_text>If we now consider $f(x) = x^2$, the derivative is $f(x) = 2x$. For $x0$, the derivative is positive and the function is increasing. For $x = 0$, the derivative is 0. This means that the local slope is 0. In other words, here, the function is locally flat.<jupyter_code>x_values = np.arange(-4,4,0.1)
f_x = np.array([x**2 for x in x_values])
g_x = np.array([2*x for x in x_values])

plt.plot(x_values, f_x)
plt.plot(x_values, g_x)

plt.xlabel("f(x) = x^2 and its derivative, f'(x) = 2x")

plt.show()<jupyter_output><empty_output><jupyter_text>In 2D, in the $(x,y)$ plane, a function of $x$ and $y$ is a surface giving the altitude at every point. The gradient is the generalization of the derivative: at a given $(x,y)$ point, the gradient is oriented towards the direction of maximum slope, and its magnitude is the slope of the plane tangent to the surface at this point. ![Gradient](images/Gradient2.svg.png)The gradient, represented by the blue arrows, denote the direction of greatest change of a scalar function. The values of the function are represented in greyscale and increase in value from white (low) to dark (high).In a black and white picture, the greyscale level is analogous to the altitude, and the gradient is a measure of how fast the level changes and of the direction of the change. An edge in the picture (a black to white transition) leads to a large gradient perpendicular to the edge, from white to black. In a colour picture, one can compute a gradient for each colour level, and e.g. take the maximum gradient over the three colour levels.  How to calculate the gradient of an image?An image gradient is a directional change in the intensity or color in an image. Mathematically, the gradient of a two-variable function (here the image intensity function) at each image point is a 2D vector with the components given by the derivatives in the horizontal and vertical directions. At each image point, the gradient vector points in the direction of largest possible intensity increase, and the length of the gradient vector corresponds to the rate of change in that direction.Since the intensity function of a digital image is only known at discrete points, derivatives of this function cannot be defined unless we assume that there is an underlying continuous intensity function which has been sampled at the image points.Approximations of these derivative functions can be defined at varying degrees of accuracy. One such way is by  by using finite differences.A finite differene is an expression of the form $f(x+b)-f(x+a)$.Three types are commonly considered: forward, backward, and central finite differences.1. A forward finite difference is given by: $f(x+h) - f(x)$2. A backward finite difference is given by: $f(x) - f(x-h)$3. A central difference is given by: $f(x+h)-f(x-h)$For image processing, a central difference is usually used to calculate the gradient. In this case, $f(x,y)$ is the color of a pixel at location $(x,y)$, and the gradient vector of the pixel is defined as:\begin{equation*}\nabla f(x,y) =  \begin{bmatrix} g_x \\ g_y \end{bmatrix} = \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix}\end{equation*}\begin{equation*}\nabla f(x,y) =  \begin{bmatrix} f(x+1, y) - f(x-1,y) \\ f(x,y+1) - f(x,y-1) \end{bmatrix}\end{equation*}![Image gradient](images/image-gradient-vector-pixel-location.png)For the case of $\frac{\partial f}{\partial y}$ we can apply a 1-dimensional filter to the image $\mathbf{A}$ by convolution:\begin{equation*}\frac{\partial f}{\partial y} = \begin{bmatrix} 0 & 1 & 0\\ 0 & 0 & 0 \\ 0 & -1 & 0 \end{bmatrix} * \mathbf{A}\end{equation*}For the case of $\frac{\partial f}{\partial x}$ we can use the transpose of the previous filter:\begin{equation*}\frac{\partial f}{\partial x} = \begin{bmatrix} 0 & 0 & 0\\ -1 & 0 & 1 \\ 0 & 0 & 0\end{bmatrix} * \mathbf{A}\end{equation*}With $g_x$ and $g_y$, we can calculate the direction and the magnitud of the gradient:\begin{equation*}\theta = \arctan \bigg(\frac{g_y}{g_x}\bigg)\\||\nabla f|| = \sqrt{g_x^2 + g_y^2}\end{equation*}<jupyter_code>radial_gradient = cv2.imread('images/smooth_radial_gradient.png')
gray_gradient = cv2.cvtColor(radial_gradient, cv2.COLOR_BGR2GRAY)

plt.imshow(gray_gradient, cmap='gray')
# A more detailed approach would be to consider the following kernels:

filter_x = np.array(
            [
            [-1, 0, 1],
            [-1, 0, 1],
            [-1, 0, 1]
            ])

filter_y = np.array(
            [
                [-1, -1, -1],
                [0, 0, 0],
                [1, 1, 1]
            ])

filter_xy = np.array(
            [
                [0, -1, 0],
                [-1, 0, 1],
                [0, 1, 0]
            ])

filtered_gradient_x = np.zeros(gray_gradient.shape)
filtered_gradient_y = np.zeros(gray_gradient.shape)
'''
for row in range(1, gray_gradient.shape[0]-1):
    for col in range(1, gray_gradient.shape[1]-1):
        #area = gray_gradient[row-1:row+2, col-1:col+2]
        #value_x = np.sum(np.multiply(area, filter_x))
        #value_y = np.sum(np.multiply(area, filter_y))
        
        #filtered_gradient_x[row][col] = value_x
        #filtered_gradient_y[row][col] = value_y
        
        for i in range(3):
            for j in range(3):
                filtered_gradient_x[row][col] += gray_gradient[row+i-1][col+j-1]*filter_x[i][j]
                filtered_gradient_y[row][col] += gray_gradient[row+i-1][col+j-1]*filter_y[i][j]                
'''

filtered_gradient_x = cv2.filter2D(gray_gradient, cv2.CV_32F, filter_x)
filtered_gradient_y = cv2.filter2D(gray_gradient, cv2.CV_32F, filter_y)
filtered_gradient_xy = cv2.filter2D(gray_gradient, cv2.CV_32F, filter_xy)

f, (ax1,ax2, ax3) = plt.subplots(1, 3, figsize=(15,5))
ax1.set_title('Filter x')
ax1.imshow(filtered_gradient_x, cmap='gray')

ax2.set_title('filter y')
ax2.imshow(filtered_gradient_y, cmap='gray')

ax3.set_title('filter xy')
ax3.imshow(filtered_gradient_xy, cmap='gray')<jupyter_output><empty_output><jupyter_text>HistogramsA histogram is a data structure that is used to compress data and to represent its probability distribution. An histogram can have many dimensions but in practice, 1D and 2D histograms are most often used.We define the histogram by describing its bins. The bins are baskets that count the number of entries with a value falling within the bin range.<jupyter_code>values = np.random.random_sample((50,))
print(f"Max value: {np.max(values)}, Min value: {np.min(values)}")
counts, bins, bars = plt.hist(values, bins=5, range=(0,1), rwidth = 0.8)

print(counts, bins, bars)

plt.show()<jupyter_output>[ 9. 12.  9.  7. 13.] [0.  0.2 0.4 0.6 0.8 1. ] <a list of 5 Patch objects><jupyter_text>The following cell shows how to calculate a histogram from scratch.<jupyter_code>buckets = 5
histogram_values = [0]*buckets

for value in values:
    index = int(value*buckets)
    histogram_values[index] += 1
    
x_values = np.arange(0, 1, 1/buckets)
x_values = np.append(x_values, 1)

positions = np.arange(0,buckets)

plt.bar(positions, histogram_values)
plt.xticks(positions - 0.5, np.round(x_values, 1))
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1171411265899455, 'fraction_numerical': 0.02035130224106602, 'mean_word_length': 3.9915356711003627, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '319763', 'license': 'MIT', 'n_tokens_mistral': 2796, 'n_tokens_neox': 2609, 'n_words': 968, 'path': 'Notebooks/9. CV. Histograms and gradients.ipynb', 'repo_name': 'octavio-navarro/Vision_2020_02', 'size': '8255'}