LPX55's picture
refactor: update import paths for forensics utilities and enhance logging functionality in inference data
bc355a9
import numpy as np
import cv2 as cv
from PIL import Image
def norm_mat(mat):
return cv.normalize(mat, None, 0, 255, cv.NORM_MINMAX).astype(np.uint8)
def minmax_dev(patch, mask):
c = patch[1, 1]
minimum, maximum, _, _ = cv.minMaxLoc(patch, mask)
if c < minimum:
return -1
if c > maximum:
return +1
return 0
def blk_filter(img, radius):
result = np.zeros_like(img, np.float32)
rows, cols = result.shape
block = 2 * radius + 1
for i in range(radius, rows, block):
for j in range(radius, cols, block):
result[
i - radius : i + radius + 1, j - radius : j + radius + 1
] = np.std(
img[i - radius : i + radius + 1, j - radius : j + radius + 1]
)
return cv.normalize(result, None, 0, 127, cv.NORM_MINMAX, cv.CV_8UC1)
def minmax_process(image, channel=4, radius=2):
"""
Analyzes local pixel value deviations in an image to detect potential manipulation by comparing each pixel with its neighbors. This forensic technique helps identify areas where pixel values deviate significantly from their local context, which can indicate digital tampering or editing.
Args:
image: Union[np.ndarray, Image.Image]: Input image to process
channel (int): Channel to process (0:Grayscale, 1:Blue, 2:Green, 3:Red, 4:RGB Norm)
radius (int): Radius for block filtering
Returns:
PIL.Image.Image: The processed image showing minmax deviations.
Raises:
ValueError: If the input image is invalid or channel/radius parameters are out of valid range.
"""
if not isinstance(image, np.ndarray):
image = np.array(image) # Ensure image is a NumPy array
if channel == 0:
img = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
elif channel == 4:
b, g, r = cv.split(image.astype(np.float64))
img = cv.sqrt(cv.pow(b, 2) + cv.pow(g, 2) + cv.pow(r, 2))
else:
img = image[:, :, 3 - channel]
kernel = 3
border = kernel // 2
shape = (img.shape[0] - kernel + 1, img.shape[1] - kernel + 1, kernel, kernel)
strides = 2 * img.strides
patches = np.lib.stride_tricks.as_strided(img, shape=shape, strides=strides)
patches = patches.reshape((-1, kernel, kernel))
mask = np.full((kernel, kernel), 255, dtype=np.uint8)
mask[border, border] = 0
blocks = [0] * shape[0] * shape[1]
for i, patch in enumerate(patches):
blocks[i] = minmax_dev(patch, mask)
output = np.array(blocks).reshape(shape[:-2])
output = cv.copyMakeBorder(
output, border, border, border, border, cv.BORDER_CONSTANT
)
low = output == -1
high = output == +1
minmax = np.zeros_like(image)
if radius > 0:
radius += 3
low = blk_filter(low, radius)
high = blk_filter(high, radius)
if channel <= 2:
minmax[:, :, 2 - channel] = low
minmax[:, :, 2 - channel] += high
else:
minmax = np.repeat(low[:, :, np.newaxis], 3, axis=2)
minmax += np.repeat(high[:, :, np.newaxis], 3, axis=2)
minmax = norm_mat(minmax)
else:
if channel == 0:
minmax[low] = [0, 0, 255]
minmax[high] = [0, 0, 255]
elif channel == 1:
minmax[low] = [0, 255, 0]
minmax[high] = [0, 255, 0]
elif channel == 2:
minmax[low] = [255, 0, 0]
minmax[high] = [255, 0, 0]
elif channel == 3:
minmax[low] = [255, 255, 255]
minmax[high] = [255, 255, 255]
return Image.fromarray(minmax)