scikit-image
356 строк · 13.9 Кб
1#cython: cdivision=True
2#cython: boundscheck=False
3#cython: nonecheck=False
4#cython: wraparound=False
5import numpy as np
6cimport numpy as cnp
7from libc.math cimport sin, cos
8from .._shared.interpolation cimport bilinear_interpolation, round
9from .._shared.transform cimport integrate
10
11cdef extern from "numpy/npy_math.h":
12cnp.float64_t NAN "NPY_NAN"
13
14from .._shared.fused_numerics cimport np_anyint as any_int
15
16cnp.import_array()
17
18def _glcm_loop(any_int[:, ::1] image, cnp.float64_t[:] distances,
19cnp.float64_t[:] angles, Py_ssize_t levels,
20cnp.uint32_t[:, :, :, ::1] out):
21"""Perform co-occurrence matrix accumulation.
22
23Parameters
24----------
25image : ndarray
26Integer typed input image. Only positive valued images are supported.
27If type is other than uint8, the argument `levels` needs to be set.
28distances : ndarray
29List of pixel pair distance offsets.
30angles : ndarray
31List of pixel pair angles in radians.
32levels : int
33The input image should contain integers in [0, `levels`-1],
34where levels indicate the number of gray-levels counted
35(typically 256 for an 8-bit image).
36out : ndarray
37On input a 4D array of zeros, and on output it contains
38the results of the GLCM computation.
39
40"""
41
42cdef:
43Py_ssize_t a_idx, d_idx, r, c, rows, cols, row, col, start_row,\
44end_row, start_col, end_col, offset_row, offset_col
45any_int i, j
46cnp.float64_t angle, distance
47
48with nogil:
49rows = image.shape[0]
50cols = image.shape[1]
51
52for a_idx in range(angles.shape[0]):
53angle = angles[a_idx]
54for d_idx in range(distances.shape[0]):
55distance = distances[d_idx]
56offset_row = round(sin(angle) * distance)
57offset_col = round(cos(angle) * distance)
58start_row = max(0, -offset_row)
59end_row = min(rows, rows - offset_row)
60start_col = max(0, -offset_col)
61end_col = min(cols, cols - offset_col)
62for r in range(start_row, end_row):
63for c in range(start_col, end_col):
64i = image[r, c]
65# compute the location of the offset pixel
66row = r + offset_row
67col = c + offset_col
68j = image[row, col]
69if 0 <= i < levels and 0 <= j < levels:
70out[i, j, d_idx, a_idx] += 1
71
72
73cdef inline int _bit_rotate_right(int value, int length) noexcept nogil:
74"""Cyclic bit shift to the right.
75
76Parameters
77----------
78value : int
79integer value to shift
80length : int
81number of bits of integer
82
83"""
84return (value >> 1) | ((value & 1) << (length - 1))
85
86
87def _local_binary_pattern(cnp.float64_t[:, ::1] image,
88int P, cnp.float64_t R, char method=b'D'):
89"""Gray scale and rotation invariant LBP (Local Binary Patterns).
90
91LBP is an invariant descriptor that can be used for texture classification.
92
93Parameters
94----------
95image : (N, M) cnp.float64_t array
96Graylevel image.
97P : int
98Number of circularly symmetric neighbor set points (quantization of
99the angular space).
100R : float
101Radius of circle (spatial resolution of the operator).
102method : {'D', 'R', 'U', 'N', 'V'}
103Method to determine the pattern.
104
105* 'D': 'default'
106* 'R': 'ror'
107* 'U': 'uniform'
108* 'N': 'nri_uniform'
109* 'V': 'var'
110
111Returns
112-------
113output : (N, M) array
114LBP image.
115"""
116
117# texture weights
118cdef int[::1] weights = 2 ** np.arange(P, dtype=np.int32)
119# local position of texture elements
120rr = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.float64) / P)
121cc = R * np.cos(2 * np.pi * np.arange(P, dtype=np.float64) / P)
122cdef cnp.float64_t[::1] rp = np.round(rr, 5)
123cdef cnp.float64_t[::1] cp = np.round(cc, 5)
124
125# pre-allocate arrays for computation
126cdef cnp.float64_t[::1] texture = np.zeros(P, dtype=np.float64)
127cdef signed char[::1] signed_texture = np.zeros(P, dtype=np.int8)
128cdef int[::1] rotation_chain = np.zeros(P, dtype=np.int32)
129
130output_shape = (image.shape[0], image.shape[1])
131cdef cnp.float64_t[:, ::1] output = np.zeros(output_shape, dtype=np.float64)
132
133cdef Py_ssize_t rows = image.shape[0]
134cdef Py_ssize_t cols = image.shape[1]
135
136cdef cnp.float64_t lbp
137cdef Py_ssize_t r, c, changes, i
138cdef Py_ssize_t rot_index, n_ones
139cdef cnp.int8_t first_zero, first_one
140
141# To compute the variance features
142cdef cnp.float64_t sum_, var_, texture_i
143
144with nogil:
145for r in range(image.shape[0]):
146for c in range(image.shape[1]):
147for i in range(P):
148bilinear_interpolation[cnp.float64_t, cnp.float64_t, cnp.float64_t](
149&image[0, 0], rows, cols, r + rp[i], c + cp[i],
150b'C', 0, &texture[i])
151# signed / thresholded texture
152for i in range(P):
153if texture[i] - image[r, c] >= 0:
154signed_texture[i] = 1
155else:
156signed_texture[i] = 0
157
158lbp = 0
159
160# if method == b'var':
161if method == b'V':
162# Compute the variance without passing from numpy.
163# Following the LBP paper, we're taking a biased estimate
164# of the variance (ddof=0)
165sum_ = 0.0
166var_ = 0.0
167for i in range(P):
168texture_i = texture[i]
169sum_ += texture_i
170var_ += texture_i * texture_i
171var_ = (var_ - (sum_ * sum_) / P) / P
172if var_ != 0:
173lbp = var_
174else:
175lbp = NAN
176# if method == b'uniform':
177elif method == b'U' or method == b'N':
178# determine number of 0 - 1 changes
179changes = 0
180for i in range(P - 1):
181changes += (signed_texture[i]
182- signed_texture[i + 1]) != 0
183if method == b'N':
184# Uniform local binary patterns are defined as patterns
185# with at most 2 value changes (from 0 to 1 or from 1 to
186# 0). Uniform patterns can be characterized by their
187# number `n_ones` of 1. The possible values for
188# `n_ones` range from 0 to P.
189#
190# Here is an example for P = 4:
191# n_ones=0: 0000
192# n_ones=1: 0001, 1000, 0100, 0010
193# n_ones=2: 0011, 1001, 1100, 0110
194# n_ones=3: 0111, 1011, 1101, 1110
195# n_ones=4: 1111
196#
197# For a pattern of size P there are 2 constant patterns
198# corresponding to n_ones=0 and n_ones=P. For each other
199# value of `n_ones` , i.e n_ones=[1..P-1], there are P
200# possible patterns which are related to each other
201# through circular permutations. The total number of
202# uniform patterns is thus (2 + P * (P - 1)).
203
204# Given any pattern (uniform or not) we must be able to
205# associate a unique code:
206#
207# 1. Constant patterns patterns (with n_ones=0 and
208# n_ones=P) and non uniform patterns are given fixed
209# code values.
210#
211# 2. Other uniform patterns are indexed considering the
212# value of n_ones, and an index called 'rot_index'
213# representing the number of circular right shifts
214# required to obtain the pattern starting from a
215# reference position (corresponding to all zeros stacked
216# on the right). This number of rotations (or circular
217# right shifts) 'rot_index' is efficiently computed by
218# considering the positions of the first 1 and the first
219# 0 found in the pattern.
220
221if changes <= 2:
222# We have a uniform pattern
223n_ones = 0 # determines the number of ones
224first_one = -1 # position was the first one
225first_zero = -1 # position of the first zero
226for i in range(P):
227if signed_texture[i]:
228n_ones += 1
229if first_one == -1:
230first_one = i
231else:
232if first_zero == -1:
233first_zero = i
234if n_ones == 0:
235lbp = 0
236elif n_ones == P:
237lbp = P * (P - 1) + 1
238else:
239if first_one == 0:
240rot_index = n_ones - first_zero
241else:
242rot_index = P - first_one
243lbp = 1 + (n_ones - 1) * P + rot_index
244else: # changes > 2
245lbp = P * (P - 1) + 2
246else: # method != 'N'
247if changes <= 2:
248for i in range(P):
249lbp += signed_texture[i]
250else:
251lbp = P + 1
252else:
253# method == b'default'
254for i in range(P):
255lbp += signed_texture[i] * weights[i]
256
257# method == b'ror'
258if method == b'R':
259# shift LBP P times to the right and get minimum value
260rotation_chain[0] = <int>lbp
261for i in range(1, P):
262rotation_chain[i] = \
263_bit_rotate_right(rotation_chain[i - 1], P)
264lbp = rotation_chain[0]
265for i in range(1, P):
266lbp = min(lbp, rotation_chain[i])
267
268output[r, c] = lbp
269
270return np.asarray(output)
271
272
273# Constant values that are used by `_multiblock_lbp` function.
274# Values represent offsets of neighbor rectangles relative to central one.
275# It has order starting from top left and going clockwise.
276cdef:
277Py_ssize_t[::1] mlbp_r_offsets = np.asarray([-1, -1, -1, 0, 1, 1, 1, 0],
278dtype=np.intp)
279Py_ssize_t[::1] mlbp_c_offsets = np.asarray([-1, 0, 1, 1, 1, 0, -1, -1],
280dtype=np.intp)
281
282
283cpdef int _multiblock_lbp(np_floats[:, ::1] int_image,
284Py_ssize_t r,
285Py_ssize_t c,
286Py_ssize_t width,
287Py_ssize_t height) noexcept nogil:
288"""Multi-block local binary pattern (MB-LBP) [1]_.
289
290Parameters
291----------
292int_image : (N, M) float array
293Integral image.
294r : int
295Row-coordinate of top left corner of a rectangle containing feature.
296c : int
297Column-coordinate of top left corner of a rectangle containing feature.
298width : int
299Width of one of 9 equal rectangles that will be used to compute
300a feature.
301height : int
302Height of one of 9 equal rectangles that will be used to compute
303a feature.
304
305Returns
306-------
307output : int
3088-bit MB-LBP feature descriptor.
309
310References
311----------
312.. [1] L. Zhang, R. Chu, S. Xiang, S. Liao, S.Z. Li. "Face Detection Based
313on Multi-Block LBP Representation", In Proceedings: Advances in
314Biometrics, International Conference, ICB 2007, Seoul, Korea.
315http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
316:DOI:`10.1007/978-3-540-74549-5_2`
317"""
318
319cdef:
320# Top-left coordinates of central rectangle.
321Py_ssize_t central_rect_r = r + height
322Py_ssize_t central_rect_c = c + width
323
324Py_ssize_t r_shift = height - 1
325Py_ssize_t c_shift = width - 1
326
327Py_ssize_t current_rect_r, current_rect_c
328Py_ssize_t element_num
329np_floats current_rect_val
330int has_greater_value
331int lbp_code = 0
332
333# Sum of intensity values of central rectangle.
334cdef np_floats central_rect_val = integrate(int_image, central_rect_r,
335central_rect_c,
336central_rect_r + r_shift,
337central_rect_c + c_shift)
338
339for element_num in range(8):
340
341current_rect_r = central_rect_r + mlbp_r_offsets[element_num]*height
342current_rect_c = central_rect_c + mlbp_c_offsets[element_num]*width
343
344
345current_rect_val = integrate(int_image, current_rect_r, current_rect_c,
346current_rect_r + r_shift,
347current_rect_c + c_shift)
348
349
350has_greater_value = current_rect_val >= central_rect_val
351
352# If current rectangle's intensity value is bigger
353# make corresponding bit to 1.
354lbp_code |= has_greater_value << (7 - element_num)
355
356return lbp_code
357