How it works...
Matrices in OpenCV's Python interface are presented with NumPy arrays. NumPy provides powerful yet clear tools to deal with multi-dimensional matrices, which are also called tensors. And, of course, NumPy supports plain 2-dimensional matrices. That's why we need to import its module. And this is the reason why we're using a lot of np's functions in this recipe.
Here it's necessary to say a few words about matrix dimensions and types. Matrices have two independent characteristics—shape type and element type. Firstly, let's talk about shape. Shape describes all dimensions of the matrix. A matrix usually has three spatial dimensions: width (also called number of columns), height (also called number of rows), and number of channels. Usually it's subscribed in height, width, channels format. OpenCV works with full color or grayscale matrices. This means that only 3-channels or 1-channels may be handled by OpenCV routines. Grayscale matrices may be imagined as planar tables of numbers, where each element (pixel) stores only one value. Full color ones may be considered as tables where each element stores not one but three values in a row. An example of a full color matrix is one with red, green, and blue channels, respectively—this means each element stores values for red, green, and blue components. But for historical reasons, OpenCV stores color values for RGB representation in BGR format—so be careful.
Another feature of a matrix is its element type. The element type defines which data type is used to represent element values. For example, each pixel can store values in the [0-255] range—in this case, it is np.uint8. Or, it can store float (np.float32) or double (np.float64) values.
np.full is used to create matrices. It takes the following parameters: shape of the matrix in (height, width, channels) format, initial value for each pixel (or each component of the pixel), and the type of pixel value. It's possible to pass a single number as a second parameter—in this case, all pixel values are initialized with this number. Also, we can pass initial numbers for each pixel element.
np.fill helps you to assign the same values for all pixels—just pass a value to assign as a parameter. The difference here between np.fill and np.full is that the first one doesn't create a matrix but just assigns values to existing elements.
To get access to individual pixels, you can use the [] operator and specify indexes of the desired element; for example, image[240, 160] gives you access to the pixel at height 240 and width 160. The order of the indexes corresponds with the order of dimensions in the matrix shape—the first index is along the first dimension, the second index is along the second dimension, and so on. If you specify indexes only for some dimensions, you'll get a slice (a tensor with a low dimension number). It's possible to address all the pixels along a dimension by using a colon (:) instead of index. For example, image[:, 320, :] actually means—give all pixels along height and channels that have index 320 along the width dimension.
The : symbol also helps to specify certain regions inside the matrix—we just need to add start the index before : and end the index after : (the end of an index isn't included in the range). For instance, image[100:600, 100:200, 2] gives us all pixels with height indexes in the range of [100, 600], width indexes in the range of [100, 200], and channel index 2.