Cropping investigation
This commit is contained in:
1 parent
48602c24f1
commit
bd1964ffcd
10 files changed
+814
-24
No files matched your search
@@ -0,0 +1,95 @@
|
||||
"""Group small isolated border marks using evidence from one displayed page.
|
||||
|
||||
This identifies dispensable border marks, without recognizing pagination or
|
||||
consulting other pages. Component IDs are local to the caller's foreground.
|
||||
Long paper-line candidates are excluded from grouping but remain in the crop.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
|
||||
def classify_edge_marks(labels, stats, eligible, rgb, dpi):
|
||||
px = dpi/25.4
|
||||
height, width = labels.shape
|
||||
components = []
|
||||
for i, (x, y, w, h, area) in enumerate(stats[1:], 1):
|
||||
if not eligible[i] or area < .15*px*px:
|
||||
continue
|
||||
# Exclusion from candidate grouping ONLY. Pixels remain in the crop mask.
|
||||
if ((h > 25*px and w < 3*px) or (w > 25*px and h < .6*px)):
|
||||
continue
|
||||
components.append(i)
|
||||
parent = {i:i for i in components}
|
||||
def find(i):
|
||||
while parent[i] != i:
|
||||
parent[i] = parent[parent[i]]
|
||||
i = parent[i]
|
||||
return i
|
||||
# A y sweep bounds the search on noisy pages; uncertain large groups stay.
|
||||
edge_components = [i for i in components if stats[i,2] <= 40*px
|
||||
and stats[i,3] <= 18*px and
|
||||
(stats[i,1]+stats[i,3] <= 40*px
|
||||
or stats[i,1] >= height-40*px)]
|
||||
if len(edge_components) > 512:
|
||||
return [], []
|
||||
colours = {}
|
||||
if rgb is not None:
|
||||
for i in edge_components:
|
||||
x,y,w,h,_ = stats[i]
|
||||
values = rgb[y:y+h,x:x+w][labels[y:y+h,x:x+w]==i]
|
||||
colours[i] = float(np.median(values.max(axis=1)-values.min(axis=1)))
|
||||
ordered = sorted(edge_components, key=lambda i:stats[i,1])
|
||||
for a, i in enumerate(ordered):
|
||||
x,y,w,h,_ = stats[i]
|
||||
for j in ordered[a+1:]:
|
||||
xx,yy,ww,hh,_ = stats[j]
|
||||
if yy > y+h+1.5*px:
|
||||
break
|
||||
gap_x = max(0, xx-(x+w), x-(xx+ww))
|
||||
gap_y = max(0, yy-(y+h), y-(yy+hh))
|
||||
incompatible = (rgb is not None and
|
||||
((colours[i] < 35 and colours[j] > 60)
|
||||
or (colours[j] < 35 and colours[i] > 60)))
|
||||
if not incompatible and gap_x <= 3*px and gap_y <= 1.5*px:
|
||||
parent[find(j)] = find(i)
|
||||
groups = {}
|
||||
for i in edge_components:
|
||||
groups.setdefault(find(i), []).append(i)
|
||||
rows = []
|
||||
for ids in groups.values():
|
||||
s=stats[ids];x,y=s[:,:2].min(axis=0);right,bottom=(s[:,:2]+s[:,2:4]).max(axis=0)
|
||||
w,h=right-x,bottom-y
|
||||
if not (w <= 40*px and h <= 18*px and h >= 1.5*px):
|
||||
continue
|
||||
edge = 'top' if bottom <= 40*px else 'bottom' if y >= height-40*px else None
|
||||
if edge is None:
|
||||
continue
|
||||
underline_ids=[]
|
||||
for i in ids:
|
||||
xx,yy,ww,hh,area=stats[i]
|
||||
if ww > 3*hh and ww >= .65*w and yy+hh >= bottom-1.5*px:
|
||||
underline_ids.append(i)
|
||||
lateral = (x+w/2 < width*.25 or x+w/2 > width*.75)
|
||||
# Check separation from other compact strokes across the full width.
|
||||
# Confirmed long line candidates remain in the actual crop mask.
|
||||
body = [i for i in components if i not in ids and stats[i,3] < 18*px]
|
||||
edge_distance = (y if edge == 'top' else height-bottom)/px
|
||||
if body:
|
||||
gap = ((min(stats[i,1] for i in body)-(y+h))/px if edge=='top' else
|
||||
(y-max(stats[i,1]+stats[i,3] for i in body))/px)
|
||||
else:
|
||||
gap = None
|
||||
truncated = (x <= px or y <= px or right >= width-px or bottom >= height-px)
|
||||
reasons=[]
|
||||
if lateral:reasons.append('lateral-placement')
|
||||
if gap is not None and gap >= 20:reasons.append('all-column-stroke-gap>=20mm')
|
||||
if edge_distance <= 15:reasons.append('within-15mm-of-edge')
|
||||
if underline_ids:reasons.append('underline-protection')
|
||||
if truncated:reasons.append('scan-edge-truncated')
|
||||
proposed = (lateral and edge_distance <= 15 and gap is not None
|
||||
and gap >= 20 and not underline_ids and not truncated)
|
||||
rows.append(dict(bbox_mm=[round(float(v/px),2) for v in [x,y,x+w,y+h]],
|
||||
component_ids=ids,edge=edge,edge_distance_mm=round(edge_distance,2),gap_mm=None if gap is None else round(gap,2),
|
||||
evidence=reasons,proposed_pagination=bool(proposed)))
|
||||
removed = [i for row in rows if row['proposed_pagination'] for i in row['component_ids']]
|
||||
return removed, rows
|
||||
|
||||
Reference in new issue
Block a user