def lu2010(im): im_h, im_w = im.shape # im_bg_row = polynomial_background_easy(im) # TODO: implement full # im_bg = polynomial_background_easy(im_bg_row.T).T # im_bg = im_bg.clip(0.1, 255) IM = cv2.erode(niblack(im, window_size=61, k=0.2), rect33) inpainted, modified = inpaint.inpaint_ng14(im, -IM) im_bg = (inpainted & ~IM) | (modified & IM) im_bg = im_bg.astype(np.float32).clip(0.1, 255) debug_imwrite('bg.png', im_bg) C = np.percentile(im, 30) I_bar = clip_u8(C / im_bg * im) debug_imwrite('ibar.png', I_bar) I_bar_padded = np.pad(I_bar, (1, 1), 'edge') V_h = cv2.absdiff(I_bar_padded[2:, 1:-1], I_bar_padded[:-2, 1:-1]) V_v = cv2.absdiff(I_bar_padded[1:-1, 2:], I_bar_padded[1:-1, :-2]) V = V_h + V_v V[V < V_h] = 255 # clip overflow _, stroke_edges = cv2.threshold(V, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 1 if high contrast, 0 otherwise. E_inv = stroke_edges & 1 E_inv_255 = E_inv * 255 debug_imwrite('e_inv.png', E_inv_255) im_high = im & E_inv_255 debug_imwrite('im_high.png', im_high) # calculate stroke width H, _ = np.histogram(nonzero_distances_row(E_inv), np.arange(im_h / 100)) H[1] = 0 # don't use adjacent pix W = H.argmax() print('stroke width:', W) size = 2 * W N_min = W if size >= 16: E_inv = E_inv.astype(np.uint16) window = (size | 1, size | 1) N_e = cv2.boxFilter(E_inv, -1, window, normalize=False) debug_imwrite('n_e.png', bool_to_u8(N_e >= N_min)) E_mean = cv2.boxFilter(im_high, cv2.CV_32S, window, normalize=False) / (N_e.astype(np.float64) + 0.1) debug_imwrite('i_bar_e_mean.png', bool_to_u8(I_bar <= E_mean)) out = ~bool_to_u8((N_e >= N_min) & (I_bar <= E_mean)) debug_imwrite('lu2010.png', out) return out
def gradient2(im): im_inv = ~im mins = erode_square(im_inv, 15) maxes = dilate_square(im_inv, 15) diff = maxes - mins return bool_to_u8(diff < 50)
def remove_stroke_outliers(im, lines, k=1.0): stroke_widths = fast_stroke_width(im) if lib.debug: lib.debug_imwrite('strokes.png', lib.normalize_u8(stroke_widths.clip(0, 10))) mask = np.zeros(im.shape, dtype=np.uint8) for line in lines: for letter in line: sliced = letter.crop().apply(mask) sliced |= letter.raster() lib.debug_imwrite('letter_mask.png', -mask) masked_strokes = stroke_widths.copy() masked_strokes &= -mask strokes_mean, strokes_std = masked_mean_std(masked_strokes, mask) if lib.debug: print('overall: mean:', strokes_mean, 'std:', strokes_std) debug = cv2.cvtColor(im, cv2.COLOR_GRAY2RGB) new_lines = [] for line in lines: if len(line) <= 1: continue good_letters = [] for letter in line: crop = letter.crop() if not crop.nonempty(): continue raster = letter.raster() sliced_strokes = crop.apply(stroke_widths).copy() sliced_strokes &= lib.bool_to_u8(raster) mean, std = masked_mean_std(sliced_strokes, raster) if mean < strokes_mean - k * strokes_std: if lib.debug: print('skipping {:4d} {:4d} {:.03f} {:.03f}'.format( letter.x, letter.y, mean, std, )) letter.box(debug, color=lib.RED) else: if lib.debug: letter.box(debug, color=lib.GREEN) good_letters.append(letter) if good_letters: new_lines.append(TextLine(good_letters, underlines=line.underlines)) lib.debug_imwrite("stroke_filter.png", debug) return new_lines
def retinex(im, mu_1=0.9, mu_2=25, sigma=5): G = cv2.GaussianBlur(im, (0, 0), sigma) debug_imwrite('G.png', G) bools = (im < mu_1 * G) & (cv2.absdiff(im, G) > mu_2) return bool_to_u8(bools)
def roth(im, s=51, t=0.8): im_h, im_w = im.shape means = cv2.blur(im, (s, s)) ints = cv2.bitwise_not(bool_to_u8(im > means * t)) debug_imwrite('roth.png', ints) return ints
def sauvola(im, window_size=61, k=0.2): assert im.dtype == np.uint8 means, stds = mean_std(im, window_size) thresh = means * (1 + k * ((stds / 127) - 1)) return bool_to_u8(im > thresh)
def niblack(im, window_size=61, k=0.2): means, stds = mean_std(im, window_size) thresh = means + k * stds return bool_to_u8(im > thresh)