Esempio n. 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
Esempio n. 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))        
Esempio n. 3
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
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)
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)
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)