In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article is about how Python uses the Canny algorithm to detect the edges of coins. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
I. background of the question
There is a silver coin of one yuan on the paper. Can you find its coordinate equation with the help of Canny and Hough?
To determine the coordinate equation of a circle, we first need to detect its edge, and then find out its relative position and radius on the paper.
In this article, we use the Canny algorithm to detect the edges of silver coins on paper.
2. Canny algorithm
Canny can be used to get the edges of objects in an image, as follows
Perform Gaussian smoothing
Calculate the image gradient (record its intensity, direction)
Perform non-maximization inhibition
Carry out lag edge tracking
After the above four steps, the effect of extracting the edges of coins on paper is as follows
(1) Gaussian smoothing class GaussianSmoothingNet (nn.Module): def _ init__ (self)-> None: super (GaussianSmoothingNet, self). _ init__ () filter_size = 5 # shape = (1,5), Gaussian filtering kernel generated_filters = gaussian (filter_size,std=1.0). Reshape ([1 Filter_size]) # GFH (V): horizontal (vertical) Gaussian filtering kernel of gaussian filter of horizontal (vertical) self.GFH = nn.Conv2d (1,1, kernel_size= (1 filterfiltering size), padding= (0thod filterfiltering size 2)) self.GFV = nn.Conv2d (1,1, kernel_size= (filter_size,1) Padding= (filter_size//2,0)) # sets the value of w to the Gaussian smooth kernel, and the value of b to 0.0 init_parameter (self.GFH, generated_filters, np.array ([0.0])) init_parameter (self.GFV, generated_filters.T, np.array ([0.0])) def forward (self, img): img_r = img [: 0:1] # take out the data of the three channels of RGB img_g = img [:, 1:2] img_b = img [: 2:3] # filter the three channels of the picture horizontally and vertically blurred_img_r = self.GFV (self.GFH (img_r)) blurred_img_g = self.GFV (self.GFH (img_g)) blurred_img_b = self.GFV (self.GFH (img_b)) # merge into one picture blurred_img = torch.stack ([blurred_img_r) Blurred_img_g, blurred_img_b], dim=1) blurred_img = torch.stack ([torch.squeeze (blurred_img)]) return blurred_img
The picture after Gaussian smoothing (blur) is more blurred than the original image, as shown in the silver coin on the right side of the following figure.
For the complete code, please see: gaussian_smoothing
(2) Sobel operator calculates the gradient PAI = 3.1415926class SobelFilterNet (nn.Module): def _ init__ (self)-> None: super (SobelFilterNet, self). _ _ init__ () sobel_filter = np.array ([[- 1,0,1], [- 2,0,2], [- 1,0] ]) self.SFH = nn.Conv2d (1,1, kernel_size=sobel_filter.shape, padding=sobel_filter.shape [0] / / 2) self.SFV = nn.Conv2d (1,1, kernel_size=sobel_filter.shape, padding=sobel_filter.shape [0] / / 2) init_parameter (self.SFH, sobel_filter, np.array ([0.0])) init_parameter (self.SFV, sobel_filter.T Np.array ([0.0]) def forward (self, img): img_r = img [:, 0:1] img_g = img [:, 1:2] img_b = img [: 2:3] # # SFH (V): Sobel filtering of sobel filter of horizontal (vertical) horizontal (vertical) grad_r_x = self.SFH (img_r) # x direction gradient of channel R grad_r_y = self.SFV (img_r) grad_g_x = self.SFH (img_g) grad_g_y = self.SFV (img_g) Grad_b_x = self.SFH (img_b) grad_b_y = self.SFV (img_b) # calculation strength (magnitude) and direction (orientation) magnitude_r = torch.sqrt (grad_r_x**2 + grad_r_y**2) # Grr ^ 2 = GRX ^ 2 + Gry ^ 2 magnitude_g = torch.sqrt (grad_g_x**2 + grad_g_y* * 2) magnitude_b = torch.sqrt (grad_b_x**2 + grad_b_y**2) grad_magnitude = magnitude_r + magnitude_g + magnitude_b grad_y = grad_r_y + grad_g_y + grad_b_y grad_x = grad_r_x + grad_g_x + grad_b_x # tan θ = grad_y / grad_x convert to Angle (bearing) grad_orientation = (torch.atan2 (grad_y) Grad_x) * (180.0 / PAI) grad_orientation = torch.round (grad_orientation / 45.0) * 45.0 # converted to a multiple of 45 return grad_magnitude, grad_orientation
Output the gradient intensity as a picture, and get the rightmost picture on the bottom right. We can see that the gradient value of the edge area of the coin is larger (the larger the brighter).
For the complete code, please see: sobel_filter
(3) non-maximization inhibition
The process of non-maximization inhibition (NMS) is:
Each point of the gradient intensity matrix grad_magnitude is taken as a central pixel and compared with the gradient intensity of two adjacent points (8 in total) in the same direction or in the opposite direction.
If the gradient of the center point is less than the gradient in these two directions, the gradient value of the center of the point is set to 0.
After the above two steps, you can replace the gradient roof effect with a pixel width while retaining the gradient intensity of the roof ridge (the maximum gradient).
Class NonMaxSupression (nn.Module): def _ init__ (self)-> None: super (NonMaxSupression, self). _ _ init__ () all_orient_magnitude = np.stack ([filter_0, filter_45, filter_90, filter_135, filter_180, filter_225, filter_270 Filter_315])''directional_filter functions are detailed below' 'self.directional_filter = nn.Conv2d (1,8, kernel_size=filter_0.shape, padding=filter_0.shape [- 1] / / 2) init_parameter (self.directional_filter, all_filters [:, None,...], np.zeros (shape= (all_filters.shape [0],)) def forward (self) Grad_magnitude Grad_orientation): all_orient_magnitude = self.directional_filter (grad_magnitude) # the difference between the current point gradient and its neighborhood points in the other 8 directions (equivalent to the second-order gradient)''\ 3 | 2 /\ | / 4\ | / 1-| -5 / |\ 8 / |\ / 6 | 7\ Note: each area is 45 degrees' positive_orient = (grad_orientation / 45)% 8 # sets the type of positive direction There are eight different directions negative_orient = (grad_orientation / 45) + 4)% 8 # + 4 = 4 * 45 = 180, that is, rotate 180 degrees (such as 1-(+ 4)-> 5) height = positive_orient.size () [2] # to get the width and height of the picture width = positive_orient.size () [3] pixel_ Count = height * width # calculate the number of pixels in the image pixel_offset = torch.FloatTensor ([range (pixel_count)]) position = (positive_orient.view (- 1). Data * pixel_count + pixel_offset). Squeeze () # Angle * number of pixels + pixel position # get all the points in the image and its Gradient of the gradient of the positive neighborhood point (current point gradient-positive neighborhood point gradient Judge whether the current point is the largest in the neighborhood according to its value and the size of 0) channel_select_filtered_positive = all_orient_magnitude.view (- 1) [position.long ()] .view (1, height Width) position = (negative_orient.view (- 1). Data * pixel_count + pixel_offset). Squeeze () # gets the gradient of the gradient of all points in the image and their opposite neighborhood points channel_select_filtered_negative = all_orient_magnitude.view (- 1) [position.long ()] .view (1, height Width) # combined into two channels channel_select_filtered = torch.stack ([channel_select_filtered_positive, channel_select_filtered_negative]) is_max = channel_select_filtered.min (dim=0) [0] > 0.0 # if min {current gradient-forward point gradient, current gradient-reverse point gradient} > 0 Then the current maximum gradient is_max= torch.unsqueeze (is_max, dim=0) thin_edges = grad_magnitude.clone () thin_ edges [is _ max==0] = 0.0 return thin_edges
What is the use of directional_filter?
# enter tensor ([1, 1, 1.], [1, 1.], [1, 1.], [1, 1.]) # output tensor ([0, 0, 1.], [0, 0, 1.], [0, 0, 1.]], [[0, 0, 1.] [0., 0., 1.], [1., 1., 1.]], [[0., 0., 0.], [0., 0., 0.], [1., 1., 1.]], [[1., 0., 0.], [1., 0., 0.], [1. 1., 1.]], [[1., 0., 0.], [1., 0., 0.], [1., 0., 0.]], [[1., 1., 1.], [1., 0., 0.], [1., 0., 0.]], [[1., 1., 1.] [0, 0, 0.], [0, 0, 0.], [[1, 1, 1.], [0, 0, 1.], [0, 0, 1.]], grad_fn=)
You can see that it gets the gradient values of the eight directions entered (in the code of the current project, to obtain the difference between the current point gradient and the other eight direction gradients)
According to the intensity and direction of the gradient, the direction is divided into eight categories (that is, there are eight possible directions for each point), as shown in the "meter" figure in the code above.
The following is the process of calculating the gradient intensity of the adjacent points of the forward neighborhood of the current point (reverse homology)
Gradient direction grad_orientation: 0, 1, 2, 3, 4, 5, 6, 7 (a total of 8 directions)
Gradient intensity all_orient_magnitude in all directions: [. The gradient of direction 0..], [.. The gradient of direction 1..], [. Gradient of direction 7..]
Therefore, for a point with direction I, its position in the gradient strength is all_orient_ Magnitude [I] [x, y]. After changing all_orient_magnitude into an one-dimensional vector, the corresponding position is position = current_orient × pixel_count + pixel_offset. According to this position information, we can get the difference between the gradient intensity of the current point and its forward neighborhood point (the same can also be obtained in the opposite direction).
The following is an auxiliary illustration:
The final effect is as shown in the figure on the right (unmaximized suppression on the left)
For the complete code, please see: nonmax_supression
(4) lag edge tracking
After thinking about it, we find that so far there are still the following problems:
If there is noise in the image, edge-independent points (pseudo edges) may appear.
Edge points are sometimes overcast and sometimes bright.
So in the end, we need to do lag edge tracking, and the steps are as follows:
Two thresholds (one high and one low) are set, and the gradient intensity of pixels whose gradient intensity is less than the low threshold is set to 0 to get image A
When the gradient intensity of the pixel whose gradient intensity is less than the high threshold is set to 0, the image B is obtained.
We know that because the threshold of An is lower, the edge retention is more complete and the continuity is better, but there may be more pseudo edges, and B is just the opposite of A.
Based on this, we assume that based on B and An as a supplement, the missing pixels in B are completed by recursive tracking.
To_bw = lambda image: (image > 0.0) .astype (float) class HysteresisThresholding (nn.Module): def _ init__ (self, low_threshold=1.0, high_threshold=3.0)-> None: super (HysteresisThresholding, self). _ _ init__ () self.low_threshold = low_threshold self.high_threshold = high_threshold def thresholding (self, low_thresh: torch.Tensor High_thresh: torch.Tensor): died = torch.zeros_like (low_thresh). Squeeze () low_thresh = low_thresh.squeeze () final_image = high_thresh.squeeze (). Clone () height = final_image.shape [0]-1 width = final_image.shape [1]-1 def connected (x, y Gap = 1): right = x + gap bottom = y + gap left = x-gap top = y-gap if left
< 0 or top < 0 or right >= width or bottom > = height: return False return final_image [top, left] > 0 or final_image [top, x] > 0 or final_image [top, right] > 0\ or final_image [y, left] > 0 or final_image [y, right] > 0\ or final_image [bottom, left] > 0 or final_image [bottom, x] > 0 or final_ image [bottom] Right] > 0 # def trace (x:int, y:int): right = x + 1 bottom = y + 1 left = x-1 top = y-1 if left
< 0 or top < 0 or right >= width or bottom > = height or died [y, x] or final_image [y, x] > 0: return pass_high = final_image [y, x] > 0.0 pass_low = low_thresh [y, x] > 0.0 died [y, x] = True if pass_high: died [y X] = False elif pass_low and not pass_high: if connected (x, y) or connected (x, y, 2): # if there is a connection in other directions final_image [y, x] = low_thresh [y, x] died [y, x] = False # go back to if final_ image [y X] > 0.0: # if low_thresh [top, left] > 0: trace (left, top) if low_thresh [top, x] > 0: trace (x, top) if low_thresh [top, right] > 0: trace (right, top) if low_thresh [y, left] > 0: trace (left) Y) if low_thresh [bottom, left] > 0: trace (left, bottom) # Down trace (right, y) trace (x, bottom) trace (right, bottom) for i in range (width): for j in range (height): trace (I J) final_image = final_image.unsqueeze (dim=0) .unsqueeze (dim=0) return final_image def forward (self, thin_edges, grad_magnitude, grad_orientation): low_thresholded: torch.Tensor = thin_edges.clone () low_ assigned [thin _ edges]
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 292
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.