Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions digital_image_processing/edge_detection/canny.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Implementation of canny edge-detection algorithm
"""
import cv2
import numpy as np
from digital_image_processing.filters.convolve import img_convolve
Expand Down
Empty file.
115 changes: 115 additions & 0 deletions digital_image_processing/feature/hog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
Implementation hog(Histogram of Oriented Gradients) feature of image according
the paper[https://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf].
"""

import math
import cv2
import numpy as np
from digital_image_processing.filters.convolve import img_convolve


def hog_feature(image, gamma=0.4, cell_size=6, bin_size=18, block_size=4):
rows, cols = image.shape[0], image.shape[1]

"""
Gamma normalization, however, the author point out this step can be omitted in HOG descriptor computation.
"""
norm = (image + 0.5)/255
norm = np.power(norm, 1/gamma)
img_norm = 255 * norm - 0.5

# Get gradient and angle, the kernel below perform better than others(3x3 Sobel).
kernel_x = np.array([[-1, 0, 1]])
kernel_y = np.array([[-1], [0], [1]])

dst_x = img_convolve(img_norm, kernel_x)
dst_y = img_convolve(img_norm, kernel_y)

dst_xy = np.sqrt((np.square(dst_x)) + (np.square(dst_y)))
dst_xy = dst_xy * 255 / np.max(dst_xy)
gradient_magnitude_global = dst_xy

# Get the angles and convert them in range (0, 360)
theta = np.arctan2(dst_y, dst_x)
gradient_angle_global = np.rad2deg(theta) # range(-180, 180)
gradient_angle_global = np.where(gradient_angle_global < 0,
gradient_angle_global + 360,
gradient_angle_global) # range(0, 360)

"""
Orientation binning, The second step of calculation is creating the cell histograms. Each pixel within the cell
casts a weighted vote for an orientation-based histogram channel based on the values found in the gradient
computation. In tests, the gradient magnitude itself generally produces the best results.
"""
angle_unit = 360 / bin_size
cell_gradient_mtx = np.zeros((rows // cell_size, cols // cell_size, bin_size))
for i in range(cell_gradient_mtx.shape[0]):
for j in range(cell_gradient_mtx.shape[1]):
pixes_grad_per_cell = gradient_magnitude_global[i * cell_size:(i + 1) * cell_size,
j * cell_size:(j + 1) * cell_size]
pixes_angle_per_cell = gradient_angle_global[i * cell_size:(i + 1) * cell_size,
j * cell_size:(j + 1) * cell_size]

orientation_centers = [0] * bin_size
for cell_i in range(pixes_grad_per_cell.shape[0]):
for cell_j in range(pixes_grad_per_cell.shape[1]):
gradient_strength = pixes_grad_per_cell[cell_i][cell_j]
gradient_angle = pixes_angle_per_cell[cell_i][cell_j]

bin_index = int(gradient_angle / angle_unit)
if gradient_angle == 360:
bin_index = 0
orientation_centers[bin_index] += gradient_strength

cell_gradient_mtx[i][j] = orientation_centers

"""
Descriptor blocks. Grouping cells into larger spatial blocks and contrast normalizing each
block separately. The final descriptor is then the vector of all components of the normalized cell
responses from all of the blocks in the detection window.
"""
hog_descriptor_mtx = []
out_rows, out_cols = cell_gradient_mtx.shape[1] - block_size + 1, cell_gradient_mtx.shape[0] - block_size + 1
for i in range(0, out_rows):
for j in range(0, out_cols):
block_vector = np.ravel(cell_gradient_mtx[i:i + block_size, j:j + block_size, :])

# Block L2 normalization
eps = 1e-5
block_vector = block_vector / np.sqrt(np.sum(block_vector ** 2) + eps ** 2)
hog_descriptor_mtx.append(block_vector)

# showing hog
hog_dst_image = np.zeros([rows, cols])
cell_gradient = cell_gradient_mtx
cell_width = cell_size // 2
max_mag = np.array(cell_gradient).max()
for x in range(cell_gradient.shape[0]):
for y in range(cell_gradient.shape[1]):
cell_grad = cell_gradient[x][y]
cell_grad /= max_mag
angle = 0
angle_gap = angle_unit
for magnitude in cell_grad:
angle_radian = math.radians(angle)
x1 = int(x * cell_size + magnitude * cell_width * math.cos(angle_radian))
y1 = int(y * cell_size + magnitude * cell_width * math.sin(angle_radian))
x2 = int(x * cell_size - magnitude * cell_width * math.cos(angle_radian))
y2 = int(y * cell_size - magnitude * cell_width * math.sin(angle_radian))

strength = int(255 * math.sqrt(magnitude))
cv2.line(hog_dst_image, (y1, x1), (y2, x2), strength)

angle += angle_gap

return hog_descriptor_mtx, hog_dst_image


if __name__ == '__main__':
# read original image in gray model
img = cv2.imread('../image_data/lena.jpg', 0)
# extract hog feature
hog_dsp, hog_img = hog_feature(img)
cv2.imshow('hog', hog_img.astype(np.uint8))
cv2.waitKey(0)
109 changes: 87 additions & 22 deletions digital_image_processing/filters/convolve.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,104 @@
# @Author : lightXu
# @File : convolve.py
# @Time : 2019/7/8 0008 下午 16:13
"""
Implementation of image convolve algorithm
"""
from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
from numpy import array, zeros, ravel, pad, dot, uint8
import math


def im2col(image, block_size):
rows, cols = image.shape
dst_height = cols - block_size[1] + 1
dst_width = rows - block_size[0] + 1
image_array = zeros((dst_height * dst_width, block_size[1] * block_size[0]))
def im2col(image, block_size, row_stride, col_stride, dst_rows, dst_cols):
"""
:param image: padded image array
:param block_size: filter shape tuple
:param row_stride: stride in row channels
:param col_stride: stride in row channels
:param dst_rows: the rows of the filtered input image
:param dst_cols: the cols of the filtered input image
:return: the reshape array with shape(dst_rows*dst_cols, block_size[1] * block_size[0])
"""

image_array = zeros((dst_rows * dst_cols, block_size[1] * block_size[0]))
row = 0
for i in range(0, dst_height):
for j in range(0, dst_width):
window = ravel(image[i:i + block_size[0], j:j + block_size[1]])
for i in range(0, dst_rows):
for j in range(0, dst_cols):
window = ravel(image[i * row_stride:i * row_stride + block_size[0],
j * col_stride:j * col_stride + block_size[1]])
image_array[row, :] = window
row += 1

return image_array


def img_convolve(image, filter_kernel):
def img_convolve(image, kernel, row_stride=1, col_stride=1):
"""
:param image: input image array
:param kernel: filter kernel array
:param row_stride: stride in row channels
:param col_stride: stride in row channels
:return: the filter result

Example:
>>> image = array([[1,2,3,4,5],[2,3,4,5,6], [1,2,3,4,5]])
>>> kernel = array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
>>> img_convolve(image, kernel, 1, 1)
array([[3., 6., 6., 6., 3.],
[3., 6., 6., 6., 3.],
[3., 6., 6., 6., 3.]])

>>> img_convolve(image, kernel, 2, 1)
array([[3., 6., 6., 6., 3.],
[3., 6., 6., 6., 3.]])

>>> img_convolve(image, kernel, 2, 2)
array([[3., 6., 3.],
[3., 6., 3.]])

>>> kernel_1_3 = array([[-1, 2, -1]])
>>> img_convolve(image, kernel_1_3, 1, 1)
array([[-1., 0., 0., 0., 1.],
[-1., 0., 0., 0., 1.],
[-1., 0., 0., 0., 1.]])

>>> kernel_3_1 = array([[-2], [1], [-2]])
>>> img_convolve(image, kernel_3_1, 1, 1)
array([[ -5., -8., -11., -14., -17.],
[ -2., -5., -8., -11., -14.],
[ -5., -8., -11., -14., -17.]])

>>> kernel_3_1 = array([[-2], [1], [-2]])
>>> img_convolve(image, kernel_3_1, 2, 1)
array([[ -5., -8., -11., -14., -17.],
[ -5., -8., -11., -14., -17.]])


>>> kernel_3_1 = array([[-2], [1], [-2]])
>>> img_convolve(image, kernel_3_1, 2, 2)
array([[ -5., -11., -17.],
[ -5., -11., -17.]])
"""

height, width = image.shape[0], image.shape[1]
k_size = filter_kernel.shape[0]
pad_size = k_size//2
# Pads image with the edge values of array.
image_tmp = pad(image, pad_size, mode='edge')
k_size_row, k_size_col = kernel.shape[0], kernel.shape[1]

# im2col, turn the k_size*k_size pixels into a row and np.vstack all rows
image_array = im2col(image_tmp, (k_size, k_size))
# "SAME" convolve mode
dst_rows = math.ceil(height / row_stride) # ceil
dst_cols = math.ceil(width / col_stride) # ceil

pad_h = max((dst_rows - 1) * row_stride + k_size_row - height, 0)
pad_top = pad_h // 2 # floor
pad_bottom = pad_h - pad_top
pad_w = max((dst_cols - 1) * col_stride + k_size_col - width, 0)
pad_left = pad_w // 2 # floor
pad_right = pad_w - pad_left

# Pads image with the edge values of array.
image_tmp = pad(array=image,
pad_width=((pad_top, pad_bottom), (pad_left, pad_right)),
mode='edge')

# turn the kernel into shape(k*k, 1)
kernel_array = ravel(filter_kernel)
# reshape and get the dst image
dst = dot(image_array, kernel_array).reshape(height, width)
image_array = im2col(image_tmp, (k_size_row, k_size_col), row_stride, col_stride, dst_rows, dst_cols)
kernel_array = ravel(kernel)
dst = dot(image_array, kernel_array).reshape(dst_rows, dst_cols)
return dst


Expand Down
6 changes: 3 additions & 3 deletions digital_image_processing/filters/sobel_filter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @Author : lightXu
# @File : sobel_filter.py
# @Time : 2019/7/8 0008 下午 16:26
"""
Implementation of sobel filter algorithm
"""
import numpy as np
from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
from digital_image_processing.filters.convolve import img_convolve
Expand Down