Categories
Uncategorized

Basic Image Processing: Edge Detection

The next technique that we will discuss is edge detection, which is one of the most simple ways of detecting “features” in the image. When talking about image features, what we really mean is something in the image that can be mathematically defined which distinguishes one part of an image from the rest of the image. The pixel values themselves can be considered features, but it is often helpful to extract different types of features that can allow to generalize about different regions of an image. This method of edge detection, similar to image blurring, moves a kernel throughout an entire image, and highlights the pixels where the values change dramatically when compared to their neighbors. For this technique, we will define our own filter (albeit a common one called the Sobel operator) and apply it to the entire image.

Looking at the functions defined below, you should notice that they are almost exactly the same as the blur_image function we previously defined. The only difference comes in the first line (lines 15-17, and lines 25-27, respectively), where we define the kernel. I should note that while this definition takes up 3 lines in reality, the program treats this as only one line because lines 15 and 16 never properly end the statements that come before it. You can see that the function we use (np.array) has a “([[“ right after the function name, while the end of the line only contains “],”. These first three lines could have similarly been typed out: “hor_kernel = np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]])”, but I chose to separate them into 3 different lines in order to emphasize that we are, in fact, defining a 2-dimensional 3×3 array to serve as the filter. Notice, as well, that in defining the array, we have called the NumPy library (shortened to np).

A Sobel filter is always oriented with negatives on one side of the filter, positives on the other, and zeros in the middle. Applying any filter to a group of pixels is actually done via a process called convolution, which is demonstrated on the right below. Here, the Sobel filter is being applied to 2 pixels, the orange and the blue. In convolution, you place the center pixel of the filter over the pixel you are currently evaluating, then multiply each overlapping element, and add everything together. This results in a value of -23 for the orange square and -310 for the blue square. However, in reality only the magnitude of these values will be considered, as the edge strength is considered as one thing (the magnitude), while the edge direction determines the positive or negative sign. When additionally using a vertical edge filter (as is defined in our function vert_edge below), the overall edge strength can be calculated, as well as a specific angle, which can be calculated based on the ratio of these two edge values for a particular pixel. It should be noted that detecting the edges of a color image requires running through the 2-dimensional image 3 times, once for each the blue, green, and red values of a given pixel. This results in edge strengths for each color, which can again be combined to form the final edge-detected image.

After typing the above functions into your Image_Process.py file, you can test them in the same way as demonstrated by the blur_image function above. You should see images that look similar to the below pictures. Notice the highlighted areas for the horizontal and vertical filters, respectively. The horizontal kernel results in mostly vertical lines, while the vertical kernel results in horizontal lines, because they are horizontally and vertically comparing pixels, respectively:

We can also write a function to combine these two edge detectors together through addition. This may not be the most effective way of combining them, but it can achieve some interesting results. Note that the only reason that direct addition works is because of the datatype of the number. These numbers are represented as int8, meaning that they are an integer with 8 bits. As each bit is binary, that means that this number can only hold up to 28 = 256 possible numbers, matching the values between 0 and 255 which are used for the GBR values of each pixel. You can think of this by saying that any number higher than 256 (or any multiple of 256) subtracts 256 until the given number fits within these constraints. In the actual computer, it just means that any bit past the 8th bit (or any bits beyond this) isn’t considered, as it hasn’t been allocated in memory. The code and results of this can be seen below: