def clean_page(img, max_scale=defaults.CC_SCALE_MAX, min_scale=defaults.CC_SCALE_MIN): #img = cv2.imread(sys.argv[1]) (h,w,d)=img.shape gray = grayscale(img) #create gaussian filtered and unfiltered binary images sigma = arg.float_value('sigma',default_value=defaults.GAUSSIAN_FILTER_SIGMA) if arg.boolean_value('verbose'): print 'Binarizing image with sigma value of ' + str(sigma) gaussian_filtered = scipy.ndimage.gaussian_filter(gray, sigma=sigma) binary_threshold = arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'Binarizing image with sigma value of ' + str(sigma) gaussian_binary = binarize(gaussian_filtered, threshold=binary_threshold) binary = binarize(gray, threshold=binary_threshold) #Draw out statistics on average connected component size in the rescaled, binary image average_size = cc.average_size(gaussian_binary) #print 'Initial mask average size is ' + str(average_size) max_size = average_size*max_scale min_size = average_size*min_scale #primary mask is connected components filtered by size mask = cc.form_mask(gaussian_binary, max_size, min_size) #secondary mask is formed from canny edges canny_mask = form_canny_mask(gaussian_filtered, mask=mask) #final mask is size filtered connected components on canny mask final_mask = cc.form_mask(canny_mask, max_size, min_size) #apply mask and return images cleaned = cv2.bitwise_not(final_mask * binary) return (cv2.bitwise_not(binary), final_mask, cleaned)
def filter_text_like_areas(img, segmentation, average_size): #see if a given rectangular area (2d slice) is very text like #First step is to estimate furigana like elements so they can be masked furigana_areas = furigana.estimate_furigana(img, segmentation) furigana_mask = np.array(furigana_areas == 0, 'B') #binarize the image, clean it via the segmentation and remove furigana too binary_threshold = arg.integer_value( 'binary_threshold', default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'binarizing images with threshold value of ' + str( binary_threshold) binary = clean.binarize(img, threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print 'average cc size for binaryized grayscale image is ' + str( binary_average_size) segmentation_mask = np.array(segmentation != 0, 'B') cleaned = binary * segmentation_mask * furigana_mask inv_cleaned = cv2.bitwise_not(cleaned) areas = cc.get_connected_components(segmentation) text_like_areas = [] nontext_like_areas = [] for area in areas: #if area_is_text_like(cleaned, area, average_size): if text_like_histogram(cleaned, area, average_size): text_like_areas.append(area) else: nontext_like_areas.append(area) return (text_like_areas, nontext_like_areas)
def estimate_furigana(img, segmentation): (w,h)=img.shape[:2] if arg.boolean_value('verbose'): print('Estimateding furigana in ' + str(h) + 'x' + str(w) + ' image.') text_areas = segmentation #form binary image from grayscale binary_threshold = arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print('binarizing images with threshold value of ' + str(binary_threshold)) binary = clean.binarize(img,threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print('average cc size for binaryized grayscale image is ' + str(binary_average_size)) #apply mask and return images text_mask = binary_mask(text_areas) cleaned = cv2.bitwise_not(text_mask*binary) cleaned_average_size = cc.average_size(cleaned) if arg.boolean_value('verbose'): print('average cc size for cleaned, binaryized grayscale image is ' + str(cleaned_average_size)) columns = scipy.ndimage.filters.gaussian_filter(cleaned,(defaults.FURIGANA_VERTICAL_SIGMA_MULTIPLIER*binary_average_size,defaults.FURIGANA_HORIZONTAL_SIGMA_MULTIPLIER*binary_average_size)) columns = clean.binarize(columns,threshold=defaults.FURIGANA_BINARY_THRESHOLD) furigana = columns*text_mask #go through the columns in each text area, and: #1) Estimate the standard column width (it should be similar to the average connected component width) #2) Separate out those columns which are significantly thinner (>75%) than the standard width boxes = cc.get_connected_components(furigana) furigana_lines = [] non_furigana_lines = [] lines_general = [] for box in boxes: line_width = cc_width(box) line_to_left = find_cc_to_left(box, boxes, max_dist=line_width*defaults.FURIGANA_DISTANCE_MULTIPLIER) if line_to_left is None: non_furigana_lines.append(box) continue left_line_width = cc_width(line_to_left) if line_width < left_line_width * defaults.FURIGANA_WIDTH_THRESHOLD: furigana_lines.append(box) else: non_furigana_lines.append(box) furigana_mask = np.zeros(furigana.shape) for f in furigana_lines: furigana_mask[f[0].start:f[0].stop,f[1].start:f[1].stop]=255 #furigana_mask[f]=1 furigana = furigana_mask #furigana * furigana_mask if arg.boolean_value('debug'): furigana = 0.25*(columns*text_mask) + 0.25*img + 0.5*furigana return furigana
def filter_text_like_areas(img, segmentation, average_size): #see if a given rectangular area (2d slice) is very text like #First step is to estimate furigana like elements so they can be masked furigana_areas = furigana.estimate_furigana(img, segmentation) furigana_mask = np.array(furigana_areas==0,'B') #binarize the image, clean it via the segmentation and remove furigana too binary_threshold = arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'binarizing images with threshold value of ' + str(binary_threshold) binary = clean.binarize(img,threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print 'average cc size for binaryized grayscale image is ' + str(binary_average_size) segmentation_mask = np.array(segmentation!=0,'B') cleaned = binary * segmentation_mask * furigana_mask inv_cleaned = cv2.bitwise_not(cleaned) areas = cc.get_connected_components(segmentation) text_like_areas = [] nontext_like_areas = [] for area in areas: #if area_is_text_like(cleaned, area, average_size): if text_like_histogram(cleaned, area, average_size): text_like_areas.append(area) else: nontext_like_areas.append(area) return (text_like_areas, nontext_like_areas)
def estimate_furigana(img, segmentation): (w,h)=img.shape[:2] if arg.boolean_value('verbose'): print 'Estimateding furigana in ' + str(h) + 'x' + str(w) + ' image.' text_areas = segmentation #form binary image from grayscale binary_threshold = arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'binarizing images with threshold value of ' + str(binary_threshold) binary = clean.binarize(img,threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print 'average cc size for binaryized grayscale image is ' + str(binary_average_size) #apply mask and return images text_mask = binary_mask(text_areas) cleaned = cv2.bitwise_not(text_mask*binary) cleaned_average_size = cc.average_size(cleaned) if arg.boolean_value('verbose'): print 'average cc size for cleaned, binaryized grayscale image is ' + str(cleaned_average_size) columns = scipy.ndimage.filters.gaussian_filter(cleaned,(defaults.FURIGANA_VERTICAL_SIGMA_MULTIPLIER*binary_average_size,defaults.FURIGANA_HORIZONTAL_SIGMA_MULTIPLIER*binary_average_size)) columns = clean.binarize(columns,threshold=defaults.FURIGANA_BINARY_THRESHOLD) furigana = columns*text_mask #go through the columns in each text area, and: #1) Estimate the standard column width (it should be similar to the average connected component width) #2) Separate out those columns which are significantly thinner (>75%) than the standard width boxes = cc.get_connected_components(furigana) furigana_lines = [] non_furigana_lines = [] lines_general = [] for box in boxes: line_width = cc_width(box) line_to_left = find_cc_to_left(box, boxes, max_dist=line_width*defaults.FURIGANA_DISTANCE_MULTIPLIER) if line_to_left is None: non_furigana_lines.append(box) continue left_line_width = cc_width(line_to_left) if line_width < left_line_width * defaults.FURIGANA_WIDTH_THRESHOLD: furigana_lines.append(box) else: non_furigana_lines.append(box) furigana_mask = np.zeros(furigana.shape) for f in furigana_lines: furigana_mask[f[0].start:f[0].stop,f[1].start:f[1].stop]=255 #furigana_mask[f]=1 furigana = furigana_mask #furigana * furigana_mask if arg.boolean_value('debug'): furigana = 0.25*(columns*text_mask) + 0.25*img + 0.5*furigana return furigana
def main(): parser = arg.parser parser = argparse.ArgumentParser( description='Estimate areas of furigana in segmented raw manga scan.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument( 'segmentation_file', help= 'Input 3 channel segmentation of input image, with text areas in R channel.' ) parser.add_argument('-o', '--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') #parser.add_argument('-m','--mask', dest='mask', default=None, help='Output (binary) mask for non-graphical regions.') #parser.add_argument('-b','--binary', dest='binary', default=None, help='Binarized version of input file.') parser.add_argument( '-v', '--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument( '--display', help='Display output using OPENCV api and block program exit.', action="store_true") parser.add_argument('-d', '--debug', help='Overlay input image into output.', action="store_true") #parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) #parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=1) arg.value = parser.parse_args() infile = arg.string_value('infile') segmentation_file = arg.string_value('segmentation_file') outfile = arg.string_value('outfile', default_value=infile + '.furigana.png') if not os.path.isfile(infile) or not os.path.isfile(segmentation_file): print( 'Please provide a regular existing input file. Use -h option for help.' ) sys.exit(-1) if arg.boolean_value('verbose'): print('\tProcessing file ' + infile) print('\tWith segmentation file ' + segmentation_file) print('\tAnd generating output ' + outfile) furigana = estimate_furigana_from_files(infile, segmentation_file) imsave(outfile, furigana) if arg.boolean_value('display'): cv2.imshow('Furigana', furigana) if cv2.waitKey(0) == 27: cv2.destroyAllWindows() cv2.destroyAllWindows()
def cleaned2segmented(cleaned, average_size): vertical_smoothing_threshold = defaults.VERTICAL_SMOOTHING_MULTIPLIER * average_size horizontal_smoothing_threshold = defaults.HORIZONTAL_SMOOTHING_MULTIPLIER * average_size (h, w) = cleaned.shape[:2] if arg.boolean_value('verbose'): print 'Applying run length smoothing with vertical threshold ' + str(vertical_smoothing_threshold) \ +' and horizontal threshold ' + str(horizontal_smoothing_threshold) run_length_smoothed = rls.RLSO(cv2.bitwise_not(cleaned), vertical_smoothing_threshold, horizontal_smoothing_threshold) components = cc.get_connected_components(run_length_smoothed) text = np.zeros((h, w), np.uint8) #text_columns = np.zeros((h,w),np.uint8) #text_rows = np.zeros((h,w),np.uint8) for component in components: seg_thresh = arg.integer_value('segment_threshold', default_value=1) (aspect, v_lines, h_lines) = ocr.segment_into_lines(cv2.bitwise_not(cleaned), component, min_segment_threshold=seg_thresh) if len(v_lines) < 2 and len(h_lines) < 2: continue ocr.draw_2d_slices(text, [component], color=255, line_size=-1) #ocr.draw_2d_slices(text_columns,v_lines,color=255,line_size=-1) #ocr.draw_2d_slices(text_rows,h_lines,color=255,line_size=-1) return text
def cleaned2segmented(cleaned, average_size): "cleaned是已经把图像中的字细化了" vertical_smoothing_threshold = defaults.VERTICAL_SMOOTHING_MULTIPLIER*average_size horizontal_smoothing_threshold = defaults.HORIZONTAL_SMOOTHING_MULTIPLIER*average_size (h,w) = cleaned.shape[:2] if arg.boolean_value('verbose'): print 'Applying run length smoothing with vertical threshold ' + str(vertical_smoothing_threshold) \ +' and horizontal threshold ' + str(horizontal_smoothing_threshold) run_length_smoothed = rls.RLSO( cv2.bitwise_not(cleaned), vertical_smoothing_threshold, horizontal_smoothing_threshold) components = cc.get_connected_components(run_length_smoothed) text = np.zeros((h,w),np.uint8) #text_columns = np.zeros((h,w),np.uint8) #text_rows = np.zeros((h,w),np.uint8) for component in components: seg_thresh = arg.integer_value('segment_threshold',default_value=1) (aspect, v_lines, h_lines) = ocr.segment_into_lines(cv2.bitwise_not(cleaned), component,min_segment_threshold=seg_thresh) if len(v_lines)<2 and len(h_lines)<2:continue ocr.draw_2d_slices(text,[component],color=255,line_size=-1) #ocr.draw_2d_slices(text_columns,v_lines,color=255,line_size=-1) #ocr.draw_2d_slices(text_rows,h_lines,color=255,line_size=-1) return text
def main(): parser = arg.parser parser = argparse.ArgumentParser(description='Segment raw Manga scan image.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') #parser.add_argument('-m','--mask', dest='mask', default=None, help='Output (binary) mask for non-graphical regions.') #parser.add_argument('-b','--binary', dest='binary', default=None, help='Binarized version of input file.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument('--display', help='Display output using OPENCV api and block program exit.', action="store_true") parser.add_argument('-d','--debug', help='Overlay input image into output.', action="store_true") parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) parser.add_argument('--binary_threshold', help='Binarization threshold value from 0 to 255.',type=float,default=defaults.BINARY_THRESHOLD) parser.add_argument('--furigana', help='Attempt to suppress furigana characters to improve OCR.', action="store_true") parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=defaults.SEGMENTATION_THRESHOLD) parser.add_argument('--additional_filtering', help='Attempt to filter false text positives by histogram processing.', action="store_true") arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.segmented.png') binary_outfile = infile + '.binary.png' if not os.path.isfile(infile): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) if arg.boolean_value('verbose'): print '\tProcessing file ' + infile print '\tGenerating output ' + outfile segmented = segment_image_file(infile) imsave(outfile,segmented) #cv2.imwrite(outfile,segmented) #if binary is not None: # cv2.imwrite(binary_outfile, binary) if arg.boolean_value('display'): cv2.imshow('Segmented', segmented) #cv2.imshow('Cleaned',cleaned) #if args.mask is not None: # cv2.imshow('Mask',mask) if cv2.waitKey(0) == 27: cv2.destroyAllWindows() cv2.destroyAllWindows()
def main(): parser = arg.parser parser = argparse.ArgumentParser(description='Segment raw Manga scan image.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') #parser.add_argument('-m','--mask', dest='mask', default=None, help='Output (binary) mask for non-graphical regions.') #parser.add_argument('-b','--binary', dest='binary', default=None, help='Binarized version of input file.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument('--display', help='Display output using OPENCV api and block program exit.', action="store_true") parser.add_argument('-d','--debug', help='Overlay input image into output.', action="store_true") parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) parser.add_argument('--binary_threshold', help='Binarization threshold value from 0 to 255.',type=float,default=defaults.BINARY_THRESHOLD) parser.add_argument('--furigana', help='Attempt to suppress furigana characters to improve OCR.', action="store_true") parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=defaults.SEGMENTATION_THRESHOLD) parser.add_argument('--additional_filtering', help='Attempt to filter false text positives by histogram processing.', action="store_true") arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.segmented.png') binary_outfile = infile + '.binary.png' if not os.path.isfile(infile): print('Please provide a regular existing input file. Use -h option for help.') sys.exit(-1) if arg.boolean_value('verbose'): print('\tProcessing file ' + infile) print('\tGenerating output ' + outfile) segmented = segment_image_file(infile) imsave(outfile,segmented) #cv2.imwrite(outfile,segmented) #if binary is not None: # cv2.imwrite(binary_outfile, binary) if arg.boolean_value('display'): cv2.imshow('Segmented', segmented) #cv2.imshow('Cleaned',cleaned) #if args.mask is not None: # cv2.imshow('Mask',mask) if cv2.waitKey(0) == 27: cv2.destroyAllWindows() cv2.destroyAllWindows()
def main(): parser = arg.parser parser = argparse.ArgumentParser(description='Estimate areas of furigana in segmented raw manga scan.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('segmentation_file', help='Input 3 channel segmentation of input image, with text areas in R channel.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') #parser.add_argument('-m','--mask', dest='mask', default=None, help='Output (binary) mask for non-graphical regions.') #parser.add_argument('-b','--binary', dest='binary', default=None, help='Binarized version of input file.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument('--display', help='Display output using OPENCV api and block program exit.', action="store_true") parser.add_argument('-d','--debug', help='Overlay input image into output.', action="store_true") #parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) #parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=1) arg.value = parser.parse_args() infile = arg.string_value('infile') segmentation_file = arg.string_value('segmentation_file') outfile = arg.string_value('outfile',default_value=infile + '.furigana.png') if not os.path.isfile(infile) or not os.path.isfile(segmentation_file): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) if arg.boolean_value('verbose'): print '\tProcessing file ' + infile print '\tWith segmentation file ' + segmentation_file print '\tAnd generating output ' + outfile furigana = estimate_furigana_from_files(infile, segmentation_file) imsave(outfile,furigana) if arg.boolean_value('display'): cv2.imshow('Furigana', furigana) if cv2.waitKey(0) == 27: cv2.destroyAllWindows() cv2.destroyAllWindows()
def main(): parser = arg.parser parser = argparse.ArgumentParser(description='Basic OCR on raw manga scan.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") #parser.add_argument('-d','--debug', help='Overlay input image into output.', action="store_true") parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) parser.add_argument('--binary_threshold', help='Binarization threshold value from 0 to 255.',type=int,default=defaults.BINARY_THRESHOLD) parser.add_argument('--furigana', help='Attempt to suppress furigana characters to improve OCR.', action="store_true") parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=defaults.SEGMENTATION_THRESHOLD) arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.html') if not os.path.isfile(infile): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) if arg.boolean_value('verbose'): print '\tProcessing file ' + infile print '\tGenerating output ' + outfile img = cv2.imread(infile) gray = clean.grayscale(img) binary = clean.binarize(gray) segmented = segmentation.segment_image_file(infile) components = cc.get_connected_components(segmented) #perhaps do more strict filtering of connected components because sections of characters #will not be dripped from run length smoothed areas? Yes. Results quite good. #filtered = cc.filter_by_size(img,components,average_size*100,average_size*1) blurbs = ocr_on_bounding_boxes(binary, components) for blurb in blurbs: print str(blurb.x)+','+str(blurb.y)+' '+str(blurb.w)+'x'+str(blurb.h)+' '+ str(blurb.confidence)+'% :'+ blurb.text
def main(): parser = arg.parser parser = argparse.ArgumentParser(description='Basic OCR on raw manga scan.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") #parser.add_argument('-d','--debug', help='Overlay input image into output.', action="store_true") parser.add_argument('--sigma', help='Std Dev of gaussian preprocesing filter.',type=float,default=None) parser.add_argument('--binary_threshold', help='Binarization threshold value from 0 to 255.',type=int,default=defaults.BINARY_THRESHOLD) parser.add_argument('--furigana', help='Attempt to suppress furigana characters to improve OCR.', action="store_true") parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=defaults.SEGMENTATION_THRESHOLD) arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.html') if not os.path.isfile(infile): print ('Please provide a regular existing input file. Use -h option for help.') sys.exit(-1) if arg.boolean_value('verbose'): print ('\tProcessing file ' + infile) print ('\tGenerating output ' + outfile) img = cv2.imread(infile) gray = clean.grayscale(img) binary = clean.binarize(gray) segmented = segmentation.segment_image_file(infile) components = cc.get_connected_components(segmented) #perhaps do more strict filtering of connected components because sections of characters #will not be dripped from run length smoothed areas? Yes. Results quite good. #filtered = cc.filter_by_size(img,components,average_size*100,average_size*1) blurbs = ocr_on_bounding_boxes(binary, components) for blurb in blurbs: print (str(blurb.x)+','+str(blurb.y)+' '+str(blurb.w)+'x'+str(blurb.h)+' '+ str(blurb.confidence)+'% :'+ blurb.text)
def text_like_histogram(img, area, average_size): if not arg.boolean_value('additional_filtering'): return True (x, y, w, h) = dimensions_2d_slice(area) x_subimage = np.copy(img) x_histogram = np.zeros(w,int) y_subimage = np.copy(img) y_histogram = np.zeros(h,int) aoi = img[area] ccs = cc.get_connected_components(aoi) if( len(ccs) < 2): return False #avg = average_size avg = cc.average_size(aoi) mean_width = cc.mean_width(aoi) mean_height = cc.mean_height(aoi) if arg.boolean_value('verbose'): print 'average size = ' + str(avg) + ' mean width = ' + str(mean_width) + ' mean height = ' + str(mean_height) if math.isnan(avg) or avg==0: if arg.boolean_value('verbose'): print 'Rejecting area since average size is NaN' #return False #in a text area, the average size of a blob (cc) will reflect #that of the used characters/typeface. Thus if there simply aren't #enough pixels per character, we can drop this as a text candidate #note the following is failing in odd situations, probably due to incorrect #calculation of 'avg size' #TODO: replace testing against "average size" with testing against #hard thresholds for connected component width and height. i.e. #if they're all thin small ccs, we can drop this area #if avg < defaults.MINIMUM_TEXT_SIZE_THRESHOLD: if mean_width < defaults.MINIMUM_TEXT_SIZE_THRESHOLD or \ mean_height < defaults.MINIMUM_TEXT_SIZE_THRESHOLD: if arg.boolean_value('verbose'): print 'Rejecting area since average width or height is less than threshold.' return False #check the basic aspect ratio of the ccs if mean_width/mean_height < 0.5 or mean_width/mean_height > 2: if arg.boolean_value('verbose'): print 'Rejecting area since mean cc aspect ratio not textlike.' return False width_multiplier = float(avg) height_multiplier = float(avg) #gaussian filter the subimages in x,y directions to emphasise peaks and troughs x_subimage = scipy.ndimage.filters.gaussian_filter(x_subimage,(0.01*width_multiplier,0)) y_subimage = scipy.ndimage.filters.gaussian_filter(y_subimage,(0,0.01*height_multiplier)) #put together the histogram for black pixels over the x directon (along columns) of the component for i,col in enumerate(range(x,x+w)): black_pixel_count = np.count_nonzero(y_subimage[y:y+h,col]) x_histogram[i] = black_pixel_count #and along the y direction (along rows) for i,row in enumerate(range(y,y+h)): black_pixel_count = np.count_nonzero(x_subimage[row,x:x+w]) y_histogram[i] = black_pixel_count h_white_runs = get_white_runs(x_histogram) num_h_white_runs = len(h_white_runs) h_black_runs = get_black_runs(x_histogram) num_h_black_runs = len(h_black_runs) (h_spacing_mean, h_spacing_variance) = slicing_list_stats(h_white_runs) (h_character_mean, h_character_variance) = slicing_list_stats(h_black_runs) v_white_runs = get_white_runs(y_histogram) num_v_white_runs = len(v_white_runs) v_black_runs = get_black_runs(y_histogram) num_v_black_runs = len(v_black_runs) (v_spacing_mean, v_spacing_variance) = slicing_list_stats(v_white_runs) (v_character_mean, v_character_variance) = slicing_list_stats(v_black_runs) if arg.boolean_value('verbose'): print 'x ' + str(x) + ' y ' +str(y) + ' w ' + str(w) + ' h ' + str(h) print 'white runs ' + str(len(h_white_runs)) + ' ' + str(len(v_white_runs)) print 'white runs mean ' + str(h_spacing_mean) + ' ' + str(v_spacing_mean) print 'white runs std ' + str(h_spacing_variance) + ' ' + str(v_spacing_variance) print 'black runs ' + str(len(h_black_runs)) + ' ' + str(len(v_black_runs)) print 'black runs mean ' + str(h_character_mean) + ' ' + str(v_character_mean) print 'black runs std ' + str(h_character_variance) + ' ' + str(v_character_variance) if num_h_white_runs < 2 and num_v_white_runs < 2: if arg.boolean_value('verbose'): print 'Rejecting area since not sufficient amount post filtering whitespace.' return False if v_spacing_variance > defaults.MAXIMUM_VERTICAL_SPACE_VARIANCE: if arg.boolean_value('verbose'): print 'Rejecting area since vertical inter-character space variance too high.' return False if v_character_mean < avg*0.5 or v_character_mean > avg*2.0: pass #return False if h_character_mean < avg*0.5 or h_character_mean > avg*2.0: pass #return False return True
def segment_image(img, max_scale=defaults.CC_SCALE_MAX, min_scale=defaults.CC_SCALE_MIN): (h, w) = img.shape[:2] if arg.boolean_value('verbose'): print 'Segmenting ' + str(h) + 'x' + str(w) + ' image.' #create gaussian filtered and unfiltered binary images binary_threshold = arg.integer_value( 'binary_threshold', default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'binarizing images with threshold value of ' + str( binary_threshold) binary = clean.binarize(img, threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print 'average cc size for binaryized grayscale image is ' + str( binary_average_size) ''' The necessary sigma needed for Gaussian filtering (to remove screentones and other noise) seems to be a function of the resolution the manga was scanned at (or original page size, I'm not sure). Assuming 'normal' page size for a phonebook style Manga is 17.5cmx11.5cm (6.8x4.5in). A scan of 300dpi will result in an image about 1900x1350, which requires a sigma of 1.5 to 1.8. I'm encountering many smaller images that may be nonstandard scanning dpi values or just smaller magazines. Haven't found hard info on this yet. They require sigma values of about 0.5 to 0.7. I'll therefore (for now) just calculate required (nonspecified) sigma as a linear function of vertical image resolution. ''' sigma = (0.8 / 676.0) * float(h) - 0.9 sigma = arg.float_value('sigma', default_value=sigma) if arg.boolean_value('verbose'): print 'Applying Gaussian filter with sigma (std dev) of ' + str(sigma) gaussian_filtered = scipy.ndimage.gaussian_filter(img, sigma=sigma) gaussian_binary = clean.binarize(gaussian_filtered, threshold=binary_threshold) #Draw out statistics on average connected component size in the rescaled, binary image average_size = cc.average_size(gaussian_binary) if arg.boolean_value('verbose'): print 'Binarized Gaussian filtered image average cc size: ' + str( average_size) max_size = average_size * max_scale min_size = average_size * min_scale #primary mask is connected components filtered by size mask = cc.form_mask(gaussian_binary, max_size, min_size) #secondary mask is formed from canny edges canny_mask = clean.form_canny_mask(gaussian_filtered, mask=mask) #final mask is size filtered connected components on canny mask final_mask = cc.form_mask(canny_mask, max_size, min_size) #apply mask and return images cleaned = cv2.bitwise_not(final_mask * binary) text_only = cleaned2segmented(cleaned, average_size) #if desired, suppress furigana characters (which interfere with OCR) suppress_furigana = arg.boolean_value('furigana') if suppress_furigana: if arg.boolean_value('verbose'): print 'Attempting to suppress furigana characters which interfere with OCR.' furigana_mask = furigana.estimate_furigana(cleaned, text_only) furigana_mask = np.array(furigana_mask == 0, 'B') cleaned = cv2.bitwise_not(cleaned) * furigana_mask cleaned = cv2.bitwise_not(cleaned) text_only = cleaned2segmented(cleaned, average_size) (text_like_areas, nontext_like_areas) = filter_text_like_areas(img, segmentation=text_only, average_size=average_size) if arg.boolean_value('verbose'): print '**********there are ' + str( len(text_like_areas)) + ' text like areas total.' text_only = np.zeros(img.shape) cc.draw_bounding_boxes(text_only, text_like_areas, color=(255), line_size=-1) if arg.boolean_value('debug'): text_only = 0.5 * text_only + 0.5 * img #text_rows = 0.5*text_rows+0.5*gray #text_colums = 0.5*text_columns+0.5*gray #text_only = filter_text_like_areas(img, segmentation=text_only, average_size=average_size) segmented_image = np.zeros((h, w, 3), np.uint8) segmented_image[:, :, 0] = img segmented_image[:, :, 1] = text_only segmented_image[:, :, 2] = text_only return segmented_image
def text_like_histogram(img, area, average_size): if not arg.boolean_value('additional_filtering'): return True (x, y, w, h) = dimensions_2d_slice(area) x_subimage = np.copy(img) x_histogram = np.zeros(w, int) y_subimage = np.copy(img) y_histogram = np.zeros(h, int) aoi = img[area] ccs = cc.get_connected_components(aoi) if (len(ccs) < 2): return False #avg = average_size avg = cc.average_size(aoi) mean_width = cc.mean_width(aoi) mean_height = cc.mean_height(aoi) if arg.boolean_value('verbose'): print 'average size = ' + str(avg) + ' mean width = ' + str( mean_width) + ' mean height = ' + str(mean_height) if math.isnan(avg) or avg == 0: if arg.boolean_value('verbose'): print 'Rejecting area since average size is NaN' #return False #in a text area, the average size of a blob (cc) will reflect #that of the used characters/typeface. Thus if there simply aren't #enough pixels per character, we can drop this as a text candidate #note the following is failing in odd situations, probably due to incorrect #calculation of 'avg size' #TODO: replace testing against "average size" with testing against #hard thresholds for connected component width and height. i.e. #if they're all thin small ccs, we can drop this area #if avg < defaults.MINIMUM_TEXT_SIZE_THRESHOLD: if mean_width < defaults.MINIMUM_TEXT_SIZE_THRESHOLD or \ mean_height < defaults.MINIMUM_TEXT_SIZE_THRESHOLD: if arg.boolean_value('verbose'): print 'Rejecting area since average width or height is less than threshold.' return False #check the basic aspect ratio of the ccs if mean_width / mean_height < 0.5 or mean_width / mean_height > 2: if arg.boolean_value('verbose'): print 'Rejecting area since mean cc aspect ratio not textlike.' return False width_multiplier = float(avg) height_multiplier = float(avg) #gaussian filter the subimages in x,y directions to emphasise peaks and troughs x_subimage = scipy.ndimage.filters.gaussian_filter( x_subimage, (0.01 * width_multiplier, 0)) y_subimage = scipy.ndimage.filters.gaussian_filter( y_subimage, (0, 0.01 * height_multiplier)) #put together the histogram for black pixels over the x directon (along columns) of the component for i, col in enumerate(range(x, x + w)): black_pixel_count = np.count_nonzero(y_subimage[y:y + h, col]) x_histogram[i] = black_pixel_count #and along the y direction (along rows) for i, row in enumerate(range(y, y + h)): black_pixel_count = np.count_nonzero(x_subimage[row, x:x + w]) y_histogram[i] = black_pixel_count h_white_runs = get_white_runs(x_histogram) num_h_white_runs = len(h_white_runs) h_black_runs = get_black_runs(x_histogram) num_h_black_runs = len(h_black_runs) (h_spacing_mean, h_spacing_variance) = slicing_list_stats(h_white_runs) (h_character_mean, h_character_variance) = slicing_list_stats(h_black_runs) v_white_runs = get_white_runs(y_histogram) num_v_white_runs = len(v_white_runs) v_black_runs = get_black_runs(y_histogram) num_v_black_runs = len(v_black_runs) (v_spacing_mean, v_spacing_variance) = slicing_list_stats(v_white_runs) (v_character_mean, v_character_variance) = slicing_list_stats(v_black_runs) if arg.boolean_value('verbose'): print 'x ' + str(x) + ' y ' + str(y) + ' w ' + str(w) + ' h ' + str(h) print 'white runs ' + str(len(h_white_runs)) + ' ' + str( len(v_white_runs)) print 'white runs mean ' + str(h_spacing_mean) + ' ' + str( v_spacing_mean) print 'white runs std ' + str(h_spacing_variance) + ' ' + str( v_spacing_variance) print 'black runs ' + str(len(h_black_runs)) + ' ' + str( len(v_black_runs)) print 'black runs mean ' + str(h_character_mean) + ' ' + str( v_character_mean) print 'black runs std ' + str(h_character_variance) + ' ' + str( v_character_variance) if num_h_white_runs < 2 and num_v_white_runs < 2: if arg.boolean_value('verbose'): print 'Rejecting area since not sufficient amount post filtering whitespace.' return False if v_spacing_variance > defaults.MAXIMUM_VERTICAL_SPACE_VARIANCE: if arg.boolean_value('verbose'): print 'Rejecting area since vertical inter-character space variance too high.' return False if v_character_mean < avg * 0.5 or v_character_mean > avg * 2.0: pass #return False if h_character_mean < avg * 0.5 or h_character_mean > avg * 2.0: pass #return False return True
def segment_image(img, max_scale=defaults.CC_SCALE_MAX, min_scale=defaults.CC_SCALE_MIN): (h,w)=img.shape[:2] if arg.boolean_value('verbose'): print 'Segmenting ' + str(h) + 'x' + str(w) + ' image.' #create gaussian filtered and unfiltered binary images binary_threshold = arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'binarizing images with threshold value of ' + str(binary_threshold) binary = clean.binarize(img,threshold=binary_threshold) binary_average_size = cc.average_size(binary) if arg.boolean_value('verbose'): print 'average cc size for binaryized grayscale image is ' + str(binary_average_size) ''' The necessary sigma needed for Gaussian filtering (to remove screentones and other noise) seems to be a function of the resolution the manga was scanned at (or original page size, I'm not sure). Assuming 'normal' page size for a phonebook style Manga is 17.5cmx11.5cm (6.8x4.5in). A scan of 300dpi will result in an image about 1900x1350, which requires a sigma of 1.5 to 1.8. I'm encountering many smaller images that may be nonstandard scanning dpi values or just smaller magazines. Haven't found hard info on this yet. They require sigma values of about 0.5 to 0.7. I'll therefore (for now) just calculate required (nonspecified) sigma as a linear function of vertical image resolution. ''' sigma = (0.8/676.0)*float(h)-0.9 sigma = arg.float_value('sigma',default_value=sigma) if arg.boolean_value('verbose'): print 'Applying Gaussian filter with sigma (std dev) of ' + str(sigma) gaussian_filtered = scipy.ndimage.gaussian_filter(img, sigma=sigma) gaussian_binary = clean.binarize(gaussian_filtered,threshold=binary_threshold) #Draw out statistics on average connected component size in the rescaled, binary image average_size = cc.average_size(gaussian_binary) if arg.boolean_value('verbose'): print 'Binarized Gaussian filtered image average cc size: ' + str(average_size) max_size = average_size*max_scale min_size = average_size*min_scale #primary mask is connected components filtered by size mask = cc.form_mask(gaussian_binary, max_size, min_size) #secondary mask is formed from canny edges canny_mask = clean.form_canny_mask(gaussian_filtered, mask=mask) #final mask is size filtered connected components on canny mask final_mask = cc.form_mask(canny_mask, max_size, min_size) #apply mask and return images cleaned = cv2.bitwise_not(final_mask * binary) text_only = cleaned2segmented(cleaned, average_size) #if desired, suppress furigana characters (which interfere with OCR) suppress_furigana = arg.boolean_value('furigana') if suppress_furigana: if arg.boolean_value('verbose'): print 'Attempting to suppress furigana characters which interfere with OCR.' furigana_mask = furigana.estimate_furigana(cleaned, text_only) furigana_mask = np.array(furigana_mask==0,'B') cleaned = cv2.bitwise_not(cleaned)*furigana_mask cleaned = cv2.bitwise_not(cleaned) text_only = cleaned2segmented(cleaned, average_size) (text_like_areas, nontext_like_areas) = filter_text_like_areas(img, segmentation=text_only, average_size=average_size) if arg.boolean_value('verbose'): print '**********there are ' + str(len(text_like_areas)) + ' text like areas total.' text_only = np.zeros(img.shape) cc.draw_bounding_boxes(text_only, text_like_areas,color=(255),line_size=-1) if arg.boolean_value('debug'): text_only = 0.5*text_only + 0.5*img #text_rows = 0.5*text_rows+0.5*gray #text_colums = 0.5*text_columns+0.5*gray #text_only = filter_text_like_areas(img, segmentation=text_only, average_size=average_size) segmented_image = np.zeros((h,w,3), np.uint8) segmented_image[:,:,0] = img segmented_image[:,:,1] = text_only segmented_image[:,:,2] = text_only return segmented_image
import scipy.ndimage import glob if __name__ == '__main__': AbsPath = 'C:/Users/blue.i/Desktop/New folder/MangaTextDetection-master/' for filename in glob.glob(os.path.join(AbsPath, '*.jpg')): infile = "" + filename print(infile) outfile = infile + '.text_areas.jpg' img = cv2.imread(infile) gray = clean.grayscale(img) binary_threshold = arg.integer_value( 'binary_threshold', default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print('Binarizing with threshold value of ' + str(binary_threshold)) inv_binary = cv2.bitwise_not( clean.binarize(gray, threshold=binary_threshold)) binary = clean.binarize(gray, threshold=binary_threshold) segmented_image = seg.segment_image(gray) segmented_image = segmented_image[:, :, 2] myImage = np.copy(segmented_image) image, contours, hierarchy = cv2.findContours(myImage, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: if cv2.contourArea(cnt) > 4000: cv2.drawContours(img, [cnt], 0, (255, 255, 255), cv2.FILLED) imsave(outfile, img)
'-v', '--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument( '--display', help='Display output using OPENCV api and block program exit.', action="store_true") arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.cleaned.png') binary_outfile = arg.string_value('binary', default_value=infile + '.binary.png') mask = arg.boolean_value('mask') if not os.path.isfile(infile): print( 'Please provide a regular existing input file. Use -h option for help.' ) sys.exit(-1) if arg.boolean_value('verbose'): print('\tProcessing file ' + infile) print('\tGenerating output ' + outfile) (binary, mask, cleaned) = clean_image_file(infile) cv2.imwrite(outfile, cleaned) if binary is not None:
if __name__ == '__main__': parser = arg.parser parser = argparse.ArgumentParser(description='Clean raw Manga scan image.') parser.add_argument('infile', help='Input (color) raw Manga scan image to clean.') parser.add_argument('-o','--output', dest='outfile', help='Output (color) cleaned raw manga scan image.') parser.add_argument('-m','--mask', dest='mask', default=None, help='Output (binary) mask for non-graphical regions.') parser.add_argument('-b','--binary', dest='binary', default=None, help='Binarized version of input file.') parser.add_argument('-v','--verbose', help='Verbose operation. Print status messages during processing', action="store_true") parser.add_argument('--display', help='Display output using OPENCV api and block program exit.', action="store_true") arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile',default_value=infile + '.cleaned.png') binary_outfile = arg.string_value('binary',default_value=infile + '.binary.png') mask = arg.boolean_value('mask') if not os.path.isfile(infile): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) if arg.boolean_value('verbose'): print '\tProcessing file ' + infile print '\tGenerating output ' + outfile (binary,mask,cleaned) = clean_image_file(infile) cv2.imwrite(outfile,cleaned) if binary is not None: cv2.imwrite(binary_outfile, binary)
parser.add_argument('--binary_threshold', help='Binarization threshold value from 0 to 255.',type=int,default=defaults.BINARY_THRESHOLD) #parser.add_argument('--segment_threshold', help='Threshold for nonzero pixels to separete vert/horiz text lines.',type=int,default=1) parser.add_argument('--additional_filtering', help='Attempt to filter false text positives by histogram processing.', action="store_true") arg.value = parser.parse_args() infile = arg.string_value('infile') outfile = arg.string_value('outfile',default_value=infile + '.text_areas.png') if not os.path.isfile(infile): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) img = cv2.imread(infile) gray = clean.grayscale(img) binary_threshold=arg.integer_value('binary_threshold',default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'Binarizing with threshold value of ' + str(binary_threshold) inv_binary = cv2.bitwise_not(clean.binarize(gray, threshold=binary_threshold)) binary = clean.binarize(gray, threshold=binary_threshold) segmented_image = seg.segment_image(gray) segmented_image = segmented_image[:,:,2] components = cc.get_connected_components(segmented_image) cc.draw_bounding_boxes(img,components,color=(255,0,0),line_size=2) imsave(outfile, img) if arg.boolean_value('display'): cv2.imshow('segmented_image',segmented_image) if cv2.waitKey(0) == 27:
infile = arg.string_value('infile') outfile = arg.string_value('outfile', default_value=infile + '.text_areas.png') if not os.path.isfile(infile): print( 'Please provide a regular existing input file. Use -h option for help.' ) sys.exit(-1) img = cv2.imread(infile) gray = clean.grayscale(img) binary_threshold = arg.integer_value( 'binary_threshold', default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print('Binarizing with threshold value of ' + str(binary_threshold)) inv_binary = cv2.bitwise_not( clean.binarize(gray, threshold=binary_threshold)) binary = clean.binarize(gray, threshold=binary_threshold) segmented_image = seg.segment_image(gray) segmented_image = segmented_image[:, :, 2] components = cc.get_connected_components(segmented_image) cc.draw_bounding_boxes(img, components, color=(255, 0, 0), line_size=2) imsave(outfile, img) if arg.boolean_value('display'): cv2.imshow('segmented_image', segmented_image)
if not os.path.isfile(infile): print 'Please provide a regular existing input file. Use -h option for help.' sys.exit(-1) img = cv2.imread(infile) cv2.imshow('srcimg', img) gray = clean.grayscale(img) binary_threshold = arg.integer_value('binary_threshold', default_value=defaults.BINARY_THRESHOLD) if arg.boolean_value('verbose'): print 'Binarizing with threshold value of ' + str(binary_threshold) inv_binary = cv2.bitwise_not(clean.binarize(gray, threshold=binary_threshold)) #cv2.imshow('inv_binary', inv_binary) binary = clean.binarize(gray, threshold=binary_threshold) #cv2.imshow('binary', binary) segmented_image = seg.segment_image(gray) cv2.imshow('segmented_image', segmented_image)