L'article "Region Covariance: A fast descriptor for detection and classification [^ 1]" qui aborde les problèmes dans le domaine de la vision par ordinateur tels que la détection d'objets et la classification de texture en utilisant une matrice de covariance de distribution comme quantité de caractéristiques. Résumé et implémentation simple en Python pour la détection d'objets.
Introduction
La sélection des fonctionnalités est très importante dans les problèmes de détection et de classification. En fait, les valeurs RVB, les valeurs de luminosité et leurs gradients sont souvent utilisés dans le domaine de la vision par ordinateur, mais ces caractéristiques ne sont pas robustes aux conditions d'éclairage et peuvent devenir des caractéristiques de grande dimension en fonction de la taille de l'image. Il y a des problèmes tels que. Il existe également une méthode qui utilise un histogramme d'image, mais si le nombre de cases dans l'histogramme est $ b $ et le nombre d'entités est $ d $, la dimension de l'histogramme est $ b ^ d $, et le nombre d'entités La dimension augmente de façon exponentielle. Voici un exemple de $ b = 4, d = 3 $ avec des valeurs RVB comme caractéristiques. → →
Par conséquent, dans cet article, nous proposons d'utiliser la matrice de co-distribution de distribution comme descripteur de région de l'image. Pour le nombre d'entités $ d $, la dimension de la matrice de variance-co-dispersion est $ d \ fois d $, ce qui a l'avantage d'être plus petite que lorsque les entités sont utilisées telles quelles ou qu'un histogramme est utilisé. De plus, cet article propose un algorithme de recherche du plus proche voisin qui utilise une matrice distribuée co-distribuée.
Covariance as a Region Descriptor
Soit $ I $ une image de valeur de luminosité de $ W \ fois H $ ou une image RVB de $ W \ fois H \ fois 3 $. $ W \ fois H \ fois d $ image caractéristique dimensionnelle $ F $ obtenue à partir de cette image
F(x, y) = \phi(I, x, y)
Laisser. Ici, la fonction $ \ phi $ est un mappage basé sur la valeur de luminosité, la valeur RVB et la réponse du filtre telle que le dégradé. Soit le vecteur de caractéristiques dimensionnelles $ d $ dans l'aire rectangulaire $ R \ sous-ensemble F $ $ z_1, \ dots, z_n $, et la matrice distribuée co-distribuée $ C_R $ représente l'aire $ R $.
C_R = \frac{1}{n-1}\sum_{k=1}^n(z_k - \mu)(z_k - \mu)^\top,\quad \mu=\frac1n\sum_{k=1}^n z_k
Calculez avec.
Comme mentionné ci-dessus, la dimension de la matrice distribuée co-distribuée est $ d \ fois d $, et comme il s'agit d'une matrice symétrique, elle a $ \ frac12d (d + 1) $ valeurs différentes. C'est moins que lorsque la quantité d'entités est utilisée telle quelle ($ n \ fois d $ dimension) ou lorsque l'histogramme est utilisé ($ b ^ d $ dimension).
De plus, étant donné que $ C_R $ ne dispose pas d'informations sur l'ordre et le nombre de points caractéristiques, il a un attribut qui est invariant à la rotation et à l'échelle en fonction des quantités d'entités qui composent la matrice. (Par exemple, l'invariance de rotation est perdue lors de l'utilisation des informations de gradient dans la direction $ x, y $)
Distance Calculation on Covariance Matrices
La matrice distribuée co-distribuée est un ensemble de matrices symétriques à valeur constante positive, et non un espace euclidien.
\mathcal{P}(d) := \left\{X\in\mathbb{R}^{d\times d}\mid X=X^\top,X\succ O\right\}
Il appartient à. Ceci peut être confirmé par le fait que $ -C_R $ ne peut pas être une matrice distribuée co-distribuée pour la matrice distribuée co-distribuée $ C_R $. Par conséquent, lors d'une recherche du plus proche voisin à l'aide d'une matrice distribuée co-distribuée, la distance n'est pas sur la distance euclidienne mais sur $ \ mathcal {P} (d) $ (lié au groupe de Lee ou hybride de Lehman).
\rho(C_1, C_2) = \sqrt{\sum_{i=1}^d \ln^2\lambda_i(C_1, C_2)} \tag{1}
Est utilisé. Ici, $ \ lambda_i (C_1, C_2) $ est la valeur propre généralisée de $ C_1, C_2 $, pour le vecteur propre généralisé $ x_i \ neq0 $.
\lambda_i C_1 x_i - C_2 x_i = 0, \quad i=1, \dots, d
Rencontrer.
Integral Images for Fast Covariance Computation
Une image intégrale est utilisée pour calculer la matrice de variance-co-dispersion à grande vitesse. Qu'est-ce qu'une image intégrée?
\text{Integral Image }(x', y') = \sum_{x<x' ,y<y'}I(x, y)
Il s'agit de la somme des valeurs de pixel existant dans le coin supérieur gauche du pixel d'intérêt défini dans. →
La composante $ (i, j) $ de la matrice de variance co-distribuée est
\begin{align}
C_R(i, j) &= \frac{1}{n-1}\sum_{k=1}^n (z_k(i) - \mu(i))(z_k(j) - \mu(j))\\
&= \frac{1}{n-1}\left[\sum_{k=1}^n z_k(i)z_k(j) - \mu(i)\sum_{k=1}^n z_k(j) - \mu(j)\sum_{k=1}^n z_k(i) + n\mu(i)\mu(j)\right]\\
&= \frac{1}{n-1}\left[\sum_{k=1}^n z_k(i)z_k(j) - \frac1n\sum_{k=1}^n z_k(i)\sum_{k=1}^n z_k(j)\right]\\
\end{align}
Il est calculé par, et on peut voir que la somme des premier et deuxième ordres de $ z_k $ est requise. Par conséquent, $ W \ fois H \ fois d $ image intégrée dimensionnelle $ P $ et $ W \ fois H \ fois d \ fois d $ image intégrée dimensionnelle $ Q $, respectivement.
\begin{align}
P(x',y',i) &= \sum_{x<x', y<y'}F(x, y, i), \quad i=1, \dots, d \tag{2}\\
Q(x',y',i, j) &= \sum_{x<x', y<y'}F(x, y, i)F(x, y, j), \quad i, j=1, \dots, d \tag{3}
\end{align}
Défini dans. De plus, les valeurs de l'image intégrée en un point $ (x, y) $ sont définies respectivement.
\begin{align}
p_{x, y} &= \left[P(x, y, 1), \dots, P(x, y, d)\right]^\top\\
Q_{x, y} &=
\begin{pmatrix}
Q(x, y, 1, 1) & \cdots & Q(x, y, 1, d)\\
\vdots & \ddots & \vdots\\
Q(x, y, d, 1) & \cdots & Q(x, y, d, d)
\end{pmatrix}
\end{align}
Laisser.
Soit la zone rectangulaire de $ (x ', y') $ en haut à gauche et $ (x '', y '') $ en bas à droite $ R (x ', y'; x '', y '') $. Ensuite, la matrice distribuée co-distribuée dans cette région rectangulaire est $ n = (x '' - x ') \ cdot (y' '- y') $.
\begin{align}
C_{R(x', y'; x'', y'')} = \frac{1}{n-1}\left[Q_{x'', y''} + Q_{x', y'} - Q_{x'', y'} - Q_{x', y''}\\
-\frac1n(p_{x'', y''} + p_{x', y'} - p_{x'', y'} - p_{x', y''})(p_{x'', y''} + p_{x', y'} - p_{x'', y'} - p_{x', y''})^\top\right] \tag{4}
\end{align}
Peut être calculé avec.
Object Detection
Dans cet article, les quantités de caractéristiques de l'image sont les coordonnées $ x, y $ du pixel, la valeur RVB de l'image $ R (x, y), G (x, y), B (x, y) $ et la valeur de luminosité de l'image $ I. La détection d'objet est effectuée à l'aide de neuf valeurs absolues des gradients de premier et de second ordre de (x, y) $. Les gradients du premier et du second ordre sont calculés à l'aide des filtres $ [-1, 0, 1], [-1, 2, -1] $.
F(x, y) = \left[x, y, R(x, y), G(x, y), B(x, y), \left|\frac{\partial I(x, y)}{\partial x}\right|, \left|\frac{\partial I(x, y)}{\partial y}\right|, \left|\frac{\partial^2 I(x, y)}{\partial x^2}\right|, \left|\frac{\partial^2 I(x, y)}{\partial y^2}\right|\right]^\top \tag{5}
Dans l'article, cinq matrices co-distribuées distribuées sont créées pour représenter des objets, et cinq matrices co-distribuées distribuées sont calculées à nouveau pour 1000 fenêtres avec une petite distance entre les matrices co-distribuées distribuées. Par conséquent, une expérience numérique a été réalisée avec un seul.
La partie entourée de rose dans l'image de gauche a été utilisée comme image d'entrée, et la détection d'objet a été effectuée sur les deux images de droite avec des résolutions différentes.
Le code source est le suivant, et le bloc-notes exécuté est Github.
calc_distance
est utilisée pour calculer la distance dans l'équation (1). La valeur propre généralisée utilise la fonction de calcul des valeurs propres pour la matrice Elmeet appelée scipy.linalg.eigh
fait._extract_features
pour extraire les caractéristiques de l'équation (5). Le calcul du gradient est effectué à l'aide de cv2.filter2D
._calc_integral_images
pour calculer les images intégrées des équations (2) et (3). L'image intégrée est calculée par cv2.integral
._calc_covariance
est utilisée pour calculer l'équation (4).from typing import List, Tuple
from itertools import product
import cv2
import numpy as np
from scipy.linalg import eigh
from tqdm import tqdm
def calc_distance(x: np.ndarray, y: np.ndarray) -> float:
w = eigh(x, y, eigvals_only=True)
return np.linalg.norm(np.log(w) ** 2)
class RegionCovarianceDetector:
"""
Object Detector Using Region Covariance
Parameters
----------
coord : bool, optional (default=True)
Whether use coordinates as features or not.
color : bool, optional (default=True)
Whether use color channels as features or not.
intensity : bool, optional (default=False)
Whether use intensity as feature or not.
kernels : a list of np.ndarray, optional (default=None)
Filters applied to intensity image. If None, no filters are used.
ratio : float, optional (default=1.15)
Scaling factor between two consecutive scales of the search window size and step size.
step : int, optional (default=3)
The minimum step size.
n_windows : int, optional (default=9)
The number of scales of the windows.
eps : float, optional (default=1e-16)
Small number to keep covariance matrices in SPD.
Attributes
----------
object_shape_ : (int, int)
The object's shape.
object_covariance_ : np.ndarray, shape (n_features, n_features)
Covariance matrix of the object.
"""
def __init__(
self,
coord: bool = True,
color: bool = True,
intensity: bool = False,
kernels: List[np.ndarray] = None,
ratio: float = 1.15,
step: int = 3,
n_windows: int = 9,
eps: float = 1e-16
):
self.coord = coord
self.color = color
self.intensity = intensity
self.kernels = kernels
self.ratio = ratio
self.step = step
self.n_windows = n_windows
self.eps = eps
self.object_shape_ = None
self.object_covariance_ = None
def _extract_features(self, img: np.ndarray) -> List[np.ndarray]:
"""
Extract image features.
Parameters
----------
img : np.ndarray, shape (h, w, c)
uint8 RGB image
Returns
-------
features : a list of np.ndarray
Features such as intensity, its gradient and so on.
"""
h, w, c = img.shape[:3]
intensity = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:, :, 2] / 255.
features = list()
# use coordinates
if self.coord:
features.append(np.tile(np.arange(w, dtype=float), (h, 1)))
features.append(np.tile(np.arange(h, dtype=float).reshape(-1, 1), (1, w)))
# use color channels
if self.color:
for i in range(c):
features.append(img[:, :, i].astype(float) / 255.)
# use intensity
if self.intensity:
features.append(intensity)
# use filtered images
if self.kernels is not None:
for kernel in self.kernels:
features.append(np.abs(cv2.filter2D(intensity, cv2.CV_64F, kernel)))
return features
def _calc_integral_images(self, img: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
"""
Calculate integral images.
Parameters
----------
img : np.ndarray, shape (h, w, c)
uint8 RGB image
Returns
-------
P : np.ndarray, shape (h+1, w+1, n_features)
First order integral images of features.
Q : np.ndarray, shape (h+1, w+1, n_features, n_features)
Second order integral images of features.
"""
h, w = img.shape[:2]
features = self._extract_features(img)
length = len(features)
# first order integral images
P = cv2.integral(np.array(features).transpose((1, 2, 0)))
# second order integral images
Q = cv2.integral(
np.array(list(map(lambda x: x[0] * x[1], product(features, features)))).transpose((1, 2, 0))
)
Q = Q.reshape(h + 1, w + 1, length, length)
return P, Q
def _calc_covariance(self, P: np.ndarray, Q: np.ndarray, pt1: Tuple[int, int], pt2: Tuple[int, int]) -> np.ndarray:
"""
Calculate covariance matrix from integral images.
Parameters
----------
P : np.ndarray, shape (h+1, w+1, n_features)
First order integral images of features.
Q : np.ndarray, shape (h+1, w+1, n_features, n_features)
Second order integral images of features.
pt1 : (int, int)
Left top coordinate.
pt2 : (int, int)
Right bottom coordinate.
Returns
-------
covariance : np.ndarray, shape (n_features, n_features)
Covariance matrix.
"""
x1, y1 = pt1
x2, y2 = pt2
q = Q[y2, x2] + Q[y1, x1] - Q[y1, x2] - Q[y2, x1]
p = P[y2, x2] + P[y1, x1] - P[y1, x2] - P[y2, x1]
n = (y2 - y1) * (x2 - x1)
covariance = (q - np.outer(p, p) / n) / (n - 1) + self.eps * np.identity(P.shape[2])
return covariance
def fit(self, img: np.ndarray):
"""
Calculate the object covariance matrix.
Parameters
----------
img : np.ndarray, shape (h, w, c)
uint8 RGB image
Returns
-------
: Fitted detector.
"""
h, w = img.shape[:2]
P, Q = self._calc_integral_images(img)
# normalize about coordinates
if self.coord:
for i, size in enumerate((w, h)):
P[:, :, i] /= size
Q[:, :, i] /= size
Q[:, :, :, i] /= size
# calculate covariance matrix
self.object_covariance_ = self._calc_covariance(P, Q, (0, 0), (w, h))
self.object_shape_ = (h, w)
return self
def predict(self, img: np.ndarray) -> Tuple[Tuple[int, int], Tuple[int, int], float]:
"""
Compute object's position in the target image.
Parameters
----------
img : np.ndarray, shape (h, w, c)
uint8 RGB image
Returns
-------
pt1 : (int, int)
Left top coordinate.
pt2 : (int, int)
Right bottom coordinate.
score : float
Dissimilarity of object and target covariance matrices.
"""
tar_h, tar_w = img.shape[:2]
obj_h, obj_w = self.object_shape_
P, Q = self._calc_integral_images(img)
# search window's shape and step size
end = (self.n_windows + 1) // 2
start = end - self.n_windows
shapes = [(int(obj_h * self.ratio ** i), int(obj_w * self.ratio ** i)) for i in range(start, end)]
steps = [int(self.step * self.ratio ** i) for i in range(self.n_windows)]
distances = list()
for shape, step in tqdm(zip(shapes, steps)):
p_h, p_w = shape
p_P, p_Q = P.copy(), Q.copy()
# normalize about coordinates
if self.coord:
for i, size in enumerate((p_w, p_h)):
p_P[:, :, i] /= size
p_Q[:, :, i] /= size
p_Q[:, :, :, i] /= size
distance = list()
y1, y2 = 0, p_h
while y2 <= tar_h:
dist = list()
x1, x2 = 0, p_w
while x2 <= tar_w:
# calculate covariance matrix
p_cov = self._calc_covariance(p_P, p_Q, (x1, y1), (x2, y2))
# jump horizontally
x1 += step
x2 += step
# calculate dissimilarity of two covariance matrices
dist.append(calc_distance(self.object_covariance_, p_cov))
# jump vertically
y1 += step
y2 += step
distance.append(dist)
distances.append(np.array(distance))
# choose the most similar window
min_indices = list(map(np.argmin, distances))
min_index = int(np.argmin([dist.flatten()[i] for i, dist in zip(min_indices, distances)]))
min_step = steps[min_index]
min_shape = shapes[min_index]
min_indice = min_indices[min_index]
b_h, b_w = distances[min_index].shape
pt1 = ((min_indice % b_w) * min_step, (min_indice // b_w) * min_step)
pt2 = (pt1[0] + min_shape[1], pt1[1] + min_shape[0])
score = distances[min_index].flatten()[min_indice]
return pt1, pt2, score
Le résultat de l'exécution et le temps de calcul sont respectivement de 6,22 s et 19,7 s.
Vous pouvez le rendre un peu plus rapide en modifiant les valeurs de n_windows
et step
.
Recommended Posts