diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Preprocessing/preomr.py	Mon May 04 22:56:18 2015 +0200
@@ -0,0 +1,663 @@
+import sys
+import cv2
+import numpy as np
+import math
+import copy
+
+from gamera.core import *
+from gamera.toolkits.musicstaves import musicstaves_rl_roach_tatem
+from gamera.toolkits.musicstaves import musicstaves_rl_fujinaga
+from gamera.toolkits.musicstaves import stafffinder_miyao
+from gamera.toolkits.musicstaves import stafffinder_dalitz
+from gamera.toolkits.musicstaves import stafffinder_projections
+from gamera.plugins import numpy_io
+init_gamera()
+
+#import ossiafinder_dalitz
+
+def intersect(r1,r2):
+    """Returns the intersection of two rectangles"""
+    x1 = max(r1['x'], r2['x'])
+    y1 = max(r1['y'], r2['y'])
+    x2 = min(r1['x'] + r1['width'],  r2['x'] + r2['width'])
+    y2 = min(r1['y'] + r1['height'], r2['y'] + r2['height'])
+    result = {"x": x1, "y": y1, "width": x2 - x1, "height": y2-y1}
+    result['area'] = result['width'] * result['height']
+    return(result)
+
+def ydist(r1,r2):
+    """distance on y-axis between two non-interecting rectangles"""
+    top1 = r1['y']
+    bottom1 = r1['y'] + r1['height']
+
+    top2 = r2['y']
+    bottom2 = r2['y'] + r2['height']
+    return(min(abs(top1-bottom2), abs(top2-bottom1)))
+    
+
+def show(img, factor=0.5):
+    """ show an image until the escape key is pressed
+    :param factor: scale factor (default 0.5, half size)
+    """
+    if factor != 1.0:
+        img = cv2.resize(img, (0,0), fx=factor, fy=factor) 
+
+    cv2.imwrite('show.png',img)
+#    while(1):
+#        k = cv2.waitKey(0)
+#        if k==27:    # Esc key to quit
+#            cv2.destroyAllWindows()
+#            exit()
+#        if k==32:    # Space to stop
+#            cv2.destroyAllWindows()
+#            break
+
+
+def max_staff_height(blob):
+    result = 0
+    for staff in blob['staves']:
+        top = staff[0].y_list[0]
+        bottom = staff[-1].y_list[0]
+        result = max(result, bottom-top)
+    return(result)
+
+
+def deskew(img):
+    """Deskews the given image based on lines detected with opencv's
+    HoughLines function."""
+    print "Deskewing."
+    imgHeight, imgWidth, imgDepth = img.shape
+    img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
+    img_edges = cv2.Canny(img_gray,50,150,apertureSize = 3)
+    minLineLength = int(imgWidth*0.5)
+    houghThresh = int(imgWidth*0.15)
+    maxLineGap = 10
+    #lines = cv2.HoughLinesP(img_edges,1,np.pi/(180*1),houghThresh,minLineLength,maxLineGap)
+    lines = cv2.HoughLines(img_edges,1,np.pi/(180*3),houghThresh)
+
+    angles = []
+    for rho,theta in lines[0]:
+        angles.append((theta - (np.pi / 2)))
+        
+        a = np.cos(theta)
+        b = np.sin(theta)
+        x0 = a*rho
+        y0 = b*rho
+        x1 = int(x0 + imgWidth*(-b))
+        y1 = int(y0 + imgWidth*(a))
+        x2 = int(x0 - imgWidth*(-b))
+        y2 = int(y0 - imgWidth*(a))
+        #cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
+
+    middle = np.median(angles)
+    middle_deg = middle * (180/np.pi)
+    
+    rotation = cv2.getRotationMatrix2D((imgWidth/2,imgHeight/2),middle_deg,1.0)
+    
+    # rotate while inverted. the background is filled with zeros
+    # (black), this inversion means that ends up white
+    deskewed = cv2.bitwise_not(cv2.warpAffine(cv2.bitwise_not(img),
+                                              rotation,
+                                              (imgWidth,imgHeight))
+                           )
+    return(deskewed)
+
+class PreOMR(object):
+    stavelineWidthThresh = 0.5
+    
+    def __init__(self, infile, deskew=False):
+        self.debug = True
+        self.infile = infile
+        self.img = cv2.imread(self.infile)
+        if deskew:
+            self.img = deskew(self.img)
+        self.original = self.img
+        self.imgHeight, self.imgWidth, self.imgDepth = self.img.shape
+        self.img_gray = cv2.cvtColor(self.img,cv2.COLOR_BGR2GRAY)
+        if self.debug:
+            self.debug_img = self.img.copy()
+        ret2,self.img_binary = cv2.threshold(self.img_gray, 
+                                             0,255,cv2.
+                                             THRESH_BINARY+cv2.
+                                             THRESH_OTSU)
+    
+    def staffline_removal(self):
+        gamera_img = numpy_io.from_numpy(self.img)
+        #self.save('tmp.png')
+        #gamera_img = load_image('tmp.png')
+
+        #ms = musicstaves_rl_roach_tatem.MusicStaves_rl_roach_tatem(gamera_img)
+        ms = musicstaves_rl_fujinaga.MusicStaves_rl_fujinaga(gamera_img)
+        cv2.imwrite('tmp.png', self.img)
+        ms.remove_staves(crossing_symbols = 'bars')
+        ms.image.save_PNG("tmpb.png")
+        staffless = cv2.imread("tmp.png", cv2.CV_LOAD_IMAGE_GRAYSCALE)
+        return(staffless)
+
+    def find_staves(self, img):
+        gamera_img = numpy_io.from_numpy(img)
+        #sf = stafffinder_projections.StaffFinder_projections(gamera_img)
+        #sf.find_staves(follow_wobble=True,preprocessing=0)
+        #sf = stafffinder_dalitz.StaffFinder_dalitz(gamera_img)
+        sf = stafffinder_miyao.StaffFinder_miyao(gamera_img)
+        sf.find_staves()
+        #sf.find_staves(debug=2)
+
+        staves = sf.get_skeleton()
+#        if self.debug:
+#            for i, staff in enumerate(staves):
+#                print "Staff %d has %d staves:" % (i+1, len(staff))
+#                for j, line in enumerate(staff):
+#                    print("    %d. line at (%d,%d)" % (j+1,line.left_x,line.y_list[0]))
+        return(staves)
+
+    def find_blobs(self, img_binary):
+        """Find blobs in the given image, returned as a list of associative
+        lists containing various cheap metrics for each blob."""
+        
+        blobs = []
+        img_inverted = cv2.bitwise_not(img_binary)
+        contours, hierarchy = cv2.findContours(img_inverted,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
+        for (i, c) in enumerate(contours):
+            blob = {}
+            blobs.append(blob)
+
+            blob['area'] = cv2.contourArea(c)
+
+            m = cv2.moments(c)
+            if m['m00'] == 0: # When would this be true?
+                blob['x'] = 0
+                blob['y'] = 0
+            else:
+                blob['x'] = m['m10'] / m['m00']
+                blob['y'] = m['m01'] / m['m00']
+
+            blob['contour'] = c
+
+            rect = cv2.boundingRect(c)
+            blob['rect'] = {'x': rect[0], 
+                            'y': rect[1], 
+                            'width': rect[2], 
+                            'height': rect[3]
+                           }
+            blob['boundingRect'] = rect
+            blob['hull'] = hull = cv2.convexHull(c)
+            blob['hull_area'] = abs(cv2.contourArea(hull))
+            blob['system'] = False
+            blob['parent'] = None
+            #blob['perimeter'] = perimeter = cv2.arcLength(c, True)
+            #blob['roundness'] = (perimeter * 0.282) / math.sqrt(area)
+            #(centre, axes, orientation) = cv2.fitEllipse(c)
+            #blob['orientation'] = orientation / 180
+            #print "orientation: %f" % orientation
+            #blob['aspect'] = float(rect[1]) / float(rect[3])
+
+        return blobs
+
+    def find_bars(self, system):
+        staffless = self.staffline_removal()
+        blobs = self.blobs
+
+        """Finds the barlines in the system, given a binary image, a hash of
+        info about the system, and blobs detected in the image.
+        
+        """
+        img = system['image']
+
+        for staff in system['staves']:
+            min_x = 0
+            max_x = self.imgWidth
+
+            for line in staff:
+                min_x = max(min_x, line.left_x)
+                max_x = min(max_x, line.left_x + len(line.y_list))
+
+                if self.debug:
+                    for (i,y) in enumerate(line.y_list):
+                        x = line.left_x + i
+                        cv2.line(self.debug_img,(x,y),(x,y),(0,255,0),3)
+                
+#            cv2.line(img,(0,int(start)),(imgWidth,int(start)),(0,255,255),3)
+#            cv2.line(img,(0,int(stop)),(imgWidth,int(stop)),(0,255,255),3)
+#            cv2.line(img,(0,int(first_staveline)),(imgWidth,int(first_staveline)),(255,255,0),3)
+#            cv2.line(img,(0,int(last_staveline)),(imgWidth,int(last_staveline)),(255,255,0),3)
+
+            # assuming single staff for now..
+            barlines = [0]
+            system['barlines'] = barlines
+
+            x_projection = []
+            
+            for x in range(min_x, max_x):
+                first_staveline = staff[0].y_list[x - staff[0].left_x]
+                last_staveline = staff[-1].y_list[x - staff[-1].left_x]
+
+                #print("Stavelines: first %d last %d" % (first_staveline, last_staveline))
+                stave_height = last_staveline - first_staveline
+
+                # mean distance between stavelines
+                avg_inter = float(stave_height) / float(len(staff)-1)
+                #print("avg_inter: %f" % (avg_inter,))
+
+                # where to look a bit above and below the stave for whitespace
+                # above a barline
+                gap = avg_inter / 2.0
+                start = first_staveline - gap
+                stop = last_staveline + gap
+
+                # above stave, stave and below stave
+                top = float(gap - 
+                            cv2.countNonZero(staffless[start:first_staveline, 
+                                                       x:x+1])) / float(gap)
+                mid = float(stave_height - 
+                            cv2.countNonZero(staffless[first_staveline:last_staveline,
+                                                       x:x+1])
+                           ) / float(stave_height)
+                bot = float(gap - 
+                            cv2.countNonZero(staffless[last_staveline:stop, x:x+1])
+                           ) / float(gap)
+                x_projection.append((top,mid,bot))
+    
+            barline_start = -1
+            gap_dist = avg_inter/4
+            gap_min = (avg_inter/float(stave_height)) * 0.3
+            gap_tolerance = int(avg_inter/10)
+            
+            margin = int(avg_inter*2)
+            
+            for x in range(min_x+margin, max_x-margin):
+                (top,mid,bot) = x_projection[x - min_x]
+                #if self.debug:
+                    #cv2.line(system['image'],(x,first_staveline),(x,int(first_staveline+((last_staveline-first_staveline)*mid))),(255,255,0),1)
+
+                # found start of barline candidate
+                if top < 0.6 and bot < 0.6 and mid > 0.95:
+                    if barline_start < 0:
+                        barline_start = x
+                else:
+                    if barline_start > 0: 
+                        # check there is nothing either side of 'barline'
+                        barline_stop = x-1
+                        barline_mid = barline_stop - ((barline_stop - barline_start)/2)
+                        #print("barline start %d stop %d mid %d" % (barline_start, barline_stop, barline_mid))
+                        left = int(max(0,barline_start-gap_dist))
+                        right = int(min(system['width']-1,(x-1)+gap_dist))
+                    
+                        total = 0
+                        for i in range(left-gap_tolerance, left+gap_tolerance+1):
+                            total = total + x_projection[i-min_x][1]
+                        left_avg = total / ((gap_tolerance*2)+1)
+
+                        total = 0
+                        for i in range(right-gap_tolerance, right+gap_tolerance+1):
+                            total = total + x_projection[i-min_x][1]
+                        right_avg = total / ((gap_tolerance*2)+1)
+                    
+                        cv2.line(img,(left,first_staveline),(left,last_staveline),(255,0,255),1)
+                        cv2.line(img,(right,first_staveline),(right,last_staveline),(255,0,255),1)
+
+                        if (left_avg <= gap_min and right_avg <= gap_min):
+                            #print("success: left_avg %f right_avg %f" % (left_avg, right_avg))
+                            cv2.line(img,(barline_mid,first_staveline),(barline_mid,last_staveline),(255,0,0),3)
+                            barlines.append(barline_mid)
+                        else:
+                            #print("fail: left_avg %f right_avg %f" % (left_avg, right_avg))
+                            cv2.line(img,(barline_mid,first_staveline),(barline_mid,last_staveline),(0,255,0),3)
+                        #show(img)
+                        barline_start = -1
+            (x1, y1, x2, y2) = system['location']
+            #show(system['image'][y1:y2, x1:x2])
+
+    def extract_bars(self, system, blobs):
+        """Given information about a system (including identified barlines),
+        and all the blobs on a page returns a list of bars in the system,
+        each an associative array containing image and location.
+        
+        """
+
+        img = system['image']
+
+        barlines = system['barlines']
+
+        result = []
+
+        for i in range(0,len(barlines)):
+            barstart = barlines[i]
+            if i == (len(barlines)-1):
+                barstop = system['width']
+            else:
+                barstop = barlines[i+1]
+            #print("barstart %d barstop %d" % (barstart, barstop))
+            contours = [system['contour']]
+            x1 = barstart
+            y1 = system['location'][1]
+            x2 = barstop
+            y2 = system['location'][3]
+            h = y2 - y1
+            w = x2 - x1
+            #print("height %d width %d" % (h, w))
+            for blob in blobs:
+                if blob['parent'] == system:
+                    if blob['rect']['x'] >= barstart and blob['rect']['x'] + blob['rect']['width'] <= barstop:
+                        contours.append(blob['contour'])
+
+            mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
+
+            cv2.drawContours(mask, contours, -1, 255, -1);
+        
+            inv = cv2.bitwise_not(img)
+            dest = cv2.bitwise_and(inv,inv,mask = mask)
+            dest = cv2.bitwise_not(dest)
+            img_bar = dest[y1:y2, x1:x2]
+            bar = {'image': img_bar,
+                   'page': dest,
+                   'location': [x1,y1,x2,y2]
+            }
+            result.append(bar)
+            #show(img_bar)
+        return(result)
+
+
+
+    def find_staveblobs(self, cutstaves=False,img=None):
+        if img == None:
+            img = self.img
+            img_binary = self.img_binary
+        else:
+            img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
+            ret2,img_binary = cv2.threshold(img_gray, 
+                                            0,255,cv2.
+                                            THRESH_BINARY+cv2.
+                                            THRESH_OTSU)
+
+        staves = self.find_staves(img)
+        blobs = self.find_blobs(img_binary)
+
+        staveblobs = []
+        otherblobs = []
+
+        if self.debug:
+            for staff in staves:
+                for line in staff:
+                    y = line.y_list[0]
+                    cv2.line(self.debug_img,(0,y),(self.imgWidth,y),(0,255,0),3)
+        for blob in blobs:
+            rect = blob['rect']
+            blob['staves'] = []
+            blob['system'] = False
+            # large enough to contain a stave?
+            blob['large'] = False
+            if rect['width'] > (self.imgWidth * self.stavelineWidthThresh):
+                blob['large'] = True
+                if self.debug:
+                    cv2.drawContours(self.debug_img,[blob['contour']],-1, (0, 255,255),2)
+                for staff in staves:
+                    inside = True
+                    for staveline in staff:
+                        leftmost = staveline.y_list[0]
+                        # all stafflines have to be in blob
+                        if leftmost < rect['y'] or leftmost > (rect['y'] + rect['height']):
+                            inside = False
+                            break
+                    if inside:
+                        blob['system'] = True
+                        blob['staves'].append(staff)
+            if blob['system']:
+                staveblobs.append(blob)
+                print("found system with %d staves" % len(blob['staves']))
+                if self.debug:
+                    cv2.drawContours(self.debug_img,[blob['contour']],-1, (0, 0,255), 2)
+            else:
+                otherblobs.append(blob)
+
+        return(staveblobs, otherblobs)
+
+    def find_systems(self):
+        img = self.img_binary
+        #print "finding staves"
+        (staveblobs, otherblobs) = self.find_staveblobs()
+        #print("found %d staves" % (len(staveblobs),))
+        blobs = staveblobs + otherblobs
+        self.blobs = blobs
+        # systems = []
+        systems = staveblobs
+        
+        # attach disconnected bits in bounding box
+        tidied = 0
+        for blob in blobs:
+            if not blob['system']:
+                blob['parent'] = None
+                for system in systems:
+                    rect = intersect(system['rect'], blob['rect'])
+                    if (rect['height'] > 0 and rect['width'] > 0):
+                        # Biggest intersection wins
+                        if (blob['parent'] == None) or (rect['area'] > blob['intersection']['area']):
+                            blob['parent'] = system
+                            blob['intersection'] = rect
+
+                # Just assign to closest bounding rectangle on y-axis
+                if blob['parent'] == None:
+                    mindist = None
+                    for system in systems:
+                        dist = ydist(system['rect'], blob['rect'])
+                        if mindist == None or mindist > dist:
+                            blob['parent'] = system
+                            mindist = dist
+                    if blob['parent'] == None:
+                        print "wtf"
+                    else:
+                        tidied = tidied + 1
+        #print "tidied %d" % tidied
+
+        # create new image for systems
+        for system in systems:
+            contours = [system['contour']]
+            x1 = system['rect']['x']
+            y1 = system['rect']['y']
+            x2 = system['rect']['x'] + system['rect']['width']
+            y2 = system['rect']['y'] + system['rect']['height']
+
+            children = 0
+            for blob in blobs:
+                if blob['parent'] == system:
+                    children = children + 1
+                    contours.append(blob['contour'])
+                    # include blob in image size/location
+                    x1 = min(x1, blob['rect']['x'])
+                    y1 = min(y1, blob['rect']['y'])
+                    x2 = max(x2, blob['rect']['x'] + blob['rect']['width'])
+                    y2 = max(y2, blob['rect']['y'] + blob['rect']['height'])
+
+            #print("found %d children" % children)
+
+            mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
+
+            cv2.drawContours(mask, contours, -1, 255, -1);
+            #src = img[x1:y1, x2:y2]
+            #srcMask = mask[y1:y2, x1:x2]
+            kernel = np.ones((4,4),np.uint8)
+            mask=cv2.dilate(mask,kernel,iterations=3)
+
+            inv = cv2.bitwise_not(self.img)
+            dest = cv2.bitwise_and(inv,inv,mask = mask)
+            dest = cv2.bitwise_not(dest)
+
+            (h,w,d) = dest.shape
+            system['image'] = dest
+            system['location'] = (x1, y1, x2, y2)
+            system['height'] = h
+            system['width'] = w
+
+            min_x = self.imgWidth
+
+            for staff in system['staves']:
+                for line in staff:
+                    min_x = min(min_x, line.left_x)
+
+            system['stave_min_x'] = min_x
+
+            #self.find_bars(system)
+            #system['bar_images'] = self.extract_bars(system, blobs)
+        if self.debug:
+            cv2.imwrite('debug.png', self.debug_img)
+        return(systems,blobs)
+
+    def blob_image(self,img,blob):
+        r = blob['rect']
+        y1 = r['y']
+        x1 = r['x']
+        y2 = r['y'] + r['height']
+        x2 = r['x'] + r['width']
+        return(img[y1:y2, x1:x2])
+
+#    def join_broken_staves(self):
+#        img = self.img
+#        (staveblobs, otherblobs) = self.find_staveblobs()
+#        for i in range(0, len(staveblobs)-1):
+#            for j in range(i, len(staveblobs)):
+#                a = staveblobs[i]
+#                b = staveblobs[j]
+#                atop = a['rect']['x']
+#                abot = a['rect']['x'] + a['rect']['height']
+#                btop = b['rect']['x']
+#                bbot = b['rect']['x'] + b['rect']['height']
+#                if atop > btop and a
+        
+
+    def remove_ossia(self):
+        img = self.img
+        
+        ossia_mask = np.ones(self.img.shape[:2], dtype="uint8") * 255
+
+        (staveblobs, otherblobs) = self.find_staveblobs()
+        staff_heights = map(lambda s: max_staff_height(s), staveblobs)
+        staff_height = max(staff_heights)
+        height_thresh = staff_height * 0.75
+
+        ossias = filter(lambda s: max_staff_height(s) < height_thresh, staveblobs)
+        
+        print("blobs %d/%d" % (len(staveblobs), len(otherblobs)))
+        #staves = self.find_staves(img)
+
+        working_img = img.copy()
+
+        for blob in staveblobs:
+            miny = self.imgHeight
+            for staff in blob['staves']:
+                staffline = staff[0]
+                miny = min(min(staffline.y_list),miny)
+            cv2.line(working_img, (0,miny-4), (self.imgWidth,miny-4), (255,255,255), 4) 
+
+        cv2.imwrite('test.png', working_img)
+        
+        (staveblobs, otherblobs) = self.find_staveblobs(img=working_img)
+        print("blobs %d/%d" % (len(staveblobs), len(otherblobs)))
+        i = 0
+#        for blob in otherblobs[112:113]:
+        for blob in otherblobs:
+            if blob['rect']['width'] < (self.imgWidth / 50):
+                continue
+#            if blob['rect']['width'] > (self.imgWidth / 2):
+#                continue
+
+            src = self.img
+            mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
+            cv2.drawContours(mask, [blob['contour']], -1, (255,255,255), -1);
+            inv = cv2.bitwise_not(src)
+            dest = cv2.bitwise_and(inv,inv,mask = mask)
+            dest = cv2.bitwise_not(dest)
+            cropped = self.blob_image(dest, blob)
+
+            gi = numpy_io.from_numpy(cropped)
+            #sf = stafffinder_projections.StaffFinder_projections(gi)
+            #sf = stafffinder_miyao.StaffFinder_miyao(gi)
+            sf = stafffinder_dalitz.StaffFinder_dalitz(gi)
+            sf.find_staves()
+            staves = sf.get_skeleton()
+
+            if (len(staves) > 0):
+                maxlines = max(map(len, staves))
+            else:
+                maxlines = 0
+            if maxlines >= 4:
+                print("aha ossia with %d lines" % (maxlines,))
+                ossias.append(blob)
+        for ossia in ossias:
+            if self.debug:
+                fn = 'removed_%d.png' % i
+                cv2.imwrite(fn, cropped)
+                i = i + 1
+            cv2.drawContours(ossia_mask, [ossia['contour']], -1, 0, -1)
+
+        # erode a little to get rid of 'ghosting' around ossia
+        kernel = np.ones((4,4),np.uint8)
+        ossia_mask=cv2.erode(ossia_mask,kernel,iterations=4)
+        
+        #cv2.imwrite('posterode.png', mask)
+        
+        result = img.copy()
+        inverted = cv2.bitwise_not(result)
+        result = cv2.bitwise_or(inverted,inverted,mask=ossia_mask)
+        result = cv2.bitwise_not(result)
+
+        if self.debug:
+            cv2.imwrite('debug.png', self.debug_img)
+
+        self.img = result
+
+    def split_movements(self, outfileA, outfileB):
+        # 2% of page width
+        indentThresh = 0.02 * self.imgWidth
+
+        systems, blobs = self.find_systems()
+        
+        # Top - down order
+        systems = sorted(systems, key=lambda system: system['rect']['y'])
+
+        xs = []
+        for system in systems:
+            xs.append(system['stave_min_x'])
+        threshold = min(xs) + indentThresh
+        
+        # Skip the first one, we don't split if the movement starts at top
+        # of page
+        found = None
+        for i in range(0,len(systems)):
+            #cv2.imwrite("system%d.png" %i, systems[i]['image'])
+            if xs[i] > threshold:
+                if found != None:
+                    print "Oops, more than one movement found."
+                found = i
+                print("New movement at system %d" % (i+1))
+
+        if (found == 0):
+            self.save_systems(outfileA, systems)
+        else:
+            self.save_systems(outfileA, systems[:found])
+            self.save_systems(outfileB, systems[found:])
+        return(found)
+
+    def save(self, outfile):
+        cv2.imwrite(outfile, self.img)
+
+    def save_systems(self, outfile, systems):
+        print "saving %s" % outfile
+        contours = []
+        for system in systems:
+            contours.append(system['contour'])
+
+            for blob in self.blobs:
+                if blob['parent'] == system:
+                    contours.append(blob['contour'])
+
+        mask = np.zeros((self.imgHeight,self.imgWidth,1), np.uint8)
+        
+        cv2.drawContours(mask, contours, -1, 255, -1);
+
+        kernel = np.ones((4,4),np.uint8)
+        mask=cv2.dilate(mask,kernel,iterations=1)
+        
+        inv = cv2.bitwise_not(self.img)
+        dest = cv2.bitwise_and(inv,inv,mask = mask)
+        dest = cv2.bitwise_not(dest)
+        cv2.imwrite(outfile,dest)
\ No newline at end of file