our_knp/cats-dogs/features.py
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

47 lines
2.4 KiB
Python

"""Fixed gradient histograms and optional coarse appearance; no learned extractor.
HOG-like: 9 unsigned soft orientation bins, 4x4-pixel cells,
2x2-cell overlapping blocks, L2-Hys normalization. Exactly 1764 values.
"""
import numpy as np
def descriptor(rgb, hybrid=False, sparse=False):
rgb = np.asarray(rgb).reshape(-1, 3, 32, 32).astype(np.float32) / 255
# Square-root compression reduces dependence on illumination.
image = np.sqrt(rgb)
gx, gy = np.zeros_like(image), np.zeros_like(image)
gx[:, :, :, 1:-1] = image[:, :, :, 2:] - image[:, :, :, :-2]
gy[:, :, 1:-1, :] = image[:, :, 2:, :] - image[:, :, :-2, :]
magnitude = np.hypot(gx, gy)
channel = magnitude.argmax(axis=1)[:, None]
mag = np.take_along_axis(magnitude, channel, axis=1)[:, 0]
dx = np.take_along_axis(gx, channel, axis=1)[:, 0]
dy = np.take_along_axis(gy, channel, axis=1)[:, 0]
angle = (np.arctan2(dy, dx) % np.pi) * (9 / np.pi)
lower = np.floor(angle).astype(int) % 9
fraction = angle - np.floor(angle)
hist = np.empty((len(rgb), 8, 8, 9), dtype=np.float32)
for b in range(9):
weighted = mag * ((lower == b) * (1 - fraction) + ((lower + 1) % 9 == b) * fraction)
hist[:, :, :, b] = weighted.reshape(-1, 8, 4, 8, 4).sum(axis=(2, 4))
blocks = np.stack([hist[:, y:y+2, x:x+2].reshape(len(rgb), -1)
for y in range(7) for x in range(7)], axis=1)
blocks /= np.sqrt((blocks * blocks).sum(axis=2, keepdims=True) + 1e-10)
blocks = np.minimum(blocks, 0.2)
blocks /= np.sqrt((blocks * blocks).sum(axis=2, keepdims=True) + 1e-10)
values = blocks.reshape(len(rgb), -1)
if sparse:
# Keep the 256 strongest local orientation features per image.
keep = np.argpartition(values, -256, axis=1)[:, -256:]
sparse_values = np.zeros_like(values)
np.put_along_axis(sparse_values, keep, np.take_along_axis(values, keep, axis=1), axis=1)
values = sparse_values
if hybrid:
color = rgb.reshape(-1, 3, 4, 8, 4, 8).mean(axis=(3, 5)).reshape(len(rgb), -1)
gray = (rgb[:, 0] * .299 + rgb[:, 1] * .587 + rgb[:, 2] * .114)
gray = gray.reshape(-1, 8, 4, 8, 4).mean(axis=(2, 4)).reshape(len(rgb), -1)
values = np.concatenate([values, color * .25, gray * .25, (1 - gray) * .25], axis=1)
encoded = np.zeros((len(rgb), 2048), dtype=np.uint8)
encoded[:, :values.shape[1]] = np.clip(np.rint(values * 3 * 255), 0, 255).astype(np.uint8)
return encoded