File size: 3,613 Bytes
237292f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7003f1
 
 
 
 
 
 
 
 
 
 
 
 
322ab55
 
237292f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc355a9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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)