comparison Preprocessing/preomr.py @ 2:46fb79167a61 tip

Main Code
author Victor Padilla <victor.padilla.mc@gmail.com>
date Mon, 04 May 2015 22:56:18 +0200
parents
children
comparison
equal deleted inserted replaced
1:0f7f611deca4 2:46fb79167a61
1 import sys
2 import cv2
3 import numpy as np
4 import math
5 import copy
6
7 from gamera.core import *
8 from gamera.toolkits.musicstaves import musicstaves_rl_roach_tatem
9 from gamera.toolkits.musicstaves import musicstaves_rl_fujinaga
10 from gamera.toolkits.musicstaves import stafffinder_miyao
11 from gamera.toolkits.musicstaves import stafffinder_dalitz
12 from gamera.toolkits.musicstaves import stafffinder_projections
13 from gamera.plugins import numpy_io
14 init_gamera()
15
16 #import ossiafinder_dalitz
17
18 def intersect(r1,r2):
19 """Returns the intersection of two rectangles"""
20 x1 = max(r1['x'], r2['x'])
21 y1 = max(r1['y'], r2['y'])
22 x2 = min(r1['x'] + r1['width'], r2['x'] + r2['width'])
23 y2 = min(r1['y'] + r1['height'], r2['y'] + r2['height'])
24 result = {"x": x1, "y": y1, "width": x2 - x1, "height": y2-y1}
25 result['area'] = result['width'] * result['height']
26 return(result)
27
28 def ydist(r1,r2):
29 """distance on y-axis between two non-interecting rectangles"""
30 top1 = r1['y']
31 bottom1 = r1['y'] + r1['height']
32
33 top2 = r2['y']
34 bottom2 = r2['y'] + r2['height']
35 return(min(abs(top1-bottom2), abs(top2-bottom1)))
36
37
38 def show(img, factor=0.5):
39 """ show an image until the escape key is pressed
40 :param factor: scale factor (default 0.5, half size)
41 """
42 if factor != 1.0:
43 img = cv2.resize(img, (0,0), fx=factor, fy=factor)
44
45 cv2.imwrite('show.png',img)
46 # while(1):
47 # k = cv2.waitKey(0)
48 # if k==27: # Esc key to quit
49 # cv2.destroyAllWindows()
50 # exit()
51 # if k==32: # Space to stop
52 # cv2.destroyAllWindows()
53 # break
54
55
56 def max_staff_height(blob):
57 result = 0
58 for staff in blob['staves']:
59 top = staff[0].y_list[0]
60 bottom = staff[-1].y_list[0]
61 result = max(result, bottom-top)
62 return(result)
63
64
65 def deskew(img):
66 """Deskews the given image based on lines detected with opencv's
67 HoughLines function."""
68 print "Deskewing."
69 imgHeight, imgWidth, imgDepth = img.shape
70 img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
71 img_edges = cv2.Canny(img_gray,50,150,apertureSize = 3)
72 minLineLength = int(imgWidth*0.5)
73 houghThresh = int(imgWidth*0.15)
74 maxLineGap = 10
75 #lines = cv2.HoughLinesP(img_edges,1,np.pi/(180*1),houghThresh,minLineLength,maxLineGap)
76 lines = cv2.HoughLines(img_edges,1,np.pi/(180*3),houghThresh)
77
78 angles = []
79 for rho,theta in lines[0]:
80 angles.append((theta - (np.pi / 2)))
81
82 a = np.cos(theta)
83 b = np.sin(theta)
84 x0 = a*rho
85 y0 = b*rho
86 x1 = int(x0 + imgWidth*(-b))
87 y1 = int(y0 + imgWidth*(a))
88 x2 = int(x0 - imgWidth*(-b))
89 y2 = int(y0 - imgWidth*(a))
90 #cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
91
92 middle = np.median(angles)
93 middle_deg = middle * (180/np.pi)
94
95 rotation = cv2.getRotationMatrix2D((imgWidth/2,imgHeight/2),middle_deg,1.0)
96
97 # rotate while inverted. the background is filled with zeros
98 # (black), this inversion means that ends up white
99 deskewed = cv2.bitwise_not(cv2.warpAffine(cv2.bitwise_not(img),
100 rotation,
101 (imgWidth,imgHeight))
102 )
103 return(deskewed)
104
105 class PreOMR(object):
106 stavelineWidthThresh = 0.5
107
108 def __init__(self, infile, deskew=False):
109 self.debug = True
110 self.infile = infile
111 self.img = cv2.imread(self.infile)
112 if deskew:
113 self.img = deskew(self.img)
114 self.original = self.img
115 self.imgHeight, self.imgWidth, self.imgDepth = self.img.shape
116 self.img_gray = cv2.cvtColor(self.img,cv2.COLOR_BGR2GRAY)
117 if self.debug:
118 self.debug_img = self.img.copy()
119 ret2,self.img_binary = cv2.threshold(self.img_gray,
120 0,255,cv2.
121 THRESH_BINARY+cv2.
122 THRESH_OTSU)
123
124 def staffline_removal(self):
125 gamera_img = numpy_io.from_numpy(self.img)
126 #self.save('tmp.png')
127 #gamera_img = load_image('tmp.png')
128
129 #ms = musicstaves_rl_roach_tatem.MusicStaves_rl_roach_tatem(gamera_img)
130 ms = musicstaves_rl_fujinaga.MusicStaves_rl_fujinaga(gamera_img)
131 cv2.imwrite('tmp.png', self.img)
132 ms.remove_staves(crossing_symbols = 'bars')
133 ms.image.save_PNG("tmpb.png")
134 staffless = cv2.imread("tmp.png", cv2.CV_LOAD_IMAGE_GRAYSCALE)
135 return(staffless)
136
137 def find_staves(self, img):
138 gamera_img = numpy_io.from_numpy(img)
139 #sf = stafffinder_projections.StaffFinder_projections(gamera_img)
140 #sf.find_staves(follow_wobble=True,preprocessing=0)
141 #sf = stafffinder_dalitz.StaffFinder_dalitz(gamera_img)
142 sf = stafffinder_miyao.StaffFinder_miyao(gamera_img)
143 sf.find_staves()
144 #sf.find_staves(debug=2)
145
146 staves = sf.get_skeleton()
147 # if self.debug:
148 # for i, staff in enumerate(staves):
149 # print "Staff %d has %d staves:" % (i+1, len(staff))
150 # for j, line in enumerate(staff):
151 # print(" %d. line at (%d,%d)" % (j+1,line.left_x,line.y_list[0]))
152 return(staves)
153
154 def find_blobs(self, img_binary):
155 """Find blobs in the given image, returned as a list of associative
156 lists containing various cheap metrics for each blob."""
157
158 blobs = []
159 img_inverted = cv2.bitwise_not(img_binary)
160 contours, hierarchy = cv2.findContours(img_inverted,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
161 for (i, c) in enumerate(contours):
162 blob = {}
163 blobs.append(blob)
164
165 blob['area'] = cv2.contourArea(c)
166
167 m = cv2.moments(c)
168 if m['m00'] == 0: # When would this be true?
169 blob['x'] = 0
170 blob['y'] = 0
171 else:
172 blob['x'] = m['m10'] / m['m00']
173 blob['y'] = m['m01'] / m['m00']
174
175 blob['contour'] = c
176
177 rect = cv2.boundingRect(c)
178 blob['rect'] = {'x': rect[0],
179 'y': rect[1],
180 'width': rect[2],
181 'height': rect[3]
182 }
183 blob['boundingRect'] = rect
184 blob['hull'] = hull = cv2.convexHull(c)
185 blob['hull_area'] = abs(cv2.contourArea(hull))
186 blob['system'] = False
187 blob['parent'] = None
188 #blob['perimeter'] = perimeter = cv2.arcLength(c, True)
189 #blob['roundness'] = (perimeter * 0.282) / math.sqrt(area)
190 #(centre, axes, orientation) = cv2.fitEllipse(c)
191 #blob['orientation'] = orientation / 180
192 #print "orientation: %f" % orientation
193 #blob['aspect'] = float(rect[1]) / float(rect[3])
194
195 return blobs
196
197 def find_bars(self, system):
198 staffless = self.staffline_removal()
199 blobs = self.blobs
200
201 """Finds the barlines in the system, given a binary image, a hash of
202 info about the system, and blobs detected in the image.
203
204 """
205 img = system['image']
206
207 for staff in system['staves']:
208 min_x = 0
209 max_x = self.imgWidth
210
211 for line in staff:
212 min_x = max(min_x, line.left_x)
213 max_x = min(max_x, line.left_x + len(line.y_list))
214
215 if self.debug:
216 for (i,y) in enumerate(line.y_list):
217 x = line.left_x + i
218 cv2.line(self.debug_img,(x,y),(x,y),(0,255,0),3)
219
220 # cv2.line(img,(0,int(start)),(imgWidth,int(start)),(0,255,255),3)
221 # cv2.line(img,(0,int(stop)),(imgWidth,int(stop)),(0,255,255),3)
222 # cv2.line(img,(0,int(first_staveline)),(imgWidth,int(first_staveline)),(255,255,0),3)
223 # cv2.line(img,(0,int(last_staveline)),(imgWidth,int(last_staveline)),(255,255,0),3)
224
225 # assuming single staff for now..
226 barlines = [0]
227 system['barlines'] = barlines
228
229 x_projection = []
230
231 for x in range(min_x, max_x):
232 first_staveline = staff[0].y_list[x - staff[0].left_x]
233 last_staveline = staff[-1].y_list[x - staff[-1].left_x]
234
235 #print("Stavelines: first %d last %d" % (first_staveline, last_staveline))
236 stave_height = last_staveline - first_staveline
237
238 # mean distance between stavelines
239 avg_inter = float(stave_height) / float(len(staff)-1)
240 #print("avg_inter: %f" % (avg_inter,))
241
242 # where to look a bit above and below the stave for whitespace
243 # above a barline
244 gap = avg_inter / 2.0
245 start = first_staveline - gap
246 stop = last_staveline + gap
247
248 # above stave, stave and below stave
249 top = float(gap -
250 cv2.countNonZero(staffless[start:first_staveline,
251 x:x+1])) / float(gap)
252 mid = float(stave_height -
253 cv2.countNonZero(staffless[first_staveline:last_staveline,
254 x:x+1])
255 ) / float(stave_height)
256 bot = float(gap -
257 cv2.countNonZero(staffless[last_staveline:stop, x:x+1])
258 ) / float(gap)
259 x_projection.append((top,mid,bot))
260
261 barline_start = -1
262 gap_dist = avg_inter/4
263 gap_min = (avg_inter/float(stave_height)) * 0.3
264 gap_tolerance = int(avg_inter/10)
265
266 margin = int(avg_inter*2)
267
268 for x in range(min_x+margin, max_x-margin):
269 (top,mid,bot) = x_projection[x - min_x]
270 #if self.debug:
271 #cv2.line(system['image'],(x,first_staveline),(x,int(first_staveline+((last_staveline-first_staveline)*mid))),(255,255,0),1)
272
273 # found start of barline candidate
274 if top < 0.6 and bot < 0.6 and mid > 0.95:
275 if barline_start < 0:
276 barline_start = x
277 else:
278 if barline_start > 0:
279 # check there is nothing either side of 'barline'
280 barline_stop = x-1
281 barline_mid = barline_stop - ((barline_stop - barline_start)/2)
282 #print("barline start %d stop %d mid %d" % (barline_start, barline_stop, barline_mid))
283 left = int(max(0,barline_start-gap_dist))
284 right = int(min(system['width']-1,(x-1)+gap_dist))
285
286 total = 0
287 for i in range(left-gap_tolerance, left+gap_tolerance+1):
288 total = total + x_projection[i-min_x][1]
289 left_avg = total / ((gap_tolerance*2)+1)
290
291 total = 0
292 for i in range(right-gap_tolerance, right+gap_tolerance+1):
293 total = total + x_projection[i-min_x][1]
294 right_avg = total / ((gap_tolerance*2)+1)
295
296 cv2.line(img,(left,first_staveline),(left,last_staveline),(255,0,255),1)
297 cv2.line(img,(right,first_staveline),(right,last_staveline),(255,0,255),1)
298
299 if (left_avg <= gap_min and right_avg <= gap_min):
300 #print("success: left_avg %f right_avg %f" % (left_avg, right_avg))
301 cv2.line(img,(barline_mid,first_staveline),(barline_mid,last_staveline),(255,0,0),3)
302 barlines.append(barline_mid)
303 else:
304 #print("fail: left_avg %f right_avg %f" % (left_avg, right_avg))
305 cv2.line(img,(barline_mid,first_staveline),(barline_mid,last_staveline),(0,255,0),3)
306 #show(img)
307 barline_start = -1
308 (x1, y1, x2, y2) = system['location']
309 #show(system['image'][y1:y2, x1:x2])
310
311 def extract_bars(self, system, blobs):
312 """Given information about a system (including identified barlines),
313 and all the blobs on a page returns a list of bars in the system,
314 each an associative array containing image and location.
315
316 """
317
318 img = system['image']
319
320 barlines = system['barlines']
321
322 result = []
323
324 for i in range(0,len(barlines)):
325 barstart = barlines[i]
326 if i == (len(barlines)-1):
327 barstop = system['width']
328 else:
329 barstop = barlines[i+1]
330 #print("barstart %d barstop %d" % (barstart, barstop))
331 contours = [system['contour']]
332 x1 = barstart
333 y1 = system['location'][1]
334 x2 = barstop
335 y2 = system['location'][3]
336 h = y2 - y1
337 w = x2 - x1
338 #print("height %d width %d" % (h, w))
339 for blob in blobs:
340 if blob['parent'] == system:
341 if blob['rect']['x'] >= barstart and blob['rect']['x'] + blob['rect']['width'] <= barstop:
342 contours.append(blob['contour'])
343
344 mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
345
346 cv2.drawContours(mask, contours, -1, 255, -1);
347
348 inv = cv2.bitwise_not(img)
349 dest = cv2.bitwise_and(inv,inv,mask = mask)
350 dest = cv2.bitwise_not(dest)
351 img_bar = dest[y1:y2, x1:x2]
352 bar = {'image': img_bar,
353 'page': dest,
354 'location': [x1,y1,x2,y2]
355 }
356 result.append(bar)
357 #show(img_bar)
358 return(result)
359
360
361
362 def find_staveblobs(self, cutstaves=False,img=None):
363 if img == None:
364 img = self.img
365 img_binary = self.img_binary
366 else:
367 img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
368 ret2,img_binary = cv2.threshold(img_gray,
369 0,255,cv2.
370 THRESH_BINARY+cv2.
371 THRESH_OTSU)
372
373 staves = self.find_staves(img)
374 blobs = self.find_blobs(img_binary)
375
376 staveblobs = []
377 otherblobs = []
378
379 if self.debug:
380 for staff in staves:
381 for line in staff:
382 y = line.y_list[0]
383 cv2.line(self.debug_img,(0,y),(self.imgWidth,y),(0,255,0),3)
384 for blob in blobs:
385 rect = blob['rect']
386 blob['staves'] = []
387 blob['system'] = False
388 # large enough to contain a stave?
389 blob['large'] = False
390 if rect['width'] > (self.imgWidth * self.stavelineWidthThresh):
391 blob['large'] = True
392 if self.debug:
393 cv2.drawContours(self.debug_img,[blob['contour']],-1, (0, 255,255),2)
394 for staff in staves:
395 inside = True
396 for staveline in staff:
397 leftmost = staveline.y_list[0]
398 # all stafflines have to be in blob
399 if leftmost < rect['y'] or leftmost > (rect['y'] + rect['height']):
400 inside = False
401 break
402 if inside:
403 blob['system'] = True
404 blob['staves'].append(staff)
405 if blob['system']:
406 staveblobs.append(blob)
407 print("found system with %d staves" % len(blob['staves']))
408 if self.debug:
409 cv2.drawContours(self.debug_img,[blob['contour']],-1, (0, 0,255), 2)
410 else:
411 otherblobs.append(blob)
412
413 return(staveblobs, otherblobs)
414
415 def find_systems(self):
416 img = self.img_binary
417 #print "finding staves"
418 (staveblobs, otherblobs) = self.find_staveblobs()
419 #print("found %d staves" % (len(staveblobs),))
420 blobs = staveblobs + otherblobs
421 self.blobs = blobs
422 # systems = []
423 systems = staveblobs
424
425 # attach disconnected bits in bounding box
426 tidied = 0
427 for blob in blobs:
428 if not blob['system']:
429 blob['parent'] = None
430 for system in systems:
431 rect = intersect(system['rect'], blob['rect'])
432 if (rect['height'] > 0 and rect['width'] > 0):
433 # Biggest intersection wins
434 if (blob['parent'] == None) or (rect['area'] > blob['intersection']['area']):
435 blob['parent'] = system
436 blob['intersection'] = rect
437
438 # Just assign to closest bounding rectangle on y-axis
439 if blob['parent'] == None:
440 mindist = None
441 for system in systems:
442 dist = ydist(system['rect'], blob['rect'])
443 if mindist == None or mindist > dist:
444 blob['parent'] = system
445 mindist = dist
446 if blob['parent'] == None:
447 print "wtf"
448 else:
449 tidied = tidied + 1
450 #print "tidied %d" % tidied
451
452 # create new image for systems
453 for system in systems:
454 contours = [system['contour']]
455 x1 = system['rect']['x']
456 y1 = system['rect']['y']
457 x2 = system['rect']['x'] + system['rect']['width']
458 y2 = system['rect']['y'] + system['rect']['height']
459
460 children = 0
461 for blob in blobs:
462 if blob['parent'] == system:
463 children = children + 1
464 contours.append(blob['contour'])
465 # include blob in image size/location
466 x1 = min(x1, blob['rect']['x'])
467 y1 = min(y1, blob['rect']['y'])
468 x2 = max(x2, blob['rect']['x'] + blob['rect']['width'])
469 y2 = max(y2, blob['rect']['y'] + blob['rect']['height'])
470
471 #print("found %d children" % children)
472
473 mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
474
475 cv2.drawContours(mask, contours, -1, 255, -1);
476 #src = img[x1:y1, x2:y2]
477 #srcMask = mask[y1:y2, x1:x2]
478 kernel = np.ones((4,4),np.uint8)
479 mask=cv2.dilate(mask,kernel,iterations=3)
480
481 inv = cv2.bitwise_not(self.img)
482 dest = cv2.bitwise_and(inv,inv,mask = mask)
483 dest = cv2.bitwise_not(dest)
484
485 (h,w,d) = dest.shape
486 system['image'] = dest
487 system['location'] = (x1, y1, x2, y2)
488 system['height'] = h
489 system['width'] = w
490
491 min_x = self.imgWidth
492
493 for staff in system['staves']:
494 for line in staff:
495 min_x = min(min_x, line.left_x)
496
497 system['stave_min_x'] = min_x
498
499 #self.find_bars(system)
500 #system['bar_images'] = self.extract_bars(system, blobs)
501 if self.debug:
502 cv2.imwrite('debug.png', self.debug_img)
503 return(systems,blobs)
504
505 def blob_image(self,img,blob):
506 r = blob['rect']
507 y1 = r['y']
508 x1 = r['x']
509 y2 = r['y'] + r['height']
510 x2 = r['x'] + r['width']
511 return(img[y1:y2, x1:x2])
512
513 # def join_broken_staves(self):
514 # img = self.img
515 # (staveblobs, otherblobs) = self.find_staveblobs()
516 # for i in range(0, len(staveblobs)-1):
517 # for j in range(i, len(staveblobs)):
518 # a = staveblobs[i]
519 # b = staveblobs[j]
520 # atop = a['rect']['x']
521 # abot = a['rect']['x'] + a['rect']['height']
522 # btop = b['rect']['x']
523 # bbot = b['rect']['x'] + b['rect']['height']
524 # if atop > btop and a
525
526
527 def remove_ossia(self):
528 img = self.img
529
530 ossia_mask = np.ones(self.img.shape[:2], dtype="uint8") * 255
531
532 (staveblobs, otherblobs) = self.find_staveblobs()
533 staff_heights = map(lambda s: max_staff_height(s), staveblobs)
534 staff_height = max(staff_heights)
535 height_thresh = staff_height * 0.75
536
537 ossias = filter(lambda s: max_staff_height(s) < height_thresh, staveblobs)
538
539 print("blobs %d/%d" % (len(staveblobs), len(otherblobs)))
540 #staves = self.find_staves(img)
541
542 working_img = img.copy()
543
544 for blob in staveblobs:
545 miny = self.imgHeight
546 for staff in blob['staves']:
547 staffline = staff[0]
548 miny = min(min(staffline.y_list),miny)
549 cv2.line(working_img, (0,miny-4), (self.imgWidth,miny-4), (255,255,255), 4)
550
551 cv2.imwrite('test.png', working_img)
552
553 (staveblobs, otherblobs) = self.find_staveblobs(img=working_img)
554 print("blobs %d/%d" % (len(staveblobs), len(otherblobs)))
555 i = 0
556 # for blob in otherblobs[112:113]:
557 for blob in otherblobs:
558 if blob['rect']['width'] < (self.imgWidth / 50):
559 continue
560 # if blob['rect']['width'] > (self.imgWidth / 2):
561 # continue
562
563 src = self.img
564 mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
565 cv2.drawContours(mask, [blob['contour']], -1, (255,255,255), -1);
566 inv = cv2.bitwise_not(src)
567 dest = cv2.bitwise_and(inv,inv,mask = mask)
568 dest = cv2.bitwise_not(dest)
569 cropped = self.blob_image(dest, blob)
570
571 gi = numpy_io.from_numpy(cropped)
572 #sf = stafffinder_projections.StaffFinder_projections(gi)
573 #sf = stafffinder_miyao.StaffFinder_miyao(gi)
574 sf = stafffinder_dalitz.StaffFinder_dalitz(gi)
575 sf.find_staves()
576 staves = sf.get_skeleton()
577
578 if (len(staves) > 0):
579 maxlines = max(map(len, staves))
580 else:
581 maxlines = 0
582 if maxlines >= 4:
583 print("aha ossia with %d lines" % (maxlines,))
584 ossias.append(blob)
585 for ossia in ossias:
586 if self.debug:
587 fn = 'removed_%d.png' % i
588 cv2.imwrite(fn, cropped)
589 i = i + 1
590 cv2.drawContours(ossia_mask, [ossia['contour']], -1, 0, -1)
591
592 # erode a little to get rid of 'ghosting' around ossia
593 kernel = np.ones((4,4),np.uint8)
594 ossia_mask=cv2.erode(ossia_mask,kernel,iterations=4)
595
596 #cv2.imwrite('posterode.png', mask)
597
598 result = img.copy()
599 inverted = cv2.bitwise_not(result)
600 result = cv2.bitwise_or(inverted,inverted,mask=ossia_mask)
601 result = cv2.bitwise_not(result)
602
603 if self.debug:
604 cv2.imwrite('debug.png', self.debug_img)
605
606 self.img = result
607
608 def split_movements(self, outfileA, outfileB):
609 # 2% of page width
610 indentThresh = 0.02 * self.imgWidth
611
612 systems, blobs = self.find_systems()
613
614 # Top - down order
615 systems = sorted(systems, key=lambda system: system['rect']['y'])
616
617 xs = []
618 for system in systems:
619 xs.append(system['stave_min_x'])
620 threshold = min(xs) + indentThresh
621
622 # Skip the first one, we don't split if the movement starts at top
623 # of page
624 found = None
625 for i in range(0,len(systems)):
626 #cv2.imwrite("system%d.png" %i, systems[i]['image'])
627 if xs[i] > threshold:
628 if found != None:
629 print "Oops, more than one movement found."
630 found = i
631 print("New movement at system %d" % (i+1))
632
633 if (found == 0):
634 self.save_systems(outfileA, systems)
635 else:
636 self.save_systems(outfileA, systems[:found])
637 self.save_systems(outfileB, systems[found:])
638 return(found)
639
640 def save(self, outfile):
641 cv2.imwrite(outfile, self.img)
642
643 def save_systems(self, outfile, systems):
644 print "saving %s" % outfile
645 contours = []
646 for system in systems:
647 contours.append(system['contour'])
648
649 for blob in self.blobs:
650 if blob['parent'] == system:
651 contours.append(blob['contour'])
652
653 mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
654
655 cv2.drawContours(mask, contours, -1, 255, -1);
656
657 kernel = np.ones((4,4),np.uint8)
658 mask=cv2.dilate(mask,kernel,iterations=1)
659
660 inv = cv2.bitwise_not(self.img)
661 dest = cv2.bitwise_and(inv,inv,mask = mask)
662 dest = cv2.bitwise_not(dest)
663 cv2.imwrite(outfile,dest)