Spaces:
Runtime error
Runtime error
#!/usr/bin/env python3 | |
import os | |
import math | |
import cv2 | |
import base64 | |
import numpy as np | |
from typing import NamedTuple, Tuple, List | |
from entity import Entity | |
from common import mkdir | |
TILE_SIZE = 416 | |
TILE_OVERLAP = 0.8 | |
class BoundingBox(NamedTuple): | |
x: float = 0.0 | |
y: float = 0.0 | |
w: float = 0.0 | |
h: float = 0.0 | |
def from_centroid(cls, c, shape): | |
(ih, iw, ic) = shape | |
self = cls(x=math.floor(w*(c.x - c.w/2)) | |
, y=math.floor(h*(c.y - c.h/2)) | |
, w=math.ceil(w*c.w) | |
, h=math.ceil(h*c.h)) | |
return self | |
def from_dict(cls, d): | |
self = cls(x=d['x'], y=d['y'], w=d['width'], h=d['height']) | |
return self | |
def from_arr(cls, a): | |
self = cls(*a) | |
return self | |
def start(self): | |
return floor_point(self.x, self.y) | |
def end(self): | |
return floor_point(self.x + self.w, self.y + self.h) | |
def to_centroid(self, shape): | |
(h, w, c) = shape | |
return Centroid(x=math.floor(self.x + self.w/2)/w | |
, y=math.floor(self.y + self.h/2)/h | |
, w=math.ceil(self.w)/w | |
, h=math.ceil(self.h)/h) | |
def intersect(self, f, r: float = 0.8): | |
six = self.x - f.x | |
siy = self.y - f.y | |
eix = six + self.w | |
eiy = siy + self.h | |
if six < 0: | |
if six + self.w < 0: | |
return None | |
six = 0 | |
if siy < 0: | |
if siy + self.h < 0: | |
return None | |
siy = 0 | |
if eix > f.w: | |
if eix - self.w > f.w: | |
return None | |
eix = f.w | |
if eiy > f.h: | |
if eiy - self.h > f.h: | |
return None | |
eiy = f.h | |
i = BoundingBox(six, siy, eix - six, eiy - siy) | |
if (i.w*i.h) < (self.w*self.h)*r: | |
return None | |
return i | |
class Centroid(BoundingBox): | |
def to_bounding_box(self, shape): | |
(h, w, c) = shape | |
return BoundingBox( | |
x=math.floor(w*(self.x - self.w/2)) | |
, y=math.floor(h*(self.y - self.h/2)) | |
, w=math.ceil(w*self.w) | |
, h=math.ceil(h*self.h)) | |
def to_annotation(self, id: int): | |
return f'{id} {self.x} {self.y} {self.w} {self.h}' | |
def read_base64(data): | |
ib = base64.b64decode(data[22:]) | |
arr = np.frombuffer(ib, dtype = np.uint8) | |
return cv2.imdecode(arr, flags=cv2.IMREAD_COLOR) | |
def read_markers(filename: str, Type: type): | |
ret = [] | |
with open(filename, 'r') as f: | |
lines = f.readlines() | |
for l in lines: | |
try: | |
(b, x,y,w,h, p) = [float(i) for i in l.split(' ')] | |
except: | |
try: | |
(b, x,y,w,h) = [float(i) for i in l.split(' ')] | |
except: | |
continue | |
p = -1 | |
ret.append({"class": b, "prob": p, "box": Type(x,y,w,h)}) | |
assert(len(ret)) | |
return ret | |
def read_centroids(filename: str): | |
return read_markers(filename, Centroid) | |
def coord_dict_to_point(c: dict): | |
return coord_to_point(c['x'], c['y'], c['width'], c['height']) | |
def coord_to_point(cx, cy, cw, ch): | |
x = math.floor(cx + cw/2) | |
y = math.floor(cy + ch/2) | |
return f"{x} {y} {math.ceil(cw)} {math.ceil(ch)}" | |
def floor_point(x: float, y: float): | |
return (math.floor(x), math.floor(y)) | |
def cut_img(im, s: Tuple[float, float], e: Tuple[float, float]): | |
x1 = math.floor(s[0]) | |
y1 = math.floor(s[1]) | |
x2 = math.floor(e[0]) | |
y2 = math.floor(e[1]) | |
return im[y1:y2, x1:x2] | |
def cut_logo(im, l): | |
(x, y, w, h) = floor_logo(l) | |
return im[y:y+h, x:x+w] | |
def add_alpha(img): | |
b, g, r = cv2.split(img) | |
a = np.ones(b.shape, dtype=b.dtype) * 50 | |
return cv2.merge((b,g,r,a)) | |
def remove_white(img): | |
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) | |
gray = 255*(gray<128) | |
coords = cv2.findNonZero(gray) | |
# Find minimum spanning bounding box | |
bb = BoundingBox(*cv2.boundingRect(coords)) | |
rect = img[bb.y:bb.y+bb.h, bb.x:bb.x+bb.w] # Crop the image - note we do this on the original image | |
return rect, bb | |
def mix(a, b, fx, fy): | |
alpha = b[:, :, 3]/255 | |
return _mix_alpha(a, b, alpha, fx, fy) | |
def mix_alpha(a, b, ba, fx, fy): | |
(ah, aw, ac) = a.shape | |
(bh, bw, bc) = b.shape | |
p = 0.2 | |
if (aw*p < bw or ah*p < bh): | |
f = min(p*aw/bw, p*ah/bh) | |
nw, nh = floor_point(bw*f, bh*f) | |
#print(f'resizing to fit in {aw}x{ah}\t {bw}x{bh}\t=> {nw}x{nh}\tfactor {f}') | |
r = cv2.resize(b, (nw, nh), interpolation = cv2.INTER_LINEAR) | |
rba = cv2.resize(ba, (nw, nh), interpolation = cv2.INTER_LINEAR) | |
return mix_alpha(a, r, rba, fx, fy) | |
assert bw > 10, f'b({bw}) too small' | |
assert bh > 10, f'b({bh}) too small' | |
return _mix_alpha(a, b, ba, fx, fy) | |
def _mix_alpha(a, b, ba, fx, fy): | |
(ah, aw, ac) = a.shape | |
(bh, bw, bc) = b.shape | |
x = math.floor(fx*(aw - bw)) | |
y = math.floor(fy*(ah - bh)) | |
# handle transparency | |
mat = a[y:y+bh,x:x+bw] | |
cols = b[:, :, :3] | |
mask = np.dstack((ba, ba, ba)) | |
a[y:y+bh,x:x+bw] = mat * (1 - mask) + cols * mask | |
#a[y:y+bh,x:x+bw] = cols | |
return BoundingBox(x, y, bw, bh) | |
def crop(id, fn, logos: List[Centroid], out = './data/squares'): | |
basename = os.path.basename(fn).replace('.png', '') | |
img_out = f"{out}/images" | |
txt_out = f"{out}/labels" | |
debug_out = f"{defaults.DEBUG_PATH}/{out}" | |
mkdir.make_dirs([debug_out, img_out, txt_out]) | |
im = cv2.imread(fn) | |
rim = cv2.imread(fn) | |
(h, w, c) = im.shape | |
(tw, th) = (min(w, TILE_SIZE), min(h, TILE_SIZE)) | |
(tx, ty)= ( | |
math.ceil(w/(tw*TILE_OVERLAP)), | |
math.ceil(h/(th*TILE_OVERLAP)) | |
) | |
print('shape', basename, tx, ty, w, h) | |
for x in range(tx): | |
for y in range(ty): | |
color = (0,x*(255/tx),y*(255/ty)) | |
logo_color = (255, 0, 0) | |
if tx < 2: | |
xs = 0 | |
else: | |
xs = (w - tw)*x/(tx - 1) | |
if ty < 2: | |
ys = 0 | |
else: | |
ys = (h - th)*y/(ty - 1) | |
f = BoundingBox(xs, ys, tw, th) | |
start = floor_point(f.x, f.y) | |
end = floor_point(f.x + f.w, f.y + f.h) | |
rim = cv2.rectangle(rim, start, end, color, 10) | |
li = [] | |
for l in logos: | |
bl = l.to_bounding_box(im.shape) | |
rim = cv2.rectangle(rim, bl.start, bl.end, logo_color, 5) | |
p = bl.intersect(f, 0.5) | |
if p: | |
li.append(p) | |
nim = cut_img(im, start, end) | |
rnim = cut_img(rim, start, end) | |
img_name =f"{img_out}/{basename}-x{x}y{y}.jpg" | |
txt_name =f"{txt_out}/{basename}-x{x}y{y}.txt" | |
cv2.imwrite(img_name, nim) | |
if len(li): | |
with open(txt_name, 'w') as label: | |
for p in li: | |
dim = cv2.rectangle(rnim, p.start, p.end, logo_color, 5) | |
lc = p.to_centroid((TILE_SIZE, TILE_SIZE, 3)) | |
a = f"{int(id)} {lc.x} {lc.y} {lc.w} {lc.h}" | |
label.write(a) | |
cv2.imwrite(f'{debug_out}/{basename}{x}{y}.debug.png', dim) | |
cv2.imwrite(f'{debug_out}/{basename}.debug.png', rim) | |
if __name__ == '__main__': | |
i = 0 | |
with os.scandir('./data/') as it: | |
for e in it: | |
if e.name.endswith('.txt') and e.is_file(): | |
print(e.name) | |
try: | |
i+=1 | |
bco, boxes = read_bounding_boxes(e.path) | |
crop(i, e.path.replace('.txt', '.png'), boxes) | |
except Exception as err: | |
print(err) | |