Tady je kompletní „end-to-end“ funkce, která pro obě oblasti (bottom‐left i top‐right):
-
Vygeneruje v barvách
BLACK
,DARK_MAX
,GRAY_MIN
,WHITE
-
vodorovné čáry (0°)
-
šikmé čáry (+15°)
-
a jednu vertikální šedou čáru (90° / GRAY)
-
-
Uloží je ve velikosti původního regionu
-
Zmenší je do „small“ pomocí vašeho
bl_div
/tr_div
(NEAREST, bez binarizace) -
Pro každý výsledný small obraz:
-
vytvoří row/col projekce
-
normalizuje je
normalize_projection_manual
(vrací(img_norm, scale, offset)
) -
spočte
find_intervals(…, ShadowType.DARK)
afind_intervals(…, ShadowType.GRAY)
-
uloží všechny mezivýsledky (
2D
, rawsmall
,row
,col
,nrow
,ncol
) -
vytiskne intervaly i skutečné prahové hodnoty, na kterých byly založeny
-
import os
from enum import Enum
from dataclasses import dataclass
from PIL import Image, ImageDraw
import numpy as np
class ShadowType(Enum):
DARK = "dark"
GRAY = "gray"
class ShadowRanges(Enum):
BLACK = 0
DARK_MAX = 188
GRAY_MIN = 189
GRAY_MAX = 232
WHITE = 255
@dataclass
class PageRegions:
top_right: tuple[int,int,int,int]
bottom: tuple[int,int,int,int]
def normalize_projection_manual(img_proj: Image.Image, original_length: int):
pixels = list(img_proj.getdata())
mn, mx = min(pixels), max(pixels)
if mx == mn:
img_proj.scale = 1.0
img_proj.offset = mn
return img_proj, 1.0, mn
inten_scale = 255.0 / (mx - mn)
length_scale = original_length / (img_proj.width if img_proj.height == 1 else img_proj.height)
scale = inten_scale * length_scale
offset = mn
lut = [min(255, max(0, int((i - offset) * scale))) for i in range(256)]
img_norm = img_proj.point(lut)
img_norm.scale = scale
img_norm.offset = offset
return img_norm, scale, offset
def find_intervals(vals: list[int], shadow: ShadowType):
if shadow is ShadowType.DARK:
lo, hi = ShadowRanges.BLACK.value, ShadowRanges.DARK_MAX.value
else:
lo, hi = ShadowRanges.GRAY_MIN.value, ShadowRanges.GRAY_MAX.value
intervals = []
start = None
for i, v in enumerate(vals):
if lo <= v <= hi:
if start is None: start = i
elif start is not None:
intervals.append((start, i-1))
start = None
if start is not None:
intervals.append((start, len(vals)-1))
return intervals
def simulate_and_measure_thresholds(
region_px: tuple[int,int],
debug_folder: str,
label: str,
div_factor: int,
orientations: list[float] = (0.0, 15.0)
):
"""
1) Vytvoří v debug_folder obraz velikosti region_px + horizontální, šikmé i vertikální čáry
2) Zmenší (NEAREST) o div_factor na small
3) Pro small:
- vytvoří row/col projekce
- normalizuje je
- spočte intervaly pro DARK i GRAY
- uloží všechny mezivýsledky
4) Vytiskne skutečná lo/hi použitá pro detekci a nalezené intervaly.
"""
os.makedirs(debug_folder, exist_ok=True)
w0, h0 = region_px
# barvy + tvary
shades = {
"black": ShadowRanges.BLACK.value,
"dark": ShadowRanges.DARK_MAX.value,
"gray": ShadowRanges.GRAY_MIN.value,
"white": ShadowRanges.WHITE.value
}
for orientation in orientations + [90.0]: # +90° jednou vertikálně
for name, intensity in shades.items():
base = Image.new("L", (w0, h0), 255)
draw = ImageDraw.Draw(base)
# horizontální / šikmá / vertikální
if orientation == 90.0:
# vertikální čára uprostřed šedě
x0 = w0//2 - 16
draw.rectangle([x0, 0, x0+31, h0], fill=ShadowRanges.GRAY_MIN.value)
else:
length_px = 368
thick_px = 32
gap = 4
img = base.copy()
d = ImageDraw.Draw(img)
d.rectangle([gap, gap, gap+length_px, gap+thick_px], fill=intensity)
img = img.rotate(orientation, expand=False, center=(w0//2,h0//2))
base = img
# 2D uložíme
fname2d = f"{label}_{name}_{int(orientation)}.png"
p2d = os.path.join(debug_folder, fname2d)
base.save(p2d)
# zmenšení small
small = base.resize((w0//div_factor, h0//div_factor), Image.Resampling.NEAREST)
p_small = os.path.join(debug_folder, f"{label}_{name}_{int(orientation)}_small.png")
small.save(p_small)
# 1D projekce
w, h = small.size
row = small.resize((w,1), resample=Image.Resampling.BILINEAR)
col = small.resize((1,h), resample=Image.Resampling.BILINEAR)
p_row = os.path.join(debug_folder, f"{label}_{name}_{int(orientation)}_small_row.png")
p_col = os.path.join(debug_folder, f"{label}_{name}_{int(orientation)}_small_col.png")
row.save(p_row)
col.save(p_col)
# normalizace
nrow, scale_r, off_r = normalize_projection_manual(row, original_length=w)
ncol, scale_c, off_c = normalize_projection_manual(col, original_length=h)
p_nrow = p_row.replace(".png", "_n.png")
p_ncol = p_col.replace(".png", "_n.png")
nrow.save(p_nrow)
ncol.save(p_ncol)
# detekce intervalů
vals_row = list(nrow.getdata())
vals_col = list(ncol.getdata())
int_row_dark = find_intervals(vals_row, ShadowType.DARK)
int_row_gray = find_intervals(vals_row, ShadowType.GRAY)
int_col_dark = find_intervals(vals_col, ShadowType.DARK)
int_col_gray = find_intervals(vals_col, ShadowType.GRAY)
print(f"[{label} {name} {orientation}°] "
f"scale_r={scale_r:.3f}, off_r={off_r}, "
f"scale_c={scale_c:.3f}, off_c={off_c}")
print(" → row_dark:", int_row_dark, " row_gray:", int_row_gray)
print(" → col_dark:", int_col_dark, " col_gray:", int_col_gray)
print(" --------------------------------------")
Jak to použít:
regions = PageRegions(
top_right=(1875, 30, 2400, 300),
bottom=(25, 3050, 2455, 3310),
)
# po výpočtu scale_factor, bl_div, tr_div v `create_thumbnail_with_regions`:
simulate_and_measure_thresholds(
region_px=(regions.bottom[2]-regions.bottom[0],
regions.bottom[3]-regions.bottom[1]),
debug_folder="/path/to/output/debug_sim_bl",
label="bl",
div_factor=bl_div,
orientations=[0.0, 15.0]
)
simulate_and_measure_thresholds(
region_px=(regions.top_right[2]-regions.top_right[0],
regions.top_right[3]-regions.top_right[1]),
debug_folder="/path/to/output/debug_sim_tr",
label="tr",
div_factor=tr_div,
orientations=[0.0, 15.0]
)
Tím dostaneš pro každou barvu a orientaci:
-
originál 2D (čára na bílé ploše)
-
small verzi
-
row/col projekce (raw i normalizované)
-
vypsané skutečné
scale
,offset
a nalezené intervaly proDARK
aGRAY
Na základě toho si přesně doladíš ShadowRanges.DARK_MAX
, GRAY_MIN
a GRAY_MAX
.
Žádné komentáře:
Okomentovat