Example #1
0
 def is_skin_hair_block(self, block, block_type):
     '''
     Return true if the block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
     is a skin/hair-block - it is if a majority (as per the threshold attribute) of the pixels in the block are
     skin/hair colored. 'block_type' says whether we are testing for a skin block ('s') or a hair block ('h).
     '''
     # Your code
     pyimage_object=PyImage(self.filename)
     k=pyimage_object.size()
     pixel_x=block[0]
     pixel_y=block[1]
     lst_skin=[]
     lst_hair=[]
     temp_x=0
     while(pixel_x<k[0]):
         temp_y=0
         while(pixel_y<k[1] and temp_y<self.block_size):
             rgb_a=pyimage_object.get_rgba(pixel_x,pixel_y)
             pixel_color=Color(rgb_a)
             if(self.is_skin(pixel_color)):
                 lst_skin.append((pixel_x,pixel_y))
             elif(self.is_hair(pixel_color)):
                 lst_hair.append((pixel_x,pixel_y))  
             pixel_y+=1
             temp_y+=1
         pixel_x+=1
         temp_x+=1
         if(len(lst_skin)>self.majority*self.block_size and  block_type=='s'):
             return True
         elif(len(lst_hair)>self.majority*self.block_size and  block_type=='h'):
             return True
         else:
             return False
Example #2
0
 def add_neighbour_blocks(self, block, graph):
     '''
     Given a block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
     and a graph (could be a hair or a skin graph), add edges from the current block to its neighbours
     on the image that are already nodes of the graph
     Check blocks to the left, top-left and top of the current block and if any of these blocks is in the
     graph (means the neighbour is also of the same type - skin or hair) add an edge from the current block
     to the neighbour.
     '''
     # Your code
     pyimage_object=PyImage(self.filename)
     k=pyimage_object.size()
     if(self.is_skin_hair_block(block, 's')):
         if((block[0]-self.block_size)<k[0] and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0]-self.block_size,block[1]), 's')):
             graph.add_edge(block, (block[0]-self.block_size,block[1]))
         if((block[1]-self.block_size)<k[1] and (block[1]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
             graph.add_edge(block, (block[0],block[1]-self.block_size))   
         if((block[1]-self.block_size)<k[1] and (block[0]-self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
             graph.add_edge(block, (block[0]-self.block_size,block[1]-self.block_size))
         if((block[1]-self.block_size)<k[1] and (block[0]+self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]+self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
             graph.add_edge(block, (block[0]+self.block_size,block[1]-self.block_size))        
     if(self.is_skin_hair_block(block, 'h')):
         if((block[0]-self.block_size)<k[0] and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0]-self.block_size,block[1]), 'h')):
             graph.add_edge(block, (block[0]-self.block_size,block[1]))
         if((block[1]-self.block_size)<k[1] and (block[1]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
             graph.add_edge(block, (block[0],block[1]-self.block_size))   
         if((block[1]-self.block_size)<k[1] and (block[0]-self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
             graph.add_edge(block, (block[0]-self.block_size,block[1]-self.block_size))
         if((block[1]-self.block_size)<k[1] and (block[0]+self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]+self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
             graph.add_edge(block, (block[0]+self.block_size,block[1]-self.block_size))        
Example #3
0
    def loadImage(self, image):
        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadImage(image)
Example #4
0
    def loadImage(self, image):
        '''This function should initialize the class by loading an image from
        the function call. It then places that image at the pyramid's lowest
        level, and specifies no pixels were lost in this process.'''

        # Reset pyramid and loss info
        if self.pyramid:
            self.pyramid = []
            self.info_loss = []

        # Create image, append things
        img = PyImage()
        img.loadImage(image)
        self.pyramid.append(img)
        self.info_loss.append((False, False))
 def __init__(self,
              filename,
              block_size=11,
              min_component_size=11,
              majority=0.56):
     '''
     Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
     threshold to decide how many skin color pixels are required to declare a block as a skin-block
     and min number of blocks required for a component. The majority argument says what fraction of
     the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
     0.5 (half).
     '''
     self.image = PyImage(filename)
     self.block_sz = block_size
     self.min_blocks = min_component_size
     self.fraction_pixel = majority
Example #6
0
    def loadImage(self, image):

        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadImage(image)
Example #7
0
 def __init__(self, filename, block_size = 5, min_component_size = 10, majority = 0.5):
     '''
     Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
     threshold to decide how many skin color pixels are required to declare a block as a skin-block
     and min number of blocks required for a component. The majority argument says what fraction of
     the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
     0.5 (half).
     '''
     # Your code
     self.filename=filename
     self.block_size=block_size
     self.majority=majority
     self.min_component_size=min_component_size
     self.pyimage_obj = PyImage(filename)
Example #8
0
 def make_block_graph(self):
     '''
     Return the skin and hair graphs - nodes are the skin/hair blocks respectively
     Initialize skin and hair graphs. For every block if it is a  skin(hair) block
     add edges to its neighbour skin(hair) blocks in the corresponding graph
     For this to work the blocks have to be traversed in the top->bottom, left->right order
     '''
     # Your code
     graph_object_skin=Graph(None)
     graph_object_hair=Graph(None)
     pyimage_object=PyImage(self.filename)
     k=pyimage_object.size()
     i=0
     while(i<k[0]):
         # 3
         j=0
         while(j<k[1]):
             #  4
             if(self.is_skin_hair_block((i,j),'s')):
                 graph_object_skin.add_node((i,j))
                 self.add_neighbour_blocks((i,j),graph_object_skin)
             j+=self.block_size
         i+=self.block_size
     i=0
     while(i<k[0]):
         #  5
         j=0
         while(j<k[1]):
             # 6
             if(self.is_skin_hair_block((i,j),'h')):
                 graph_object_hair.add_node((i,j))
                 self.add_neighbour_blocks((i,j),graph_object_hair)
             j+=self.block_size
         i+=self.block_size
     
     return graph_object_skin, graph_object_hair
Example #9
0
    def loadFile(self, filepath, nFrames, pyramid=0):
        '''This function should load frames of a video with a regular file
        expression to read filenames. The number of frames is known beforehand
        to ease computing.'''

        # Update variables based on input
        self.pyramid = pyramid
        self.nFrames = nFrames
        self.filepath = filepath

        # Separate name from extension
        path = filepath.split('.')
        extension = '.' + path[-1]
        path = "".join(path[:-1]) + '-'

        # For each frame that will be processed, we will load a single file
        for i in np.arange(1, nFrames + 1):

            print "DEBUG: Loading frame", i

            # Load each frame
            frame = PyImage()
            frame.loadFile(path + str(i) + extension)
            frame.img = frame.img.convert("RGB")
            frame.updatePixels()

            # Compute its grayscale counterpart
            grayscaleFrame = frame.copy()
            grayscaleFrame.img = grayscaleFrame.img.convert("L")
            grayscaleFrame.updatePixels()

            # Append frames to lists
            self.frames.append(frame)
            self.grayscaleFrames.append(grayscaleFrame)

            # If pyramids will be used, start a pyramid for every grayscale
            # frame to be used later on
            if pyramid:
                print "DEBUG: Computing pyramids for frame", i
                framePyramid = GaussPyramid(pyramid)
                framePyramid.loadImage(grayscaleFrame.img)
                framePyramid.reduceMax()
                self.pyramids.append(framePyramid)
Example #10
0
    def loadFile(self, filepath, nFrames, pyramid=0):

        '''This function should load frames of a video with a regular file
        expression to read filenames. The number of frames is known beforehand
        to ease computing.'''

        # Update variables based on input
        self.pyramid = pyramid
        self.nFrames = nFrames
        self.filepath = filepath

        # Separate name from extension
        path = filepath.split('.')
        extension = '.' + path[-1]
        path = "".join(path[:-1]) + '-'

        # For each frame that will be processed, we will load a single file
        for i in np.arange(1, nFrames+1):

            print "DEBUG: Loading frame", i

            # Load each frame
            frame = PyImage()
            frame.loadFile(path+str(i)+extension)
            frame.img = frame.img.convert("RGB")
            frame.updatePixels()

            # Compute its grayscale counterpart
            grayscaleFrame = frame.copy()
            grayscaleFrame.img = grayscaleFrame.img.convert("L")
            grayscaleFrame.updatePixels()

            # Append frames to lists
            self.frames.append(frame)
            self.grayscaleFrames.append(grayscaleFrame)

            # If pyramids will be used, start a pyramid for every grayscale
            # frame to be used later on
            if pyramid:
                print "DEBUG: Computing pyramids for frame", i
                framePyramid = GaussPyramid(pyramid)
                framePyramid.loadImage(grayscaleFrame.img)
                framePyramid.reduceMax()
                self.pyramids.append(framePyramid)
class FaceDetector(object):
    '''
    classdocs
    '''
    def __init__(self,
                 filename,
                 block_size=5,
                 min_component_size=15,
                 majority=0.5):
        '''
        Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
        threshold to decide how many skin color pixels are required to declare a block as a skin-block
        and min number of blocks required for a component. The majority argument says what fraction of
        the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
        0.5 (half).
        '''
        # Your code
        self.filename = filename
        self.block_size = block_size
        self.min_component_size = min_component_size
        self.majority = majority
        self.skin = Graph()
        self.hair = Graph()
        self.pyim = PyImage(filename)

    def skin_green_limits(self, red):
        '''
        Return the limits of normalized green given the normalized red component as a tuple (min, max)
        '''
        return ((-0.776 * red * red + 0.5601 * red + 0.1766),
                (-1.376 * red * red + 1.0743 * red + 0.1452))

    def is_skin(self, pixel_color):
        '''
        Given the pixel color (as a Color object) return True if it represents the skin color
        Color is skin if hue in degrees is (> 240 or less than or equal to 20) and 
        green is in the green limits and it is not white
        '''
        # Your code
        color = Color(pixel_color)
        hue = color.hue_degrees()
        W = (color.R - 0.33) * (color.R - 0.33) + (color.G - 0.33) * (color.G -
                                                                      0.33)
        green_limits = self.skin_green_limits(color.R)
        cond1 = (hue > 240 or hue <= 20)
        cond2 = green_limits[0] < color.G and color.G < green_limits[1]
        cond3 = color.rgb_abs()[0] < 230 and color.rgb_abs(
        )[1] < 230 and color.rgb_abs()[2] < 230
        cond4 = 0.6 > color.R and color.R > 0.2
        if cond1 and cond2 and cond3 and cond4 and W > 0.0004:
            return True
        return False

    def is_hair(self, pixel_color):
        '''
        Return True if the pixel color represents hair - it is if intensity < 80 and ((B-G)<15 or (B-R)<15 or
        hue is between 20 and 40)
        '''
        # Your code
        color = Color(pixel_color)
        hue = color.hue_degrees()
        R = pixel_color[0]
        G = pixel_color[1]
        B = pixel_color[2]
        if ((20 <= hue and hue <= 40) or (B - G) < 15 or
            (B - R) < 15) and color.intensity < 80:
            return True
        return False

    def is_skin_hair_block(self, block, block_type):
        '''
        Return true if the block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        is a skin/hair-block - it is if a majority (as per the threshold attribute) of the pixels in the block are
        skin/hair colored. 'block_type' says whether we are testing for a skin block ('s') or a hair block ('h).
        '''
        # Your code
        count_pixels = 0
        for pixely in range(block[1], block[1] + self.block_size):
            for pixelx in range(block[0], block[0] + self.block_size):
                if pixelx < self.pyim.size()[0] and pixely < self.pyim.size(
                )[1]:
                    color = self.pyim.get_rgba(pixelx, pixely)
                    if block_type == 's' and self.is_skin(color):
                        count_pixels += 1
                    elif block_type == 'h' and self.is_hair(color):
                        count_pixels += 1
        if ((count_pixels * 1.0) /
            (self.block_size * self.block_size * 1.0)) >= self.majority:
            return True
        return False

    def add_neighbour_blocks(self, block, graph):
        '''
        Given a block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        and a graph (could be a hair or a skin graph), add edges from the current block to its neighbours
        on the image that are already nodes of the graph
        Check blocks to the left, top-left and top of the current block and if any of these blocks is in the
        graph (means the neighbour is also of the same type - skin or hair) add an edge from the current block
        to the neighbour.
        '''
        # Your code
        valid_rangeX = range(self.pyim.size()[0])
        valid_rangeY = range(self.pyim.size()[1])
        length = self.block_size

        if self.is_skin_hair_block(block, 's'):
            if (block[0] - length) in valid_rangeX and self.is_skin_hair_block(
                (block[0] - length, block[1]), 's'):
                self.skin.add_edge((block[0] - length, block[1]), block)
            if (block[0] - length) in valid_rangeX and (
                    block[1] -
                    length) in valid_rangeY and self.is_skin_hair_block(
                        (block[0] - length, block[1] - length), 's'):
                self.skin.add_edge((block[0] - length, block[1] - length),
                                   block)
            if (block[1] - length) in valid_rangeY and self.is_skin_hair_block(
                (block[0], block[1] - length), 's'):
                self.skin.add_edge((block[0], block[1] - length), block)
            if (block[0] + length) in valid_rangeX and (
                    block[1] -
                    length) in valid_rangeY and self.is_skin_hair_block(
                        (block[0] + length, block[1] - length), 's'):
                self.skin.add_edge((block[0] + length, block[1] - length),
                                   block)

        elif self.is_skin_hair_block(block, 'h'):
            #self.hair.add_node(block)
            if (block[0] - length) in valid_rangeX and self.is_skin_hair_block(
                (block[0] - length, block[1]), 'h'):
                self.hair.add_edge((block[0] - length, block[1]), block)
            if (block[0] - length) in valid_rangeX and (
                    block[1] -
                    length) in valid_rangeY and self.is_skin_hair_block(
                        (block[0] - length, block[1] - length), 'h'):
                self.hair.add_edge((block[0] - length, block[1] - length),
                                   block)
            if (block[1] - length) in valid_rangeY and self.is_skin_hair_block(
                (block[0], block[1] - length), 'h'):
                self.hair.add_edge((block[0], block[1] - length), block)
            if (block[0] + length) in valid_rangeX and (
                    block[1] -
                    length) in valid_rangeY and self.is_skin_hair_block(
                        (block[0] + length, block[1] - length), 'h'):
                self.hair.add_edge((block[0] + length, block[1] - length),
                                   block)

    def make_block_graph(self):
        '''
        Return the skin and hair graphs - nodes are the skin/hair blocks respectively
        Initialize skin and hair graphs. For every block if it is a  skin(hair) block
        add edges to its neighbour skin(hair) blocks in the corresponding graph
        For this to work the blocks have to be traversed in the top->bottom, left->right order
        '''
        # Your code

        for col in range(0, self.pyim.size()[1], self.block_size):
            for row in range(0, self.pyim.size()[0], self.block_size):
                if self.is_skin_hair_block((row, col), 's'):
                    self.skin.add_node((row, col))
                    self.add_neighbour_blocks((row, col), self.skin.adjacency)
                elif self.is_skin_hair_block((row, col), 'h'):
                    self.hair.add_node((row, col))
                    self.add_neighbour_blocks((row, col), self.hair.adjacency)

    def find_bounding_box(self, component):
        '''
        Return the bounding box - a box is a pair of tuples - ((minx, miny), (maxx, maxy)) for the component
        Argument 'component' - is just the list of blocks in that component where each block is represented by the
        coordinates of its top-left pixel.
        '''
        # Your code
        minx = miny = maxx = maxy = component[0][0]
        for tup in component:
            if tup[0] > maxx:
                maxx = tup[0]
            if tup[0] < minx:
                minx = tup[0]
            if tup[1] > maxy:
                maxy = tup[1]
            if tup[1] < miny:
                miny = tup[1]

        return ((minx, miny), (maxx, maxy))

    def skin_hair_match(self, skin_box, hair_box):
        '''
        Return True if the skin-box and hair-box given are matching according to one of the pre-defined patterns
        '''
        # Your code
        hair_minx = hair_box[0][0]
        hair_miny = hair_box[0][1]
        hair_maxx = hair_box[1][0]
        hair_maxy = hair_box[1][1]
        skin_minx = skin_box[0][0]
        skin_miny = skin_box[0][1]
        skin_maxx = skin_box[1][0]
        skin_maxy = skin_box[1][1]
        '''if skin_miny == hair_maxy :
            return True
        if skin_miny > hair_miny and skin_minx > hair_minx and skin_maxy < hair_maxy and skin_maxx > hair_maxx:
            return True
        if hair_miny < skin_miny and skin_miny < hair_maxy:
            return True
        if hair_maxy == skin_maxy :
            return True
        if skin_miny <= hair_miny and hair_maxy <= skin_maxy:
            return True'''
        return True

    def detect_faces(self):
        '''
        Main method - to detect faces in the image that this class was initialized with
        Return list of face boxes - a box is a pair of tuples - ((minx, miny), (maxx, maxy))
        Algo: (i) Make block graph (ii) get the connected components of the graph (iii) filter the connected components
        (iv) find bounding box for each component (v) Look for matches between face and hair bounding boxes
        Return the list of face boxes that have matching hair boxes
        '''
        # Your code
        index = 0
        self.make_block_graph()
        skin_components = self.skin.get_connected_components()
        hair_components = self.hair.get_connected_components()
        skin_components = skin_components[::-1]
        hair_components = hair_components[::-1]
        for comp in skin_components:
            if len(comp) >= self.min_component_size:
                break
            index += 1
        skin_components = skin_components[index:]

        index = 0
        for comp in hair_components:
            if len(comp) >= self.min_component_size:
                break
            index += 1
        hair_components = hair_components[index:]

        skin_boxes = []
        hair_boxes = []
        for component in skin_components:
            skin_boxes.append(self.find_bounding_box(component))

        for component in hair_components:
            hair_boxes.append(self.find_bounding_box(component))

        faces = []
        for skin_box in skin_boxes:
            for hair_box in hair_boxes:
                if self.skin_hair_match(skin_box, hair_box):
                    faces.append(skin_box)

        return faces, hair_boxes

    def mark_box(self, box, color):
        '''
        Mark the box (same as in the above methods) with a given color (given as a raw triple)
        This is just a one-pixel wide line showing the box.
        '''
        # Your code
        minx = box[0][0]
        miny = box[0][1]
        maxx = box[1][0]
        maxy = box[1][1]
        if minx > self.pyim.size()[0]:
            minx = self.pyim.size()[0] - 1
        if maxx > self.pyim.size()[0]:
            maxx = self.pyim.size()[0] - 1
        if miny > self.pyim.size()[1]:
            miny = self.pyim.size()[1] - 1
        if maxy > self.pyim.size()[1]:
            maxy = self.pyim.size()[1] - 1

        for pixel in range(minx, maxx + 1):
            self.pyim.set(pixel, miny, color)
        for pixel in range(minx, maxx + 1):
            self.pyim.set(pixel, maxy, color)
        for pixel in range(miny, maxy + 1):
            self.pyim.set(minx, pixel, color)
        for pixel in range(miny, maxy + 1):
            self.pyim.set(maxx, pixel, color)

    def mark_faces(self, marked_file):
        '''
        Detect faces and mark each face detected -- mark the bounding box of each face in red
        and save the marked image in a new file
        '''
        # Your code
        faces, hair = self.detect_faces()
        for box in faces:
            self.mark_box(box, (255, 0, 0))
        '''for box in hair:
            self.mark_box(box, (0, 255, 0))'''
        self.pyim.save(marked_file)
Example #12
0
class MeanShift(object):

    '''This class should host all images related to the process of performing a
    meanshift segmentation on an image, housing functions that will perform
    such operation. It consists of a base image and two derived images, a
    filtered one and a segmented one.'''

    def __init__(self):

        self.original = None
        self.msFilter = None
        self.msSegment = None

    # ------------------------------------------------------------------------
    # Input and Output functions
    # ------------------------------------------------------------------------

    def loadFile(self, filepath):

        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadFile(filepath)

    def loadImage(self, image):

        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadImage(image)

    def saveFile(self, filepath):

        '''This function should save to disk the class' current state, by
        saving not only the original file, but also the meanshifted ones, if
        they exist.'''

        # Separate name from extension
        path = filepath.split('.')
        extension = '.' + path[-1]
        path = "".join(path[:-1]) + '-'

        # Save original
        self.original.saveFile(path + "original" + extension)

        # Save meanshift filter
        if self.msFilter is not None:
            self.msFilter.saveFile(path + "filter" + extension)

        # Save meanshift segment
        if self.msSegment is not None:
            self.msSegment.saveFile(path + "segment" + extension)

    # ------------------------------------------------------------------------
    # Meanshift functions
    # ------------------------------------------------------------------------

    def kernelIteration(self, tid, hs, hr, k, kernels, newKernels, eps,
                        kdTree, checks, ttot, img, valid):

        '''This function is responsible for computing the new kernel position
        for kernels [id*size:(id+1)*size]'''

        for j in np.arange(tid*(img.height)/ttot, (tid+1)*(img.height)/ttot):
            for i in np.arange(img.width):

                # If not valid, do not iterate again
                if not valid[j][i]:
                    newKernels[j][i] = kernels[j][i]
                    checks[j][i] = 0
                    continue

                # Initialize variables
                mainSum = np.zeros((5), dtype="float64")
                weightSum = 0.0
                base = kernels[j][i]

                # Query kdtree for the k nearest neighbors
                dist, index = kdTree.query(base,
                                           k+1,
                                           distance_upper_bound=hr,
                                           n_jobs=-1)

                # Iterate each pair distance, index found on the query
                for d, ind in itertools.izip(dist[1:], index[1:]):

                    # Get 2D indexes from 1D one
                    if ind == kdTree.n:
                        continue
                    y = ind/img.width
                    x = ind - y*img.width
                    data = kernels[y][x]

                    # Compute spatial component
                    weightS = data[:2] - base[:2]
                    weightS = weightS/(1.0 * hs)
                    weightS = -sum(weightS ** 2)
                    weightS = np.exp(weightS)

                    # Compute color component
                    weightR = data[2:] - base[2:]
                    weightR = weightR/(1.0 * hr)
                    weightR = -sum(weightR ** 2)
                    weightR = np.exp(weightR)

                    # Resulting weight is product
                    weight = weightS * weightR

                    # Add things to variables
                    weightSum += weight
                    mainSum += data * weight

                # Once done, result is average, if zero, kernel doesnt move
                if weightSum == 0:
                    print i, j
                    newKernels[j][i] = kernels[j][i]
                    checks[j][i] = 0
                    valid[j][i] = False
                    continue
                result = mainSum / weightSum
                newKernels[j][i] = result
                norm = np.linalg.norm(result - base)
                checks[j][i] = norm
                if norm < eps:
                    valid[j][i] = False

    def meanshiftFilter(self, hs, hr, k, eps, lim, ops):

        '''This function should apply a meanshift filter throughout the image.
        It takes in several arguments, including softening coefficients hs and
        hr, respectively for regular space and color space; k, the number of
        nearest neighbors to look at when filtering a local kernel; and eps,
        the threshold used to stop the iterating.'''

        # Make copies of original image
        img = self.original.copy()
        kerns = self.original.copy()

        # Initialize kernel matrix
        kernels = np.empty((img.height, img.width, 5), dtype="float64")

        # Fill first dimension with kernel's Y coordinate value
        for j in np.arange(img.height):
            kernels[j, :, 0] = j

        # Fill second dimension with kernel's X coordinate value
        for i in np.arange(img.width):
            kernels[:, i, 1] = i

        # Fill remaining dimensions with LUV values for each pixel
        # kerns.convertRGBtoLUV()
        kerns.pixels = kerns.pixels.astype("float64")
        kernels[:, :, 2:] = kerns.pixels

        # Initialize variables
        checks = np.zeros((img.height, img.width))
        boolchecks = checks.astype("bool")
        size = img.height * img.width
        kdTree = None
        newKernels = None
        firstKernel = kernels.copy()

        # Initialize list of valid points for iteration
        valid = np.ones((img.height, img.width), dtype="bool")

        # Iteration count
        count = 0

        # Wait until all vectors' magnitudes go below threshold
        while False in boolchecks:

            # Interrupt if exceeps iteration limit
            count += 1
            if count > ops:
                break

            # Iniitalize vectors for this iteration
            newKernels = np.empty((img.height, img.width, 5), dtype="float64")

            # Initialize kdtree with linearized matrix and optimized for space
            kdTree = sp.spatial.cKDTree(kernels.reshape((size, 5)),
                                        compact_nodes=True,
                                        balanced_tree=True)

            # Initialize threads, iterate every kernel
            threads = []
            for i in range(4):
                t = threading.Thread(name=str(i), target=self.kernelIteration,
                                     args=(i, hs, hr, k, kernels, newKernels,
                                           eps, kdTree, checks, 4, img, valid))
                t.setDaemon(True)  # Doesn't block main program
                threads.append(t)
                t.start()

            # Wait for threads to finish
            for t in threads:
                t.join()

            # Update kernels
            kernels = newKernels

            print "DEBUG: Checks min/avg/max:", checks.min(), \
                np.mean(checks), checks.max()

            # Maps check values to epsilon
            boolchecks = checks <= eps

            # Check how many kernels did not move
            stopped = np.count_nonzero(boolchecks)

            print "DEBUG: Fixed kernels:", stopped

            # Interrupt if limit is achieved
            if stopped >= (lim*size):
                break

        # Once done, copy the original pixel matrix
        kernelsCopy = kernels.astype("uint64")
        colors = img.pixels.copy()

        # Iterate every pixel, changing its color from the one given by its
        # kernel after iterating. Rounds down no matter the decimal part
        for j in np.arange(img.height):
            for i in np.arange(img.width):
                y, x = kernelsCopy[j][i][:2]
                img.pixels[j][i] = colors[y][x]

        # Store result in class variable
        img.updateImage()
        self.msFilter = img.copy()

    def meanshiftSegment(self, hr, filepath=None):

        '''This function should perform a segmentation in an image based on
        each pixel's color. It groups together neighbor pixels that have
        similar colors. By default, it uses the filtered image in the class
        instance, but it can load a file specified as a argument.'''

        # Check where to load image from, default is class instance
        if self.msFilter is None:
            # Check if filepath is specified
            if filepath is None:
                # Neither sources are valid, abort
                print "\nERROR: Please either " + \
                    "specify a file or run meanshiftFilter.\n"
                return
            else:
                # Load image from filepath
                img = PyImage()
                img.loadFile(filepath)
        else:
            # Load image from class
            img = self.msFilter.copy()

        # Start group matrix with zeros
        groups = np.zeros((img.height, img.width), dtype="int32")
        groups -= 1
        pixels = []  # List of pixels per group
        colors = []  # Average color per group
        lastGroup = 0

        # Iterate pixels, assigning group to each pixel
        for j in np.arange(img.height):
            for i in np.arange(img.width):

                # If pixel has no group, set a new group for it
                if groups[j][i] == -1:
                    groups[j][i] = lastGroup
                    lastGroup += 1
                    pixels.append([(j, i)])
                    colors.append(img.pixels[j][i].astype("float64"))

                # Get pixel neighbors
                neighbors = []
                if j:
                    neighbors.append((j-1, i, img.pixels[j-1][i]))
                    if i:
                        neighbors.append((j-1, i-1, img.pixels[j-1][i-1]))
                    if i < img.width - 1:
                        neighbors.append((j-1, i+1, img.pixels[j-1][i+1]))
                if j < img.height - 1:
                    neighbors.append((j+1, i, img.pixels[j+1][i]))
                    if i:
                        neighbors.append((j+1, i-1, img.pixels[j+1][i-1]))
                    if i < img.width - 1:
                        neighbors.append((j+1, i+1, img.pixels[j+1][i+1]))
                if i:
                    neighbors.append((j, i-1, img.pixels[j][i-1]))
                if i < img.width - 1:
                    neighbors.append((j, i+1, img.pixels[j][i+1]))

                # For each neighbor, check if color is similar
                group = groups[j][i]
                for neighbor in neighbors:
                    # Compute color difference
                    cDiff = colors[group] - neighbor[2]
                    cDiff = sum(cDiff ** 2)
                    cDiff **= 0.5
                    if cDiff < hr:
                        # Color is similar in all 3 channels, put neighbor as
                        # same group as current pixel
                        groups[neighbor[0]][neighbor[1]] = group
                        oldGroupLen = len(pixels[group])
                        pixels[group].append((neighbor[0], neighbor[1]))
                        color = colors[group]
                        color *= oldGroupLen
                        color += neighbor[2]
                        color /= oldGroupLen + 1
                        colors[group] = color

        print lastGroup

        # Iterate groups
        for g in range(lastGroup):
            # Iterate pixels, updating their colors
            color = colors[g].astype("uint8")
            for pixel in pixels[g]:
                img.pixels[pixel[0]][pixel[1]] = color

        # Store result
        self.msSegment = img
Example #13
0
    def meanshiftSegment(self, hr, filepath=None):

        '''This function should perform a segmentation in an image based on
        each pixel's color. It groups together neighbor pixels that have
        similar colors. By default, it uses the filtered image in the class
        instance, but it can load a file specified as a argument.'''

        # Check where to load image from, default is class instance
        if self.msFilter is None:
            # Check if filepath is specified
            if filepath is None:
                # Neither sources are valid, abort
                print "\nERROR: Please either " + \
                    "specify a file or run meanshiftFilter.\n"
                return
            else:
                # Load image from filepath
                img = PyImage()
                img.loadFile(filepath)
        else:
            # Load image from class
            img = self.msFilter.copy()

        # Start group matrix with zeros
        groups = np.zeros((img.height, img.width), dtype="int32")
        groups -= 1
        pixels = []  # List of pixels per group
        colors = []  # Average color per group
        lastGroup = 0

        # Iterate pixels, assigning group to each pixel
        for j in np.arange(img.height):
            for i in np.arange(img.width):

                # If pixel has no group, set a new group for it
                if groups[j][i] == -1:
                    groups[j][i] = lastGroup
                    lastGroup += 1
                    pixels.append([(j, i)])
                    colors.append(img.pixels[j][i].astype("float64"))

                # Get pixel neighbors
                neighbors = []
                if j:
                    neighbors.append((j-1, i, img.pixels[j-1][i]))
                    if i:
                        neighbors.append((j-1, i-1, img.pixels[j-1][i-1]))
                    if i < img.width - 1:
                        neighbors.append((j-1, i+1, img.pixels[j-1][i+1]))
                if j < img.height - 1:
                    neighbors.append((j+1, i, img.pixels[j+1][i]))
                    if i:
                        neighbors.append((j+1, i-1, img.pixels[j+1][i-1]))
                    if i < img.width - 1:
                        neighbors.append((j+1, i+1, img.pixels[j+1][i+1]))
                if i:
                    neighbors.append((j, i-1, img.pixels[j][i-1]))
                if i < img.width - 1:
                    neighbors.append((j, i+1, img.pixels[j][i+1]))

                # For each neighbor, check if color is similar
                group = groups[j][i]
                for neighbor in neighbors:
                    # Compute color difference
                    cDiff = colors[group] - neighbor[2]
                    cDiff = sum(cDiff ** 2)
                    cDiff **= 0.5
                    if cDiff < hr:
                        # Color is similar in all 3 channels, put neighbor as
                        # same group as current pixel
                        groups[neighbor[0]][neighbor[1]] = group
                        oldGroupLen = len(pixels[group])
                        pixels[group].append((neighbor[0], neighbor[1]))
                        color = colors[group]
                        color *= oldGroupLen
                        color += neighbor[2]
                        color /= oldGroupLen + 1
                        colors[group] = color

        print lastGroup

        # Iterate groups
        for g in range(lastGroup):
            # Iterate pixels, updating their colors
            color = colors[g].astype("uint8")
            for pixel in pixels[g]:
                img.pixels[pixel[0]][pixel[1]] = color

        # Store result
        self.msSegment = img
Example #14
0
class FaceDetector(object):
    '''
    classdocs
    '''

    def __init__(self, filename, block_size = 5, min_component_size = 10, majority = 0.5):
        '''
        Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
        threshold to decide how many skin color pixels are required to declare a block as a skin-block
        and min number of blocks required for a component. The majority argument says what fraction of
        the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
        0.5 (half).
        '''
        # Your code
        self.filename=filename
        self.block_size=block_size
        self.majority=majority
        self.min_component_size=min_component_size
        self.pyimage_obj = PyImage(filename)
        
        

    def skin_green_limits(self, red):
        '''
        Return the limits of normalized green given the normalized red component as a tuple (min, max)
        '''
        return ((-0.776*red*red + 0.5601*red + 0.18), (-1.376*red*red + 1.0743*red + 0.2))


    def is_skin(self, pixel_color):
        '''
        Given the pixel color (as a Color object) return True if it represents the skin color
        Color is skin if hue in degrees is (> 240 or less than or equal to 20) and 
        green is in the green limits and it is not white
        '''
        # Your code
        pix_color=pixel_color.hue_degrees()
        green=pixel_color.g
        limit=self.skin_green_limits(pixel_color.r)
        if(pix_color!=255):
            if(pix_color>240 or pix_color<=20):
                if (green>=limit[0] and green<=limit[1]):
                    return True
        return False            


    def is_hair(self, pixel_color):
        '''
        Return True if the pixel color represents hair - it is if intensity < 80 and ((B-G)<15 or (B-R)<15 or
        hue is between 20 and 40)
        '''
        # Your code
        rgb=pixel_color.rgb_abs()
        if(pixel_color.intensity<80):
            if((rgb[2]-rgb[1])<15 or (rgb[2]-rgb[0])<15 or pixel_color.hue() in range(20,40)):
                return True
        return False    
            

    def is_skin_hair_block(self, block, block_type):
        '''
        Return true if the block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        is a skin/hair-block - it is if a majority (as per the threshold attribute) of the pixels in the block are
        skin/hair colored. 'block_type' says whether we are testing for a skin block ('s') or a hair block ('h).
        '''
        # Your code
        pyimage_object=PyImage(self.filename)
        k=pyimage_object.size()
        pixel_x=block[0]
        pixel_y=block[1]
        lst_skin=[]
        lst_hair=[]
        temp_x=0
        while(pixel_x<k[0]):
            temp_y=0
            while(pixel_y<k[1] and temp_y<self.block_size):
                rgb_a=pyimage_object.get_rgba(pixel_x,pixel_y)
                pixel_color=Color(rgb_a)
                if(self.is_skin(pixel_color)):
                    lst_skin.append((pixel_x,pixel_y))
                elif(self.is_hair(pixel_color)):
                    lst_hair.append((pixel_x,pixel_y))  
                pixel_y+=1
                temp_y+=1
            pixel_x+=1
            temp_x+=1
            if(len(lst_skin)>self.majority*self.block_size and  block_type=='s'):
                return True
            elif(len(lst_hair)>self.majority*self.block_size and  block_type=='h'):
                return True
            else:
                return False
                
    def add_neighbour_blocks(self, block, graph):
        '''
        Given a block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        and a graph (could be a hair or a skin graph), add edges from the current block to its neighbours
        on the image that are already nodes of the graph
        Check blocks to the left, top-left and top of the current block and if any of these blocks is in the
        graph (means the neighbour is also of the same type - skin or hair) add an edge from the current block
        to the neighbour.
        '''
        # Your code
        pyimage_object=PyImage(self.filename)
        k=pyimage_object.size()
        if(self.is_skin_hair_block(block, 's')):
            if((block[0]-self.block_size)<k[0] and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0]-self.block_size,block[1]), 's')):
                graph.add_edge(block, (block[0]-self.block_size,block[1]))
            if((block[1]-self.block_size)<k[1] and (block[1]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
                graph.add_edge(block, (block[0],block[1]-self.block_size))   
            if((block[1]-self.block_size)<k[1] and (block[0]-self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
                graph.add_edge(block, (block[0]-self.block_size,block[1]-self.block_size))
            if((block[1]-self.block_size)<k[1] and (block[0]+self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]+self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 's')):
                graph.add_edge(block, (block[0]+self.block_size,block[1]-self.block_size))        
        if(self.is_skin_hair_block(block, 'h')):
            if((block[0]-self.block_size)<k[0] and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0]-self.block_size,block[1]), 'h')):
                graph.add_edge(block, (block[0]-self.block_size,block[1]))
            if((block[1]-self.block_size)<k[1] and (block[1]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
                graph.add_edge(block, (block[0],block[1]-self.block_size))   
            if((block[1]-self.block_size)<k[1] and (block[0]-self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]-self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
                graph.add_edge(block, (block[0]-self.block_size,block[1]-self.block_size))
            if((block[1]-self.block_size)<k[1] and (block[0]+self.block_size)<k[0] and (block[1]-self.block_size)>=0 and (block[0]+self.block_size)>=0 and self.is_skin_hair_block((block[0],block[1]-self.block_size), 'h')):
                graph.add_edge(block, (block[0]+self.block_size,block[1]-self.block_size))        
         
               
    def make_block_graph(self):
        '''
        Return the skin and hair graphs - nodes are the skin/hair blocks respectively
        Initialize skin and hair graphs. For every block if it is a  skin(hair) block
        add edges to its neighbour skin(hair) blocks in the corresponding graph
        For this to work the blocks have to be traversed in the top->bottom, left->right order
        '''
        # Your code
        graph_object_skin=Graph(None)
        graph_object_hair=Graph(None)
        pyimage_object=PyImage(self.filename)
        k=pyimage_object.size()
        i=0
        while(i<k[0]):
            # 3
            j=0
            while(j<k[1]):
                #  4
                if(self.is_skin_hair_block((i,j),'s')):
                    graph_object_skin.add_node((i,j))
                    self.add_neighbour_blocks((i,j),graph_object_skin)
                j+=self.block_size
            i+=self.block_size
        i=0
        while(i<k[0]):
            #  5
            j=0
            while(j<k[1]):
                # 6
                if(self.is_skin_hair_block((i,j),'h')):
                    graph_object_hair.add_node((i,j))
                    self.add_neighbour_blocks((i,j),graph_object_hair)
                j+=self.block_size
            i+=self.block_size
        
        return graph_object_skin, graph_object_hair


    def find_bounding_box(self, component):
        '''
        Return the bounding box - a box is a pair of tuples - ((minx, miny), (maxx, maxy)) for the component
        Argument 'component' - is just the list of blocks in that component where each block is represented by the
        coordinates of its top-left pixel.
        '''
        # Your code
        temp_y=[]
        temp_x=[]
        size_component=len(component)
        i=0
        while(i<size_component):
            # 7
            temp_y+=[component[i][1]]
            i+=1
        i=0    
        while(i<size_component):
            # 7
            temp_x+=[component[i][0]]
            i+=1    
        return ((min(temp_x),min(temp_y)),(max(temp_x),max(temp_y))) 
       
    
    def skin_hair_match(self, skin_box, hair_box):
        '''
        Return True if the skin-box and hair-box given are matching according to one of the pre-defined patterns
        '''
        # Your code
        skin_coord=self.find_bounding_box(skin_box)
        hair_coord=self.find_bounding_box(hair_box)
        min_x_skin=skin_coord[0][0]
        min_y_skin=skin_coord[0][1]
        max_x_skin=skin_coord[1][0]
        max_y_skin=skin_coord[1][1]
        min_x_hair=hair_coord[0][0]
        min_y_hair=hair_coord[0][1]
        max_x_hair=hair_coord[1][0]
        max_y_hair=hair_coord[1][1]
        if min_x_skin>min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin<max_y_hair :
            return True #2
        elif min_x_skin>min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin>max_y_hair :
            return True #3
        elif min_x_skin>min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin==max_y_hair :
            return True #4
        elif min_x_skin<min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin>max_y_hair :
            return True #5
        elif min_x_skin>min_x_hair and max_x_skin>max_x_hair and min_y_skin>min_y_hair and max_y_skin>max_y_hair :
            return True #6
        elif min_x_skin==min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin>max_y_hair :
            return True #7
        elif min_x_skin>min_x_hair and max_x_skin==max_x_hair and min_y_skin>min_y_hair and max_y_skin>max_y_hair :
            return True #8
        elif min_x_skin>min_x_hair and max_x_skin<max_x_hair and min_y_skin<min_y_hair and max_y_skin>max_y_hair :
            return True #9
        elif min_x_skin>min_x_hair and max_x_skin>max_x_hair and min_y_skin<min_y_hair and max_y_skin>max_y_hair :
            return True #10
        elif min_x_skin<min_x_hair and max_x_skin<max_x_hair and min_y_skin<min_y_hair and max_y_skin>max_y_hair :
            return True #11
        elif min_x_skin==min_x_hair and max_x_skin<max_x_hair and min_y_skin>min_y_hair and max_y_skin==max_y_hair :
            return True #12
        elif min_x_skin>min_x_hair and max_x_skin--max_x_hair and min_y_skin>min_y_hair and max_y_skin==max_y_hair :
            return True #13
        elif min_x_skin<min_x_hair and max_x_skin>max_x_hair and min_y_skin==max_y_hair:
            return True #14
        elif min_x_skin<min_x_hair and max_x_skin==max_x_hair and min_y_skin==max_y_hair:
            return True #15
        elif min_x_skin==min_x_hair and max_x_skin>max_x_hair and min_y_skin==max_y_hair:
            return True #16
        elif min_x_skin==min_x_hair and max_x_skin==max_x_hair and min_y_skin==max_y_hair:
            return True #1
        else:
            return False
        
    def detect_faces(self):
        '''
        Main method - to detect faces in the image that this class was initialized with
        Return list of face boxes - a box is a pair of tuples - ((minx, miny), (maxx, maxy))
        Algo: (i) Make block graph (ii) get the connected components of the graph (iii) filter the connected components
        (iv) find bounding box for each component (v) Look for matches between face and hair bounding boxes
        Return the list of face boxes that have matching hair boxes
        '''
        # Your code
        #1
        skin_object, hair_object=self.make_block_graph()
        skin_list=skin_object.get_connected_components()
        hair_list=hair_object.get_connected_components()
        final_list=[]
        skin_list_final=[]
        hair_list_final=[]
        for block in skin_list:
            if(len(block)>self.min_component_size):
                bounding_tuple=self.find_bounding_box(block)
                skin_list_final.append(bounding_tuple)
        for block in hair_list:
            if(len(block)>self.min_component_size):
                bounding_tuple=self.find_bounding_box(block)
                hair_list_final.append(bounding_tuple)        
        for s in skin_list_final:
            for h in hair_list_final:
                if(self.skin_hair_match(s,h)==True):
                    if s not in final_list:
                        final_list.append(s)
        #print final_list
        return final_list            
                
        
    
    
    def mark_box(self, box, color):
        '''
        Mark the box (same as in the above methods) with a given color (given as a raw triple)
        This is just a one-pixel wide line showing the box.
        '''
        # Your code
        min_x=box[0][0]
        min_y=box[0][1]
        max_x=box[1][0]
        max_y=box[1][1]
        i=min_x
        while(i<max_x):
            # 8
            self.pyimage_obj.set(i,min_y,(254,0,0))
            self.pyimage_obj.set(i,max_y,(254,0,0))
            i+=1
        i=min_y
        while(i<max_y):
            # 9
            self.pyimage_obj.set(min_x,i,(254,0,0))
            self.pyimage_obj.set(max_x,i,color)
            i+=1


    def mark_faces(self, marked_file):
        '''
        Detect faces and mark each face detected -- mark the bounding box of each face in red
        and save the marked image in a new file
        '''
        # Your code
        face_list=self.detect_faces()
        for i in face_list:
            self.mark_box(i, (254,0,0))
        self.pyimage_obj.save(marked_file)
class FaceDetector(object):
    '''
    classdocs
    '''

    def __init__(self, filename, block_size = 5,
                 min_component_size = 10, majority = 0.5):
        '''
        Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
        threshold to decide how many skin color pixels are required to declare a block as a skin-block
        and min number of blocks required for a component. The majority argument says what fraction of
        the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
        0.5 (half).
        '''
        
        
        self.input_image = PyImage(filename)
        self.block_size = block_size
        self.threshold = block_size * block_size * majority
        self.component_length = min_component_size
        self.majority = majority

    def skin_green_limits(self, red):
        '''
        Return the limits of normalized green given the normalized red component as a tuple (min, max)
        '''
        return ((-0.776*red.norm_r*red.norm_r + 0.5601*red.norm_r + 0.18),
                (-1.376*red.norm_r*red.norm_r + 1.0743*red.norm_r + 0.2))


    def is_skin(self, pixel_color):
        '''
        Given the pixel color (as a Color object) return True if it represents the skin color
        Color is skin if hue in degrees is (> 240 or less than or equal to 20) and 
        green is in the green limits and it is not white
        '''
        
    
        if((pixel_color.red > 95) and (pixel_color.green > 40)
           and (pixel_color.blue > 20)
           and ((max(pixel_color.red, pixel_color.green, pixel_color.blue)-
            min(pixel_color.red, pixel_color.green, pixel_color.blue)) > 15 )
           and (pixel_color.red-pixel_color.green > 15)
           and (pixel_color.red > pixel_color.green)\
           and (pixel_color.red > pixel_color.blue)):
            return True
        


    def is_hair(self, pixel_color):
        '''
        Return True if the pixel color represents hair - it is if intensity < 80 and ((B-G)<15 or (B-R)<15 or
        hue is between 20 and 40)
        '''
        
        
        if (pixel_color.intensity < 80 
            and ((pixel_color.norm_b - pixel_color.norm_g < 15)
            or (pixel_color.norm_b - pixel_color.norm_r < 15)
            or pixel_color.hue() in range (20, 41))):
            
            return True

        return False 


    def is_skin_hair_block(self, block, block_type):
        '''
        Return true if the block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        is a skin/hair-block - it is if a majority (as per the threshold attribute) of the pixels in the block are
        skin/hair colored. 'block_type' says whether we are testing for a skin block ('s') or a hair block ('h).
        '''
        
        
        block_check = 0
        
        pixelx = block[0]
        pixely = block[1]        
                
        for pixelx in range (pixelx, pixelx + self.block_size ):
            for pixely in range (pixely, pixely + self.block_size):
                
                if self.input_image.get_rgba(pixelx, pixely) == None:
                    continue
                                
                color_block = Color(self.input_image.get_rgba(pixelx, pixely))
                
                if (self.is_skin(color_block) and block_type == 's'):
                    block_check += 1
                elif (self.is_hair(color_block) and block_type == 'h'):
                    block_check += 1

        return block_check >= self.threshold
            

    def add_neighbour_blocks(self, block, graph):
        '''
        Given a block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        and a graph (could be a hair or a skin graph), add edges from the current block to its neighbours
        on the image that are already nodes of the graph
        Check blocks to the left, top-left and top of the current block and if any of these blocks is in the
        graph (means the neighbour is also of the same type - skin or hair) add an edge from the current block
        to the neighbour.
        '''
        
        
        for direction in ((-self.block_size, 0),
                          (-self.block_size, -self.block_size),
                          (0, -self.block_size),
                          (self.block_size, -self.block_size)):
            
            if (graph.is_node((block[0] + direction[0],
                block[1] + direction[1]))):
                
                graph.add_directed_edge(block, (block[0] + direction[0],
                                        block[1] + direction[1]))			


    def make_block_graph(self):
        '''
        Return the skin and hair graphs - nodes are the skin/hair blocks respectively
        Initialize skin and hair graphs. For every block if it is a  skin(hair) block
        add edges to its neighbour skin(hair) blocks in the corresponding graph
        For this to work the blocks have to be traversed in the top->bottom, left->right order
        '''
        
        
        comparison = lambda x, y : (1 if (x[0] > y[0])
                     else (0 if x[0]==y[0] else -1))
        
        skin_graph = Graph(comparison)
        hair_graph = Graph(comparison)
        
        pixelx = 0
        pixely = 0
        
        while pixely <= (self.input_image.size()[1] - self.block_size):

            while pixelx <= (self.input_image.size()[0] - self.block_size):

                if self.is_skin_hair_block((pixelx, pixely), 's'):
                    skin_graph.add_node((pixelx, pixely))
                    self.add_neighbour_blocks((pixelx, pixely), skin_graph)
                elif self.is_skin_hair_block((pixelx, pixely), 'h'):
                    hair_graph.add_node((pixelx, pixely))
                    self.add_neighbour_blocks((pixelx, pixely), hair_graph)

                pixelx += self.block_size
            
            pixelx = 0
            pixely += self.block_size
        
        return skin_graph, hair_graph


    def find_bounding_box(self, component):
        '''
        Return the bounding box - a box is a pair of tuples - ((minx, miny), (maxx, maxy)) for the component
        Argument 'component' - is just the list of blocks in that component where each block is represented by the
        coordinates of its top-left pixel.
        '''
        
        pixelx_list = []
        pixely_list = []
        
        for nodes in component:
            pixelx_list.append(nodes[0])
            pixely_list.append(nodes[1])
            
        return (((min(pixelx_list), min(pixely_list)),
                 (max(pixelx_list), max(pixely_list))))			
    
    def inside(self, corners, box):
        '''
        Check if a given coordinate lies inside the given box
        '''
        #print corners
        
        for corner in corners:
            if ((corner[0] >= box[0][0] and corner[1] >= box[0][1])
			        and (corner[0] <= box[1][0] and corner[1] <= box[1][1])):
                
                return True
            
        return False
                    
                    
    def skin_hair_match(self, skin_box, hair_box):
        '''
        Return True if the skin-box and hair-box given are matching according to one of the pre-defined patterns
        '''
        
        corner = [(skin_box[0][0], skin_box[0][1]),
                  (skin_box[0][0], skin_box[1][1]),
                  (skin_box[1][0], skin_box[0][1]),
                  (skin_box[1][0], skin_box[1][1])]
        
        if (self.inside(corner, hair_box)):
            return True

        elif skin_box[0][1] == hair_box[1][1]:
            return True

        else:
            return False


    def detect_faces(self):
        '''
        Main method - to detect faces in the image that this class was initialized with
        Return list of face boxes - a box is a pair of tuples - ((minx, miny), (maxx, maxy))
        Algo: (i) Make block graph (ii) get the connected components of the graph (iii) filter the connected components
        (iv) find bounding box for each component (v) Look for matches between face and hair bounding boxes
        Return the list of face boxes that have matching hair boxes
        '''
        
        
        skin_graph, hair_graph = self.make_block_graph()
        
        skin_components = skin_graph.get_connected_components()
        hair_components = hair_graph.get_connected_components()
        
        filtered_skin = []
        filtered_hair = []
        
        for component in skin_components:
            if len(component) >= self.component_length:
                filtered_skin.append(component)

        for component in hair_components:
            if len(component) >= self.component_length:
                filtered_hair.append(component)
        
        bound_face = []
        bound_hair = []
        
        for block in filtered_skin:
            bound_face.append(self.find_bounding_box(block))
        
        for block in filtered_hair:
            bound_hair.append(self.find_bounding_box(block))
            
    
        del filtered_skin, filtered_hair

        face_box = []
        
        for component in bound_face:
            for match in bound_hair:
                
                if (self.skin_hair_match(component, match)
                    == True):                    
                    face_box.append(component)

        return face_box
    
    def mark_box(self, box, color):
        '''
        Mark the box (same as in the above methods) with a given color (given as a raw triple)
        This is just a one-pixel wide line showing the box.
        '''
                
        for pixelx in range(box[0][0], box[1][0]):
            self.input_image.set(pixelx, box[0][1], color)
        for pixelx in range(box[0][0], box[1][0]):
            self.input_image.set(pixelx, box[1][1], color)
        for pixely in range(box[0][1], box[1][1]):
            self.input_image.set(box[0][0], pixely, color)
        for pixely in range(box[0][1], box[1][1]):
            self.input_image.set(box[1][0], pixely, color)    
    
    def mark_faces(self, marked_file):
        '''
        Detect faces and mark each face detected -- mark the bounding box of each face in red
        and save the marked image in a new file
        '''
        
        
        face_box = self.detect_faces()
            
        for box in face_box:
            self.mark_box(box, (255, 0, 0))

        self.input_image.save(marked_file)
Example #16
0
    def meanshiftSegment(self, hr, filepath=None):
        '''This function should perform a segmentation in an image based on
        each pixel's color. It groups together neighbor pixels that have
        similar colors. By default, it uses the filtered image in the class
        instance, but it can load a file specified as a argument.'''

        # Check where to load image from, default is class instance
        if self.msFilter is None:
            # Check if filepath is specified
            if filepath is None:
                # Neither sources are valid, abort
                print "\nERROR: Please either " + \
                    "specify a file or run meanshiftFilter.\n"
                return
            else:
                # Load image from filepath
                img = PyImage()
                img.loadFile(filepath)
        else:
            # Load image from class
            img = self.msFilter.copy()

        # Start group matrix with zeros
        groups = np.zeros((img.height, img.width), dtype="int32")
        groups -= 1
        pixels = []  # List of pixels per group
        colors = []  # Average color per group
        lastGroup = 0

        # Iterate pixels, assigning group to each pixel
        for j in np.arange(img.height):
            for i in np.arange(img.width):

                # If pixel has no group, set a new group for it
                if groups[j][i] == -1:
                    groups[j][i] = lastGroup
                    lastGroup += 1
                    pixels.append([(j, i)])
                    colors.append(img.pixels[j][i].astype("float64"))

                # Get pixel neighbors
                neighbors = []
                if j:
                    neighbors.append((j - 1, i, img.pixels[j - 1][i]))
                    if i:
                        neighbors.append(
                            (j - 1, i - 1, img.pixels[j - 1][i - 1]))
                    if i < img.width - 1:
                        neighbors.append(
                            (j - 1, i + 1, img.pixels[j - 1][i + 1]))
                if j < img.height - 1:
                    neighbors.append((j + 1, i, img.pixels[j + 1][i]))
                    if i:
                        neighbors.append(
                            (j + 1, i - 1, img.pixels[j + 1][i - 1]))
                    if i < img.width - 1:
                        neighbors.append(
                            (j + 1, i + 1, img.pixels[j + 1][i + 1]))
                if i:
                    neighbors.append((j, i - 1, img.pixels[j][i - 1]))
                if i < img.width - 1:
                    neighbors.append((j, i + 1, img.pixels[j][i + 1]))

                # For each neighbor, check if color is similar
                group = groups[j][i]
                for neighbor in neighbors:
                    # Compute color difference
                    cDiff = colors[group] - neighbor[2]
                    cDiff = sum(cDiff**2)
                    cDiff **= 0.5
                    if cDiff < hr:
                        # Color is similar in all 3 channels, put neighbor as
                        # same group as current pixel
                        groups[neighbor[0]][neighbor[1]] = group
                        oldGroupLen = len(pixels[group])
                        pixels[group].append((neighbor[0], neighbor[1]))
                        color = colors[group]
                        color *= oldGroupLen
                        color += neighbor[2]
                        color /= oldGroupLen + 1
                        colors[group] = color

        print lastGroup

        # Iterate groups
        for g in range(lastGroup):
            # Iterate pixels, updating their colors
            color = colors[g].astype("uint8")
            for pixel in pixels[g]:
                img.pixels[pixel[0]][pixel[1]] = color

        # Store result
        self.msSegment = img
Example #17
0
class MeanShift(object):
    '''This class should host all images related to the process of performing a
    meanshift segmentation on an image, housing functions that will perform
    such operation. It consists of a base image and two derived images, a
    filtered one and a segmented one.'''
    def __init__(self):

        self.original = None
        self.msFilter = None
        self.msSegment = None

    # ------------------------------------------------------------------------
    # Input and Output functions
    # ------------------------------------------------------------------------

    def loadFile(self, filepath):
        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadFile(filepath)

    def loadImage(self, image):
        '''This function should open the image contained in the specified file
        and reset the class' variables'''

        # Reset variables
        self.msFilter = None
        self.msSegment = None

        # Load image
        self.original = PyImage()
        self.original.loadImage(image)

    def saveFile(self, filepath):
        '''This function should save to disk the class' current state, by
        saving not only the original file, but also the meanshifted ones, if
        they exist.'''

        # Separate name from extension
        path = filepath.split('.')
        extension = '.' + path[-1]
        path = "".join(path[:-1]) + '-'

        # Save original
        self.original.saveFile(path + "original" + extension)

        # Save meanshift filter
        if self.msFilter is not None:
            self.msFilter.saveFile(path + "filter" + extension)

        # Save meanshift segment
        if self.msSegment is not None:
            self.msSegment.saveFile(path + "segment" + extension)

    # ------------------------------------------------------------------------
    # Meanshift functions
    # ------------------------------------------------------------------------

    def kernelIteration(self, tid, hs, hr, k, kernels, newKernels, eps, kdTree,
                        checks, ttot, img, valid):
        '''This function is responsible for computing the new kernel position
        for kernels [id*size:(id+1)*size]'''

        for j in np.arange(tid * (img.height) / ttot,
                           (tid + 1) * (img.height) / ttot):
            for i in np.arange(img.width):

                # If not valid, do not iterate again
                if not valid[j][i]:
                    newKernels[j][i] = kernels[j][i]
                    checks[j][i] = 0
                    continue

                # Initialize variables
                mainSum = np.zeros((5), dtype="float64")
                weightSum = 0.0
                base = kernels[j][i]

                # Query kdtree for the k nearest neighbors
                dist, index = kdTree.query(base,
                                           k + 1,
                                           distance_upper_bound=hr,
                                           n_jobs=-1)

                # Iterate each pair distance, index found on the query
                for d, ind in itertools.izip(dist[1:], index[1:]):

                    # Get 2D indexes from 1D one
                    if ind == kdTree.n:
                        continue
                    y = ind / img.width
                    x = ind - y * img.width
                    data = kernels[y][x]

                    # Compute spatial component
                    weightS = data[:2] - base[:2]
                    weightS = weightS / (1.0 * hs)
                    weightS = -sum(weightS**2)
                    weightS = np.exp(weightS)

                    # Compute color component
                    weightR = data[2:] - base[2:]
                    weightR = weightR / (1.0 * hr)
                    weightR = -sum(weightR**2)
                    weightR = np.exp(weightR)

                    # Resulting weight is product
                    weight = weightS * weightR

                    # Add things to variables
                    weightSum += weight
                    mainSum += data * weight

                # Once done, result is average, if zero, kernel doesnt move
                if weightSum == 0:
                    print i, j
                    newKernels[j][i] = kernels[j][i]
                    checks[j][i] = 0
                    valid[j][i] = False
                    continue
                result = mainSum / weightSum
                newKernels[j][i] = result
                norm = np.linalg.norm(result - base)
                checks[j][i] = norm
                if norm < eps:
                    valid[j][i] = False

    def meanshiftFilter(self, hs, hr, k, eps, lim, ops):
        '''This function should apply a meanshift filter throughout the image.
        It takes in several arguments, including softening coefficients hs and
        hr, respectively for regular space and color space; k, the number of
        nearest neighbors to look at when filtering a local kernel; and eps,
        the threshold used to stop the iterating.'''

        # Make copies of original image
        img = self.original.copy()
        kerns = self.original.copy()

        # Initialize kernel matrix
        kernels = np.empty((img.height, img.width, 5), dtype="float64")

        # Fill first dimension with kernel's Y coordinate value
        for j in np.arange(img.height):
            kernels[j, :, 0] = j

        # Fill second dimension with kernel's X coordinate value
        for i in np.arange(img.width):
            kernels[:, i, 1] = i

        # Fill remaining dimensions with LUV values for each pixel
        # kerns.convertRGBtoLUV()
        kerns.pixels = kerns.pixels.astype("float64")
        kernels[:, :, 2:] = kerns.pixels

        # Initialize variables
        checks = np.zeros((img.height, img.width))
        boolchecks = checks.astype("bool")
        size = img.height * img.width
        kdTree = None
        newKernels = None
        firstKernel = kernels.copy()

        # Initialize list of valid points for iteration
        valid = np.ones((img.height, img.width), dtype="bool")

        # Iteration count
        count = 0

        # Wait until all vectors' magnitudes go below threshold
        while False in boolchecks:

            # Interrupt if exceeps iteration limit
            count += 1
            if count > ops:
                break

            # Iniitalize vectors for this iteration
            newKernels = np.empty((img.height, img.width, 5), dtype="float64")

            # Initialize kdtree with linearized matrix and optimized for space
            kdTree = sp.spatial.cKDTree(kernels.reshape((size, 5)),
                                        compact_nodes=True,
                                        balanced_tree=True)

            # Initialize threads, iterate every kernel
            threads = []
            for i in range(4):
                t = threading.Thread(name=str(i),
                                     target=self.kernelIteration,
                                     args=(i, hs, hr, k, kernels, newKernels,
                                           eps, kdTree, checks, 4, img, valid))
                t.setDaemon(True)  # Doesn't block main program
                threads.append(t)
                t.start()

            # Wait for threads to finish
            for t in threads:
                t.join()

            # Update kernels
            kernels = newKernels

            print "DEBUG: Checks min/avg/max:", checks.min(), \
                np.mean(checks), checks.max()

            # Maps check values to epsilon
            boolchecks = checks <= eps

            # Check how many kernels did not move
            stopped = np.count_nonzero(boolchecks)

            print "DEBUG: Fixed kernels:", stopped

            # Interrupt if limit is achieved
            if stopped >= (lim * size):
                break

        # Once done, copy the original pixel matrix
        kernelsCopy = kernels.astype("uint64")
        colors = img.pixels.copy()

        # Iterate every pixel, changing its color from the one given by its
        # kernel after iterating. Rounds down no matter the decimal part
        for j in np.arange(img.height):
            for i in np.arange(img.width):
                y, x = kernelsCopy[j][i][:2]
                img.pixels[j][i] = colors[y][x]

        # Store result in class variable
        img.updateImage()
        self.msFilter = img.copy()

    def meanshiftSegment(self, hr, filepath=None):
        '''This function should perform a segmentation in an image based on
        each pixel's color. It groups together neighbor pixels that have
        similar colors. By default, it uses the filtered image in the class
        instance, but it can load a file specified as a argument.'''

        # Check where to load image from, default is class instance
        if self.msFilter is None:
            # Check if filepath is specified
            if filepath is None:
                # Neither sources are valid, abort
                print "\nERROR: Please either " + \
                    "specify a file or run meanshiftFilter.\n"
                return
            else:
                # Load image from filepath
                img = PyImage()
                img.loadFile(filepath)
        else:
            # Load image from class
            img = self.msFilter.copy()

        # Start group matrix with zeros
        groups = np.zeros((img.height, img.width), dtype="int32")
        groups -= 1
        pixels = []  # List of pixels per group
        colors = []  # Average color per group
        lastGroup = 0

        # Iterate pixels, assigning group to each pixel
        for j in np.arange(img.height):
            for i in np.arange(img.width):

                # If pixel has no group, set a new group for it
                if groups[j][i] == -1:
                    groups[j][i] = lastGroup
                    lastGroup += 1
                    pixels.append([(j, i)])
                    colors.append(img.pixels[j][i].astype("float64"))

                # Get pixel neighbors
                neighbors = []
                if j:
                    neighbors.append((j - 1, i, img.pixels[j - 1][i]))
                    if i:
                        neighbors.append(
                            (j - 1, i - 1, img.pixels[j - 1][i - 1]))
                    if i < img.width - 1:
                        neighbors.append(
                            (j - 1, i + 1, img.pixels[j - 1][i + 1]))
                if j < img.height - 1:
                    neighbors.append((j + 1, i, img.pixels[j + 1][i]))
                    if i:
                        neighbors.append(
                            (j + 1, i - 1, img.pixels[j + 1][i - 1]))
                    if i < img.width - 1:
                        neighbors.append(
                            (j + 1, i + 1, img.pixels[j + 1][i + 1]))
                if i:
                    neighbors.append((j, i - 1, img.pixels[j][i - 1]))
                if i < img.width - 1:
                    neighbors.append((j, i + 1, img.pixels[j][i + 1]))

                # For each neighbor, check if color is similar
                group = groups[j][i]
                for neighbor in neighbors:
                    # Compute color difference
                    cDiff = colors[group] - neighbor[2]
                    cDiff = sum(cDiff**2)
                    cDiff **= 0.5
                    if cDiff < hr:
                        # Color is similar in all 3 channels, put neighbor as
                        # same group as current pixel
                        groups[neighbor[0]][neighbor[1]] = group
                        oldGroupLen = len(pixels[group])
                        pixels[group].append((neighbor[0], neighbor[1]))
                        color = colors[group]
                        color *= oldGroupLen
                        color += neighbor[2]
                        color /= oldGroupLen + 1
                        colors[group] = color

        print lastGroup

        # Iterate groups
        for g in range(lastGroup):
            # Iterate pixels, updating their colors
            color = colors[g].astype("uint8")
            for pixel in pixels[g]:
                img.pixels[pixel[0]][pixel[1]] = color

        # Store result
        self.msSegment = img
class FaceDetector(object):
    '''
    classdocs
    '''
    def __init__(self,
                 filename,
                 block_size=11,
                 min_component_size=11,
                 majority=0.56):
        '''
        Constructor - keeps input image filename, image read from the file as a PyImage object, block size (in pixels),
        threshold to decide how many skin color pixels are required to declare a block as a skin-block
        and min number of blocks required for a component. The majority argument says what fraction of
        the block pixels must be skin/hair colored for the block to be a skin/hair block - the default value is
        0.5 (half).
        '''
        self.image = PyImage(filename)
        self.block_sz = block_size
        self.min_blocks = min_component_size
        self.fraction_pixel = majority

    def skin_green_limits(self, red):
        '''
        Return the limits of normalized green given the normalized red component as a tuple (min, max)
        '''
        return ((-0.776 * red * red + 0.5601 * red + 0.18),
                (-1.376 * red * red + 1.0743 * red + 0.2))

    def is_skin(self, pixel_color):
        '''
        Given the pixel color (as a Color object) return True if it represents the skin color
        Color is skin if hue in degrees is (> 240 or less than or equal to 20) and 
        green is in the green limits and it is not white
        '''
        hue = pixel_color.hue_degrees()
        abs_rgb = pixel_color.rgb_abs()
        green = pixel_color.g
        green_limits = self.skin_green_limits(pixel_color.r)
        if ((hue > 240 or hue <= 20)
                and (green > green_limits[0] and green < green_limits[1])
                and abs_rgb != (255, 255, 255)):
            return True

    def is_hair(self, pixel_color):
        '''
        Return True if the pixel color represents hair - it is if intensity < 80 and ((B-G)<15 or (B-R)<15 or
        hue is between 20 and 40)
        '''
        hue = pixel_color.hue_degrees()
        intensity = pixel_color.intensity
        abs_rgb = pixel_color.rgb_abs()
        if (intensity < 80 and (abs_rgb[2] - abs_rgb[1] < 15)
                or (abs_rgb[2] - abs_rgb[0] < 15) or (hue > 20 and hue < 40)):
            return True

    def is_skin_hair_block(self, block, block_type):
        '''
        Return true if the block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        is a skin/hair-block - it is if a majority (as per the threshold attribute) of the pixels in the block are
        skin/hair colored. 'block_type' says whether we are testing for a skin block ('s') or a hair block ('h).
        '''
        total_pixels = 0
        if block_type == 's':
            num_pixels = 0
            for width in range(block[0], block[0] + self.block_sz):
                for height in range(block[1], block[1] + self.block_sz):
                    if 0 <= width < self.image.size(
                    )[0] and 0 <= height < self.image.size()[1]:
                        total_pixels += 1
                        pixel_cl = Color(self.image.get_rgba(width, height))
                        if self.is_skin(pixel_cl):
                            num_pixels += 1

        else:
            num_pixels = 0
            for width in range(block[0], block[0] + self.block_sz):
                for height in range(block[1], block[1] + self.block_sz):
                    if 0 <= width < self.image.size(
                    )[0] and 0 <= height < self.image.size()[1]:
                        total_pixels += 1
                        pixel_cl = Color(self.image.get_rgba(width, height))
                        if self.is_hair(pixel_cl):
                            num_pixels += 1

        if (float(num_pixels) / total_pixels >= self.fraction_pixel):
            return True

    def add_neighbour_blocks(self, block, graph):
        '''
        Given a block (given by the argument 'block' which is the coordinate-tuple for the top-left corner)
        and a graph (could be a hair or a skin graph), add edges from the current block to its neighbours
        on the image that are already nodes of the graph
        Check blocks to the left, top-left and top of the current block and if any of these blocks is in the
        graph (means the neighbour is also of the same type - skin or hair) add an edge from the current block
        to the neighbour.
        '''
        left_x = block[0] - self.block_sz
        left_y = block[1]
        topleft_x = block[0] - self.block_sz
        topleft_y = block[1] - self.block_sz
        top_x = block[0]
        top_y = block[1] - self.block_sz
        topright_x = block[0] + self.block_sz
        topright_y = block[1] - self.block_sz

        for (block_x, block_y) in ((left_x, left_y), (topleft_x, topleft_y),
                                   (top_x, top_y), (topright_x, topright_y)):
            if 0 <= block_x < self.image.size(
            )[0] and 0 <= block_y < self.image.size()[1]:
                block2 = (block_x, block_y)
                graph.add_edge(block, block2)

    def make_block_graph(self):
        '''
        Return the skin and hair graphs - nodes are the skin/hair blocks respectively
        Initialize skin and hair graphs. For every block if it is a  skin(hair) block
        add edges to its neighbour skin(hair) blocks in the corresponding graph
        For this to work the blocks have to be traversed in the top->bottom, left->right order
        '''
        skin_graph = Graph()
        hair_graph = Graph()

        for x_cordinate in range(0, self.image.size()[0] - 1, self.block_sz):
            for y_cordinate in range(0,
                                     self.image.size()[1] - 1, self.block_sz):
                if self.is_skin_hair_block((x_cordinate, y_cordinate), 's'):
                    skin_graph.add_node((x_cordinate, y_cordinate))
                    self.add_neighbour_blocks((x_cordinate, y_cordinate),
                                              skin_graph)
                if self.is_skin_hair_block((x_cordinate, y_cordinate), 'h'):
                    hair_graph.add_node((x_cordinate, y_cordinate))
                    self.add_neighbour_blocks((x_cordinate, y_cordinate),
                                              hair_graph)

        return skin_graph, hair_graph

    def find_bounding_box(self, component):
        '''
        Return the bounding box - a box is a pair of tuples - ((minx, miny), (maxx, maxy)) for the component
        Argument 'component' - is just the list of blocks in that component where each block is represented by the
        coordinates of its top-left pixel.
        '''
        minx = component[0][0]
        miny = component[0][1]
        maxx = component[0][0]
        maxy = component[0][1]
        for i in range(0, len(component), 1):
            if component[i][0] < minx:
                minx = component[i][0]
            if component[i][1] < miny:
                miny = component[i][1]
            if component[i][0] > maxx:
                maxx = component[i][0]
            if component[i][1] > maxy:
                maxy = component[i][1]

        return ((minx, miny), (maxx, maxy))

    def skin_hair_match(self, skin_box, hair_box):
        '''
        Return True if the skin-box and hair-box given are matching according to one of the pre-defined patterns
        '''
        if skin_box[0][0] in range(hair_box[0][0], hair_box[1][0]):
            if skin_box[1][0] in range(hair_box[0][0], hair_box[1][0]):
                if skin_box[0][1] in range(hair_box[0][1], hair_box[1][1]):
                    return True  #pattern 1,2,3,4,7,8,12,13

        if skin_box[0][1] in range(hair_box[0][1], hair_box[1][1]):
            if skin_box[0][0] in range(hair_box[0][0], hair_box[1][0]):
                return True  #pattern 6
            elif skin_box[1][0] in range(hair_box[0][0], hair_box[1][0]):
                return True  #pattern 5

        if hair_box[0][1] in range(skin_box[0][1], skin_box[1][1]):
            if hair_box[1][1] in range(skin_box[0][1], skin_box[1][1]):
                if hair_box[1][0] in range(skin_box[0][0], skin_box[1][0]):
                    return True
                elif hair_box[0][0] in range(skin_box[0][0], skin_box[1][0]):
                    return True  # 9, 10, 11

    def detect_faces(self):
        '''
        Main method - to detect faces in the image that this class was initialized with
        Return list of face boxes - a box is a pair of tuples - ((minx, miny), (maxx, maxy))
        Algo: (i) Make block graph (ii) get the connected components of the graph (iii) filter the connected components
        (iv) find bounding box for each component (v) Look for matches between face and hair bounding boxes
        Return the list of face boxes that have matching hair boxes
        '''
        skin_graph, hair_graph = self.make_block_graph()
        skin_comp1 = skin_graph.get_connected_components()
        hair_comp1 = hair_graph.get_connected_components()
        skin_comp = []
        hair_comp = []
        list1 = len(skin_comp1)
        for i in range(0, list1):
            if len(skin_comp1[i]) > self.min_blocks:
                skin_comp += [skin_comp1[i]]
                list1 -= 1
        list1 = len(hair_comp1)
        for i in range(0, list1):
            if len(skin_comp1[i]) > self.min_blocks:
                hair_comp += [hair_comp1[i]]
                list1 -= 1
        face_list = []
        for skin in skin_comp:
            for hair in hair_comp:
                skin_box = self.find_bounding_box(skin)
                hair_box = self.find_bounding_box(hair)
                if self.skin_hair_match(skin_box, hair_box):
                    face_list.append(skin_box)

        return face_list

    def mark_box(self, box, color):
        '''
        Mark the box (same as in the above methods) with a given color (given as a raw triple)
        This is just a one-pixel wide line showing the box.
        '''
        for i in range(box[0][0], box[1][0]):
            self.image.set(i, box[0][1], color)
            self.image.set(i, box[1][1], color)

        for i in range(box[0][1], box[1][1]):
            self.image.set(box[0][0], i, color)
            self.image.set(box[1][0], i, color)

    def mark_faces(self, marked_file):
        '''
        Detect faces and mark each face detected -- mark the bounding box of each face in red
        and save the marked image in a new file
        '''
        face_list = self.detect_faces()
        for i in face_list:
            self.mark_box(i, (255, 0, 0))
        self.image.save(marked_file)