Lors de traitement d’image, il peut être intéressant de filtrer une couleur pour la modifier, la garder ou la supprimer. Nous allons voir dans ce tutoriel comment sélectionner et filtrer une zone de couleur avec OpenCV et Python.
Traitement d’image
Pour ce tutoriel, nous utilisons une image avec différentes formes et couleurs.
import cv2 import numpy as np fileName= "python_shapes_detection_base.png" img = cv2.imread( fileName , cv2.IMREAD_UNCHANGED) win_name = "original" cv2.namedWindow(win_name, cv2.WINDOW_NORMAL) cv2.imshow(win_name, img) cv2.waitKey()
Codes couleur RGB, HSV et Hexadecimal
Pour traiter les images, nous utilisons le module OpenCV. Il existe différentes façons de filtrer les images et les couleurs avec OpenCV en fonction des codes couleur utilisés. Pour convertir les couleurs de RGB vers HSV, nous utiliserons le module colorsys.
# format conversion import colorsys def rgb_to_hex(pixel): """ convert color from BGR to Hexa format """ #rgb normal: range (0-255, 0-255, 0.255) #red=pixel[0] #green=pixel[1] #blue=pixel[2] #bgr normal: range (0-255, 0-255, 0.255) blue=pixel[0] green=pixel[1] red=pixel[2] return '#{:02x}{:02x}{:02x}'.format(red, green, blue) def convert_rgb_to_hsv(pixel): """ convert color from BGR to HSV format """ #rgb normal: range (0-255, 0-255, 0.255) #red=pixel[0] #green=pixel[1] #blue=pixel[2] #bgr normal: range (0-255, 0-255, 0.255) blue=pixel[0] green=pixel[1] red=pixel[2] #get rgb percentage: range (0-1, 0-1, 0-1 ) red_percentage= red / float(255) green_percentage= green/ float(255) blue_percentage=blue / float(255) #get hsv percentage: range (0-1, 0-1, 0-1) color_hsv_percentage=colorsys.rgb_to_hsv(red_percentage, green_percentage, blue_percentage) #get opencv hsv: range (0-179, 0-255, 0-255) color_h=round(179*color_hsv_percentage[0])#round(360*color_hsv_percentage[0]) color_s=round(255*color_hsv_percentage[1]) color_v=round(255*color_hsv_percentage[2]) color_hsv=[color_h, color_s, color_v] #print('color_hsv: ', color_hsv) return color_hsv
N.B: le format de couleur des pixels dans OpenCV est BGR
Sélection de pixel dans OpenCV
Pour savoir quel valeur de couleur filtrer sur notre image, nous allons ajouter un écouteur d’évènement pour pouvoir sélectionner un pixel avec la souris et afficher les couleurs RGB, HSV et Hexa correspondante
def click_event(event, x, y, flags, params): """ add a click event to display BGR and HSV color on image when click on partciular pixel event - event type x - event x position y - event y position flags - params - (windows name, img object) usage : cv2.setMouseCallback(win_name, click_event, [win_name,img]) """ global rgb_to_filter,hsv_to_filter if event == cv2.EVENT_LBUTTONDOWN: img = params[1] imgdisp = img.copy() #print(f'Coordinates: ({x}, {y})') rgb = imgdisp[y,x] hsv = convert_rgb_to_hsv(rgb) hexc = rgb_to_hex(rgb) print('rgb: {}\nhsv : {}'.format(rgb,hsv)) font = cv2.FONT_HERSHEY_PLAIN cv2.putText(imgdisp, 'rgb: {}'.format(rgb), (x, y), font, 1, (255, 0, 0), 1) cv2.putText(imgdisp, 'hsv : {}'.format(hsv), (x, y+15), font, 1, (255, 0, 0), 1) cv2.putText(imgdisp, 'hex : {}'.format(hexc), (x, y+30), font, 1, (255, 0, 0), 1) cv2.imshow(params[0], imgdisp) #cv2.destroyWindow(params[0]) rgb_to_filter = rgb hsv_to_filter = convert_rgb_to_hsv(rgb)
Nous pouvons ensuite lier la fonction de gestion de l’évènement à la fenêtre
cv2.setMouseCallback(win_name, click_event, [win_name,img])
Filtrer les couleurs avec OpenCV
Pour filtrer les couleurs, il existe différentes manières selon le code couleur utilisé et ce que vous souhaitez faire exactement.
Pour le format RGB, il est possible de traiter les pixels un par un. Dans cet exemple, nous filtrons les couleurs proches (th) de la couleur sélectionnée (color)
def colorThresh(img,color,keep=True,th=3, ncolor = [0,0,0,0]): """remove or keep specific color from image and replace with ncolor""" imgfilt = img.copy() #data format if isinstance(img,np.ndarray):#opencv h,w,c = img.shape #pixel_list = [img[y,x].tolist() for y in range(0, h) for x in range(0,w)] for y in range(0, h): for x in range(0,w): pixel = img[y,x] if keep: if abs(color[0]-pixel[0])<th and abs(color[1]-pixel[1])<th and abs(color[2]-pixel[2])<th: imgfilt[y,x] = pixel else: imgfilt[y,x] = ncolor else: if abs(color[0]-pixel[0])<th and abs(color[1]-pixel[1])<th and abs(color[2]-pixel[2])<th: imgfilt[y,x] = ncolor else: imgfilt[y,x] = pixel return imgfilt
Pour le format HSV, il existe une fonction plus directe et plus rapide en utilisant un mask construit avec inRange.
lower = np.array([10, 100, 100]) upper = np.array([20, 255, 255]) imghsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask = cv2.inRange(imghsv, lower, upper) result = cv2.bitwise_and(img, img, mask=mask) #keep color #result = cv2.bitwise_and(img, img, mask=255-mask) #remove color
N.B: les deux fonctions précédente donne le même résultat dans cet exemple même si la méthode HSV est beaucoup plus rapide. Vous pouvez adapter ces fonctions selon votre besoin
Voici le résultat du code complet que vous trouverez dans la section suivante. Il vous permet de sélectionner un pixel de l’image et de conserver la forme de couleur correspondante ou la supprimer.
Code complet pour la sélection et le filtrage de couleur
Ce code vous permettra de sélectionner une couleur, avec une souris ou autre, pour la filtrer. Vous pouvez travailler sur les fonctions données pour travailler sur les pixels ou sur des champs de couleur HSV.
#!/usr/bin/env python # -*- coding: utf-8 -*- # # imgProc.py # # imgprocessing utility functions to filter, remove pixel of certain color import cv2 import numpy as np import math def colorThresh(img,color,keep=True,th=3, ncolor = [0,0,0,0]): """remove specific color from image and replace with ncolor""" #img = img.convert("RGBA") imgfilt = img.copy() #data format if isinstance(img,np.ndarray):#opencv h,w,c = img.shape #pixel_list = [img[y,x].tolist() for y in range(0, h) for x in range(0,w)] for y in range(0, h): for x in range(0,w): pixel = img[y,x] if keep: #print(((color[0]-pixel[0])**2+(color[1]-pixel[1])**2+(color[2]-pixel[2])**2)) #print(color[0]-pixel[0]) #print(color[1]-pixel[1]) #print(color[2]-pixel[2]) if abs(color[0]-pixel[0])<th and abs(color[1]-pixel[1])<th and abs(color[2]-pixel[2])<th: imgfilt[y,x] = pixel else: imgfilt[y,x] = ncolor else: #print(((color[0]-pixel[0])**2+(color[1]-pixel[1])**2+(color[2]-pixel[2])**2)) if abs(color[0]-pixel[0])<th and abs(color[1]-pixel[1])<th and abs(color[2]-pixel[2])<th: imgfilt[y,x] = ncolor else: imgfilt[y,x] = pixel return imgfilt # format conversion import colorsys def rgb_to_hex(pixel): """ convert color from BGR to Hexa format """ #rgb normal: range (0-255, 0-255, 0.255) #red=pixel[0] #green=pixel[1] #blue=pixel[2] #bgr normal: range (0-255, 0-255, 0.255) blue=pixel[0] green=pixel[1] red=pixel[2] return '#{:02x}{:02x}{:02x}'.format(red, green, blue) def hex_to_rgb(hexc): rgb = [] for i in (0, 2, 4): decimal = int(hexc[i:i+2], 16) rgb.append(decimal) return tuple(rgb) def convert_rgb_to_hsv(pixel): """ convert color from BGR to HSV format """ #rgb normal: range (0-255, 0-255, 0.255) #red=pixel[0] #green=pixel[1] #blue=pixel[2] #bgr normal: range (0-255, 0-255, 0.255) blue=pixel[0] green=pixel[1] red=pixel[2] #get rgb percentage: range (0-1, 0-1, 0-1 ) red_percentage= red / float(255) green_percentage= green/ float(255) blue_percentage=blue / float(255) #get hsv percentage: range (0-1, 0-1, 0-1) color_hsv_percentage=colorsys.rgb_to_hsv(red_percentage, green_percentage, blue_percentage) #get opencv hsv: range (0-179, 0-255, 0-255) color_h=round(179*color_hsv_percentage[0])#round(360*color_hsv_percentage[0]) color_s=round(255*color_hsv_percentage[1]) color_v=round(255*color_hsv_percentage[2]) color_hsv=[color_h, color_s, color_v] #print('color_hsv: ', color_hsv) return color_hsv #### opencv utils def click_event(event, x, y, flags, params): """ add a click event to display BGR and HSV color on image when click on partciular pixel event - event type x - event x position y - event y position flags - params - (windows name, img object) usage : cv2.setMouseCallback(win_name, click_event, [win_name,img]) """ global rgb_to_filter,hsv_to_filter if event == cv2.EVENT_LBUTTONDOWN: img = params[1] imgdisp = img.copy() #print(f'Coordinates: ({x}, {y})') rgb = imgdisp[y,x] hsv = convert_rgb_to_hsv(rgb) hexc = rgb_to_hex(rgb) print('rgb: {}\nhsv : {}'.format(rgb,hsv)) font = cv2.FONT_HERSHEY_PLAIN cv2.putText(imgdisp, 'rgb: {}'.format(rgb), (x, y), font, 1, (255, 0, 0), 1) cv2.putText(imgdisp, 'hsv : {}'.format(hsv), (x, y+15), font, 1, (255, 0, 0), 1) cv2.putText(imgdisp, 'hex : {}'.format(hexc), (x, y+30), font, 1, (255, 0, 0), 1) cv2.imshow(params[0], imgdisp) #cv2.destroyWindow(params[0]) rgb_to_filter = rgb hsv_to_filter = convert_rgb_to_hsv(rgb) def main(args): global rgb_to_filter,hsv_to_filter fileName= "./img/python_shapes_detection_base.png" img = cv2.imread( fileName , cv2.IMREAD_UNCHANGED) h,w,c = img.shape print("CV2 Image height: ", h," width: ", w) rgb_to_filter = [255,255,255] hsv_to_filter = [0,0,0] #show image with opencv scaleFactor = 0.1 win_name = "original" cv2.namedWindow(win_name, cv2.WINDOW_NORMAL) cv2.imshow(win_name, img) cv2.setMouseCallback(win_name, click_event, [win_name,img]) #cv2.resizeWindow(win_name, int(h*scaleFactor), int(w*scaleFactor)) cv2.waitKey() #filter hsv color imghsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) lower = np.maximum(np.array(hsv_to_filter) - 2 , np.array([0, 0, 0])) upper = np.minimum(np.array(hsv_to_filter) + 2 , np.array([255, 255, 255])) print("hsv lower:",lower) print("hsv upper:",upper) mask = cv2.inRange(imghsv, lower, upper) result = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('result', result) cv2.waitKey() #filter rgb color filtered_color = rgb_to_filter #[75, 19, 143, 255] print("rgb to filter",filtered_color) res = colorThresh(img,filtered_color,keep=True,th=30,ncolor = [0,0,0,0]) #remove all except color and replace by black #res = colorThresh(img,filtered_color,keep=False,th=30,ncolor = [0,0,0,0]) #remove color and replace by black cv2.imshow('res', res) cv2.setMouseCallback('res', click_event, ['res', res]) cv2.waitKey() cv2.destroyAllWindows() return 0 if __name__ == '__main__': import sys sys.exit(main(sys.argv))
Applications
- Reconnaissance de forme avec Python
- Détection de contour avec Python