-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimagetool
More file actions
executable file
·116 lines (98 loc) · 3.76 KB
/
Copy pathimagetool
File metadata and controls
executable file
·116 lines (98 loc) · 3.76 KB
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python3
"""
imagetool — CLI image editor using OpenCV.
Usage:
imagetool <image> info Show image info
imagetool <image> bg-black [x y w h] Make background black (GrabCut)
imagetool <image> darken <0-100> Darken image
imagetool <image> blur <x y w h> <r> Blur region
imagetool <image> mask-save <output> Save computed mask
imagetool <image> view ASCII preview
"""
import cv2
import numpy as np
import sys
import os
HELP = __doc__
def load(p): return cv2.imread(str(p))
def save(img, p): cv2.imwrite(str(p), img, [cv2.IMWRITE_JPEG_QUALITY, 97])
def info(args):
img = load(args[0])
h, w = img.shape[:2]
print(f"{w}x{h} RGB")
m = cv2.mean(img)[:3]
print(f"Avg: R={m[2]:.0f} G={m[1]:.0f} B={m[0]:.0f}")
print(f"Range: {img.min()}–{img.max()}")
def bg_black(args):
"""Remove background with GrabCut. Optionally provide bounding rect."""
path = args[0]
img = load(path)
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
bgd = np.zeros((1,65), np.float64)
fgd = np.zeros((1,65), np.float64)
if len(args) >= 5:
rx, ry, rw, rh = int(args[1]), int(args[2]), int(args[3]), int(args[4])
rect = (rx, ry, rw, rh)
print(f"Using rect: ({rx},{ry}) {rw}x{rh}")
else:
# Auto: assume subject is centered, take center 60% as probable foreground
margin_x, margin_y = int(w*0.2), int(h*0.2)
rect = (margin_x, margin_y, w - 2*margin_x, h - 2*margin_y)
print(f"Auto rect (centered): {rect}")
cv2.grabCut(img, mask, rect, bgd, fgd, 5, cv2.GC_INIT_WITH_RECT)
# GC_FGD=1, GC_PR_FGD=3 => foreground
fg_mask = np.where((mask == cv2.GC_FGD) | (mask == cv2.GC_PR_FGD), 255, 0).astype(np.uint8)
# Clean up mask
kernel = np.ones((5,5), np.uint8)
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel, iterations=2)
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_OPEN, kernel, iterations=1)
fg_mask = cv2.GaussianBlur(fg_mask.astype(np.float32), (15,15), 7)
# Apply: black background, keep foreground
mf = fg_mask[:,:,None].astype(np.float32) / 255.0
result = (img.astype(np.float32) * mf).astype(np.uint8)
# Save mask for verification
mask_out = os.path.splitext(path)[0] + "_mask.png"
cv2.imwrite(mask_out, fg_mask)
print(f"Mask saved: {mask_out}")
save(result, path)
print(f"Background removed → {path}")
def darken(args):
img = load(args[0])
pct = float(args[1])
a = 1.0 - pct/100
d = cv2.convertScaleAbs(img, alpha=a, beta=-15)
save(d, args[0])
print(f"Darkened {pct}%")
def blur(args):
img = load(args[0])
x, y, bw, bh = int(args[1]), int(args[2]), int(args[3]), int(args[4])
r = int(args[5])
k = r*2+1
img[y:y+bh, x:x+bw] = cv2.GaussianBlur(img[y:y+bh, x:x+bw], (k, k), r//2)
save(img, args[0])
print(f"Blurred ({x},{y})-({x+bw},{y+bh}) r={r}")
def mask_save(args):
img = load(args[0])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Try edge + threshold
blur = cv2.GaussianBlur(gray, (15,15), 5)
edges = cv2.Canny(blur, 30, 100)
cv2.imwrite(args[1], edges)
print(f"Edge mask saved: {args[1]}")
def view(args):
os.system(f"chafa {args[0]} 2>/dev/null || echo 'Install chafa for preview'")
def main():
if len(sys.argv) < 3:
print(HELP); sys.exit(1)
p = sys.argv[1]
if not os.path.exists(p):
print(f"Not found: {p}"); sys.exit(1)
cmd = sys.argv[2]
a = sys.argv[3:]
cmds = {"info":info,"bg-black":bg_black,"darken":darken,"blur":blur,"mask-save":mask_save,"view":view}
if cmd not in cmds:
print(f"Unknown: {cmd}"); print(HELP); sys.exit(1)
cmds[cmd]([p]+a)
if __name__ == "__main__":
main()