Das Papier "Region Covariance: Ein schneller Deskriptor für die Erkennung und Klassifizierung [^ 1]", das Probleme im Bereich der Computersicht wie Objekterkennung und Texturklassifizierung unter Verwendung einer Verteilungskovarianzmatrix als Merkmalsgröße behandelt. Zusammenfassung und einfache Implementierung in Python zur Objekterkennung.
Introduction
Die Auswahl von Merkmalen ist bei Erkennungs- und Klassifizierungsproblemen sehr wichtig. Tatsächlich werden RGB-Werte, Helligkeitswerte und ihre Farbverläufe häufig im Bereich der Bildverarbeitung verwendet. Diese Merkmale sind jedoch nicht robust gegenüber den Lichtverhältnissen und können je nach Bildgröße zu hochdimensionalen Merkmalen werden. Es gibt Probleme wie. Es gibt auch eine Methode, die ein Bildhistogramm verwendet. Wenn jedoch die Anzahl der Bins im Histogramm $ b $ und die Anzahl der Features $ d $ beträgt, beträgt die Dimension des Histogramms $ b ^ d $ und die Anzahl der Features Die Dimension nimmt exponentiell zu. Das Folgende ist ein Beispiel für $ b = 4, d = 3 $ mit RGB-Werten als Features. → →
Daher schlagen wir in diesem Artikel vor, die Verteilungs-Co-Verteilungsmatrix als Regionsdeskriptor des Bildes zu verwenden. Für die Anzahl der Merkmale $ d $ beträgt die Dimension der Varianz-Co-Dispersionsmatrix $ d \ mal d $, was den Vorteil hat, dass sie kleiner ist als wenn die Merkmale so verwendet werden, wie sie sind, oder wenn ein Histogramm verwendet wird. Darüber hinaus wird in diesem Artikel ein Suchalgorithmus für den nächsten Nachbarn vorgeschlagen, der eine verteilte, gemeinsam verteilte Matrix verwendet.
Covariance as a Region Descriptor
Sei $ I $ ein Helligkeitswertbild von $ W \ mal H $ oder ein RGB-Bild von $ W \ mal H \ mal 3 $. $ W \ times H \ times d $ dimensionales Merkmalsbild $ F $, das aus diesem Bild erhalten wurde
F(x, y) = \phi(I, x, y)
Lassen. Hier ist die Funktion $ \ phi $ eine Abbildung, die auf dem Helligkeitswert, dem RGB-Wert und der Filterantwort wie dem Gradienten basiert. Der $ d $ dimensionale Merkmalsvektor in der rechteckigen Region $ R \ Teilmenge F $ sei $ z_1, \ dots, z_n $, und die verteilte mitverteilte Matrix $ C_R $, die die Region $ R $ darstellt, sei.
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
Berechnen mit.
Wie oben erwähnt, ist die Dimension der verteilten, gemeinsam verteilten Matrix $ d \ mal d $, und da es sich um eine symmetrische Matrix handelt, hat sie $ \ frac12d (d + 1) $ unterschiedliche Werte. Dies ist niedriger als wenn die Merkmalsmenge unverändert verwendet wird ($ n \ times d $ dimension) oder wenn das Histogramm verwendet wird ($ b ^ d $ dimension).
Da $ C_R $ keine Informationen über die Reihenfolge und Anzahl der Merkmalspunkte enthält, verfügt es über ein Attribut, das abhängig von den Merkmalsmengen, aus denen die Matrix besteht, für Rotation und Skalierung unveränderlich ist. (Beispielsweise geht die Rotationsinvarianz verloren, wenn Gradienteninformationen in der Richtung $ x, y $ verwendet werden.)
Distance Calculation on Covariance Matrices
Die verteilte co-verteilte Matrix ist eine Menge positiver symmetrischer Matrizen mit konstantem Wert, nicht des euklidischen Raums.
\mathcal{P}(d) := \left\{X\in\mathbb{R}^{d\times d}\mid X=X^\top,X\succ O\right\}
Es gehört. Dies kann durch die Tatsache bestätigt werden, dass $ -C_R $ keine verteilte gemeinsam verteilte Matrix für die verteilte gemeinsam verteilte Matrix $ C_R $ sein kann. Wenn Sie also eine Suche nach dem nächsten Nachbarn unter Verwendung einer verteilten, gemeinsam verteilten Matrix durchführen, befindet sich die Entfernung nicht in der euklidischen Entfernung, sondern in $ \ mathcal {P} (d) $ (bezogen auf die Lee-Gruppe oder den Lehman-Hybrid).
\rho(C_1, C_2) = \sqrt{\sum_{i=1}^d \ln^2\lambda_i(C_1, C_2)} \tag{1}
Wird genutzt. Hier ist $ \ lambda_i (C_1, C_2) $ der verallgemeinerte Eigenwert von $ C_1, C_2 $ für den verallgemeinerten Eigenvektor $ x_i \ neq0 $.
\lambda_i C_1 x_i - C_2 x_i = 0, \quad i=1, \dots, d
Treffen.
Integral Images for Fast Covariance Computation
Ein integrales Bild wird verwendet, um die Varianz-Co-Dispersionsmatrix mit hoher Geschwindigkeit zu berechnen. Was ist ein integriertes Bild?
\text{Integral Image }(x', y') = \sum_{x<x' ,y<y'}I(x, y)
Dies ist die Summe der Pixelwerte, die oben links in dem in definierten interessierenden Pixel vorhanden sind. →
Die $ (i, j) $ -Komponente der Varianz-co-verteilten Matrix ist
\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}
Es wird berechnet von und es ist ersichtlich, dass die Summe der ersten und zweiten Ordnung von $ z_k $ erforderlich ist. Daher ist $ W \ mal H \ mal d $ dimensionales integriertes Bild $ P $ und $ W \ mal H \ mal d \ mal d $ dimensionales integriertes Bild $ Q $.
\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}
Definiert in. Außerdem werden die Werte des integrierten Bildes an einem Punkt $ (x, y) $ jeweils festgelegt.
\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}
Lassen.
Sei $ R (x ', y'; x '', y '') $ der rechteckige Bereich von $ (x ', y') $ oben links und $ (x '', y '') $ unten rechts. Dann ist die verteilte mitverteilte Matrix in diesem rechteckigen Bereich $ 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}
Kann mit berechnet werden.
Object Detection
In diesem Artikel sind die Bildmerkmale $ x, y $ Koordinaten von Pixeln, RGB-Werte von Bildern $ R (x, y), G (x, y), B (x, y) $ und Helligkeitswerte von Bildern $ I. Die Objekterkennung wird unter Verwendung von neun Absolutwerten der Gradienten erster und zweiter Ordnung von (x, y) $ durchgeführt. Die Gradienten erster und zweiter Ordnung werden mit den Filtern $ [-1, 0, 1], [-1, 2, -1] $ berechnet.
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}
In diesem Artikel werden fünf verteilte, gemeinsam verteilte Matrizen erstellt, um Objekte darzustellen, und fünf verteilte, gemeinsam verteilte Matrizen werden erneut für 1000 Fenster mit einem kleinen Abstand zwischen den verteilten, gemeinsam verteilten Matrizen berechnet. Daher wurde ein numerisches Experiment mit nur einem durchgeführt.
Der im linken Bild von Rosa umgebene Teil wurde als Eingabebild verwendet, und die Objekterkennung wurde an den beiden rechten Bildern mit unterschiedlichen Auflösungen durchgeführt.
Der Quellcode lautet wie folgt und das ausgeführte Notizbuch ist Github.
calc_distance
wird der Abstand in Gleichung (1) berechnet. Der verallgemeinerte Eigenwert verwendet die Eigenwertberechnungsfunktion für die Elmeet-Matrix mit dem Namen scipy.linalg.eigh
. tat.cv2.filter2D
durchgeführt._calc_integral_images
, um die integrierten Bilder der Gleichungen (2) und (3) zu berechnen. Das integrierte Bild wird von [cv2.integral
] berechnet (http://opencv.jp/opencv-2svn/cpp/miscellaneous_image_transformations.html#cv-integral)._calc_covariance
wird zur Berechnung von Gleichung (4) verwendet.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
Das Ausführungsergebnis und die Berechnungszeit betragen 6,22 s bzw. 19,7 s. Sie können es etwas schneller machen, indem Sie die Werte von "n_windows" und "step" ändern.
Recommended Posts