feat: adds color detection

This commit is contained in:
alban 2024-12-15 14:28:27 +01:00
parent fa76a2f8fe
commit 10e8eecd31
4 changed files with 122 additions and 31 deletions

View File

@ -20,29 +20,58 @@ pip3 install -r requirements.txt
python3 main.py python3 main.py
``` ```
... devrait afficher ... ... devrait afficher une interface utilisateur.
```shell ## Documentation Utilisateur : Paramètres OpenCV
_________
_________
_________
___ ___
___ ___
___ ___
```
... qui signifie : L'interface graphique **Yiking** permet de contrôler divers paramètres pour un traitement d'image réalisé avec OpenCV. Voici une explication de chaque paramètre et son rôle dans l'algorithme de détection.
> 11. LA PAIX (TAI 泰) ---
> La rencontre entre le Ciel et la Terre une combinaison rare de grâce et de force -annonce la paix, lharmonie, la stabilité et la prospérité. Un moment de consensus promettant des résultats constructifs et prometteurs. Ces circonstances augurent une coexistence pacifique entre tout le monde.
### Paramètres OpenCV
## Fonctionnement #### 1. **maxDist**
- **Description** : Distance maximale entre les centres de deux cercles détectés.
- **Rôle** : Permet de contrôler la densité des cercles détectés. Une valeur élevée empêche la détection de cercles trop proches.
- **Conseil** : Augmentez cette valeur si trop de cercles se chevauchent.
#### 2. **param1**
- **Description** : Seuil supérieur pour le détecteur de bords (fonction Canny).
- **Rôle** : Définit la sensibilité de la détection des bords dans l'image. Une valeur élevée capture uniquement les bords bien définis.
- **Conseil** : Utilisez une valeur basse pour détecter plus de détails, mais avec plus de bruit.
#### 3. **param2**
- **Description** : Seuil pour le processus d'accumulation circulaire (HoughCircles).
- **Rôle** : Détermaxe la robustesse de la détection des cercles. Une valeur élevée garantit que seuls les cercles bien définis seront détectés.
- **Conseil** : Augmentez cette valeur pour réduire les faux positifs.
#### 4. **maxRadius**
- **Description** : Rayon maximal des cercles détectés.
- **Rôle** : Filtre les cercles dont le rayon est inférieur à cette valeur.
- **Conseil** : Utilisez une valeur basse si vous souhaitez détecter de petits cercles.
#### 5. **maxRadius**
- **Description** : Rayon maximal des cercles détectés.
- **Rôle** : Filtre les cercles dont le rayon dépasse cette valeur.
- **Conseil** : Réglez cette valeur en fonction de la taille des cercles que vous recherchez.
#### 6. **color1_R_max, color1_V_max, color1_B_max, color1_R_max, color1_V_max, color1_B_max,**
- **Description** : Couleur en (Rouge, Vert, Bleu) avec des valeurs minimales et maximales pour le filtrage par couleur.
- **Rôle** : Définit un seuil bas et un seuil haut pour détecter uniquement les pixels correspondants à une certaine teinte.
- **Conseil** : Ajustez ces valeurs pour isoler une couleur d'intérêt. Toute teinte en dehors de cette plage sera affectée à l'autre couleur.
---
### Utilisation
- Appuyer sur le bouton `Run` pour afficher une capture.
- Utiliser les sliders de détection de forme jusqu'à obtention du résultat voulu.
- Utiliser les valeurs (R, V, B) affichées pour les boules détectées pour définir la plage de couleur d'une couleur voulue.
## Exemple
### Source ### Source
![](./tests/images/balls-full-small.jpg) ![](./tests/images/balls-full-small.jpg)
### Post traitement ### Post traitement
![](./tests/images/balls-full-small-final.jpg) ![./tests/images/gui.png]()

67
main.py
View File

@ -4,6 +4,58 @@ from process import process_frame
from PIL import Image, ImageTk # Required for displaying images from PIL import Image, ImageTk # Required for displaying images
import cv2 # OpenCV for numpy array to image conversion import cv2 # OpenCV for numpy array to image conversion
"""
Heres a detailed prompt you can use to generate the same GUI code anew using a language model:
---
**Prompt:**
"I need a Python program to create a Tkinter-based GUI named 'Yiking'. The GUI will control parameters for an OpenCV image processing program. The program must be split into two files:
1. **`process.py`**: Contains a `process_frame` function that takes parameters as input, performs OpenCV operations (not implemented in this request), and returns an image as a NumPy array and a result string.
2. **`gui.py`**: Implements the GUI and integrates with `process.py`. Here are the detailed requirements:
### GUI Details:
- **Window Title**: 'Yiking'
- **Layout**:
- **Row 1**: Two columns
- Left column: Group of sliders for controlling parameters.
- Right column: A canvas for displaying a processed image (1024x768 pixels).
- **Row 2**: Two columns
- Left column: A "Run" button.
- Right column: A text box for displaying results or error messages.
- **Widgets**:
- Sliders with corresponding min, max, and default values:
- `minDist` = (0, 500, 100)
- `param1` = (0, 500, 30)
- `param2` = (0, 400, 25)
- `minRadius` = (0, 100, 5)
- `maxRadius` = (0, 1000, 1000)
- `color1_R_min` = (0, 64, 5)
- `color1_V_min` = (0, 64, 5)
- `color1_B_min` = (0, 64, 5)
- `color1_R_min` = (0, 64, 5)
- `color1_V_min` = (0, 64, 5)
- `color1_B_min` = (0, 64, 5)
- Sliders must snap to increments of 5 and synchronize with a text entry box.
- The image canvas must resize input images to fit within 1024x768 while maintaining aspect ratio.
- A "Run" button executes the `process_frame` function with the current slider values.
- If an exception occurs in `process_frame`, it should display the error in the result text box and clear the canvas.
### Key Features:
- Use the `Pillow` library (`PIL`) to handle image conversion and resizing.
- Catch exceptions in `run_process` to display error messages.
- Keep the GUI responsive and visually clean.
### Output:
Please provide a fully functional `gui.py` implementation meeting these requirements. Include imports, class structure, and the `__main__` block. Do not implement the OpenCV functionality within `process.py`assume it exists for integration purposes."
---
This prompt is designed to give the LLM everything it needs to generate the desired `gui.py` code. Let me know if you'd like to adjust it further!
"""
class OpenCVInterface: class OpenCVInterface:
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
@ -16,12 +68,12 @@ class OpenCVInterface:
"param2": (0, 400, 25), "param2": (0, 400, 25),
"minRadius": (0, 100, 5), "minRadius": (0, 100, 5),
"maxRadius": (0, 1000, 1000), "maxRadius": (0, 1000, 1000),
"color1_R": (0, 64, 5), "color1_R_min": (0, 255, 0),
"color1_V": (0, 64, 5), "color1_R_max": (0, 255, 0),
"color1_B": (0, 64, 5), "color1_V_min": (0, 255, 0),
"color2_R": (0, 64, 5), "color1_V_max": (0, 255, 0),
"color2_V": (0, 64, 5), "color1_B_min": (0, 255, 0),
"color2_B": (0, 64, 5), "color1_B_max": (0, 255, 0),
} }
self.variables = { self.variables = {
@ -66,7 +118,7 @@ class OpenCVInterface:
def on_slide(value): def on_slide(value):
# Round value to nearest multiple of 5 # Round value to nearest multiple of 5
rounded_value = round(float(value) / 5) * 5 rounded_value = round(float(value) / 2) * 2
variable.set(int(rounded_value)) # Update the variable with the rounded value variable.set(int(rounded_value)) # Update the variable with the rounded value
# Slider # Slider
@ -100,7 +152,6 @@ class OpenCVInterface:
new_width = int(original_width * aspect_ratio) new_width = int(original_width * aspect_ratio)
new_height = int(original_height * aspect_ratio) new_height = int(original_height * aspect_ratio)
pil_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS) pil_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
tk_image = ImageTk.PhotoImage(pil_image) # Convert PIL Image to Tkinter Image tk_image = ImageTk.PhotoImage(pil_image) # Convert PIL Image to Tkinter Image
# Clear canvas and display the image # Clear canvas and display the image

View File

@ -35,16 +35,16 @@ def process_frame(params):
# Simulate processing: for now, return a dummy image and text. # Simulate processing: for now, return a dummy image and text.
# width, height = 400, 300 # width, height = 400, 300
# image = Image.new("RGB", (width, height), # image = Image.new("RGB", (width, height),
# color=(params["color1_R"] * 4, params["color1_V"] * 4, params["color1_B"] * 4)) # color=(params["color1_R_min"] * 4, params["color1_V_min"] * 4, params["color1_B_min"] * 4))
# image_tk = ImageTk.PhotoImage(image) # image_tk = ImageTk.PhotoImage(image)
# #
# result_text = f"Processed image with params: {params}" # result_text = f"Processed image with params: {params}"
# return image_tk, result_text # return image_tk, result_text
(minDist, param1, param2, minRadius, maxRadius, (minDist, param1, param2, minRadius, maxRadius,
color1_R, color1_V, color1_B, color2_R, color2_V, color2_B) = itemgetter( color1_R_min, color1_V_min, color1_B_min, color1_R_max, color1_V_max, color1_B_max) = itemgetter(
'minDist', 'param1', 'param2', 'minRadius', 'maxRadius', 'minDist', 'param1', 'param2', 'minRadius', 'maxRadius',
'color1_R', 'color1_V', 'color1_B', 'color2_R', 'color2_V', 'color2_B' 'color1_R_min', 'color1_V_min', 'color1_B_min', 'color1_R_max', 'color1_V_max', 'color1_B_max'
)(params) )(params)
# 1. Acquisition de l'image # 1. Acquisition de l'image
@ -83,6 +83,7 @@ def process_frame(params):
s = np.clip(s, 0, 255) s = np.clip(s, 0, 255)
imghsv = cv2.merge([h, s, v]) imghsv = cv2.merge([h, s, v])
boules_couleurs = [] boules_couleurs = []
boules_bgr = []
for boule in boules: for boule in boules:
half_diametre = int(boule.rayon / 2) half_diametre = int(boule.rayon / 2)
crop = imghsv[ crop = imghsv[
@ -94,10 +95,18 @@ def process_frame(params):
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, .1) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, .1)
_, labels, palette = cv2.kmeans(pixels, n_colors, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) _, labels, palette = cv2.kmeans(pixels, n_colors, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
_, counts = np.unique(labels, return_counts=True) _, counts = np.unique(labels, return_counts=True)
(b, g, r) = palette[np.argmax(counts)] / 16 (b, v, r) = palette[np.argmax(counts)]
boules_bgr.append(f"R:{int(r)} V:{int(v)} B:{int(b)}")
# A modulariser # On récupère les valeurs de R G et B qui sont à analyser
boules_couleurs.append(TYPE_1 if b > 4 else TYPE_2) if int(
color1_R_min <= math.floor(r) <= color1_R_max
and color1_V_min <= math.floor(v) <= color1_V_max
and color1_B_min <= math.floor(b) <= color1_B_max
):
boules_couleurs.append(TYPE_1)
else :
boules_couleurs.append(TYPE_2)
# 5. Calcul des distances entre chaque boule et le cochonnet selon le centre des boxs # 5. Calcul des distances entre chaque boule et le cochonnet selon le centre des boxs
boules_distance = {} boules_distance = {}
@ -116,5 +125,7 @@ def process_frame(params):
boule = boules[i] boule = boules[i]
return_text += f"{boules_couleurs[i]}\n" return_text += f"{boules_couleurs[i]}\n"
cv2.circle(img_final, (boule.x, boule.y), boule.rayon, (0, 255, 0), 2) cv2.circle(img_final, (boule.x, boule.y), boule.rayon, (0, 255, 0), 2)
cv2.putText(img_final, boules_bgr[i], (boule.x, boule.y), cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.75, color=(255, 255, 255), thickness=1, lineType=cv2.LINE_AA)
return img_final, return_text return img_final, return_text

BIN
tests/images/gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB