commit 48fa81a0b7a54344e8af70fc69c320b5186fb851 Author: alban Date: Sun Dec 8 20:45:57 2024 +0100 feat: v0.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdd0b63 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# created by virtualenv automatically +bin +lib +pyvenv.cfg +.venv +.idea + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f68802e --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Ursula Le Yiking + +## Todo + +- [ ] Utiliser une webcam en entrée +- [ ] Créer une interface avec tkinter +- [ ] Variabliser les tailles et couleurs via l'interface + +## Crash course + +Lancer, avec une version Python >= 3.10 : + +```shell +git clone https://git.interhacker.space/protonphoton/yiking +python3 -m venv .venv +source .venv/bin/activate +pip3 install -r requirements.txt +python3 main.py +``` + +... devrait afficher ... + +```shell +_________ +_________ +_________ +___ ___ +___ ___ +___ ___ +``` + +... qui signifie : + +> 11. LA PAIX (TAI 泰) +> La rencontre entre le Ciel et la Terre – une combinaison rare de grâce et de force -annonce la paix, l’harmonie, 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. + + +## Fonctionnement + +### Source +![](./tests/images/balls-full-small.jpg) + +### Post traitement +![](./tests/images/balls-full-small-final.jpg) + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..e7c5eac --- /dev/null +++ b/main.py @@ -0,0 +1,116 @@ +import math +from dataclasses import dataclass + +import numpy as np +import cv2 +import tkinter +import os +import sys +from pathlib import Path + +dir_path = Path(".").absolute() +TYPE_1 = "_________" +TYPE_2 = "___ ___" + + +@dataclass +class Object: + x: int + y: int + diametre: int + + +# 1. Acquisition de l'image +src = dir_path.joinpath('tests/images/balls-full-small.jpg') +raw_image = cv2.imread(str(src)) + +# 2. Boxing des objets via opencv +gray = cv2.cvtColor(raw_image, cv2.COLOR_BGR2GRAY) +blurred = cv2.medianBlur(gray, 25) +minDist = 100 +param1 = 30 # 500 +param2 = 25 # 200 #smaller value-> more false circles +minRadius = 5 +maxRadius = 1000 # 10 +circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, minDist, param1=param1, param2=param2, minRadius=minRadius, + maxRadius=maxRadius) + +min_diameter = 9999 +cochonnet = None +boules = [] +if circles is not None: + circles = np.uint16(np.around(circles)) + for i in circles[0, :]: + boule = Object(x=int(i[0]), y=int(i[1]), diametre=int(i[2])) + # cv2.circle(img, (boule.x, boule.y), boule.diametre, (0, 255, 0), 2) + + # 3. Détection de la box la plus petite : cochonnet + if boule.diametre < min_diameter: + min_diameter = boule.diametre + if cochonnet != None: + boules.append(cochonnet) + cochonnet = boule + else: + boules.append(boule) + +img_check_shapes = raw_image.copy() +for boule in boules: + cv2.circle(img_check_shapes, (boule.x, boule.y), boule.diametre, (0, 255, 0), 2) +cv2.circle(img_check_shapes, (cochonnet.x, cochonnet.y), cochonnet.diametre, (255, 255, 0), -1) +# cv2.imshow('img_check_shapes', img_check_shapes) +# cv2.waitKey(0) +# cv2.destroyAllWindows() + +# 4. Regroupement en liste de boules 1 ou 2 selon la couleur principale de chaque box restante + +hsv = cv2.cvtColor(raw_image, cv2.COLOR_BGR2HSV) +(h, s, v) = cv2.split(hsv) +s = s * 2 +s = np.clip(s, 0, 255) +imghsv = cv2.merge([h, s, v]) +# cv2.imshow('imghsv', imghsv) +# cv2.waitKey(0) +# cv2.destroyAllWindows() + +boules_couleurs = [] +for boule in boules: + half_diametre = int(boule.diametre / 2) + crop = imghsv[ + boule.y - half_diametre:boule.y + half_diametre, + boule.x - half_diametre:boule.x + half_diametre, + ].copy() + pixels = np.float32(crop.reshape(-1, 3)) + n_colors = 2 + 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) + _, counts = np.unique(labels, return_counts=True) + (b, g, r) = palette[np.argmax(counts)] / 16 + + # A modulariser + boules_couleurs.append(TYPE_1 if b > 4 else TYPE_2) + + # cv2.imshow('crop', crop) + # cv2.waitKey(0) + +# 5. Calcul des distances entre chaque boule et le cochonnet selon le centre des boxs +boules_distance = {} +for i, boule in enumerate(boules): + dist = int(math.sqrt(math.pow(cochonnet.x - boule.x, 2) + math.pow(cochonnet.y - boule.y, 2))) + boules_distance[i] = dist +boules_distance = dict(sorted(boules_distance.items(), key=lambda item: item[1])) + +# 6. Liste ordonnée des 6 distances les plus faibles + +boules_proches = [x for x in list(boules_distance)[0:6]] + +# 7. Sortie des 6 couleurs en --- ou - - +img_final = raw_image.copy() +for i in boules_proches: + boule = boules[i] + print(boules_couleurs[i]) + cv2.circle(img_final, (boule.x, boule.y), boule.diametre, (0, 255, 0), 2) + +# Show result for testing: +cv2.imshow('img_final', img_final) +cv2.waitKey(0) +cv2.destroyAllWindows() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cbce648 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy~=2.1.3 +opencv-python \ No newline at end of file diff --git a/tests/images/balls-full-small-final.jpg b/tests/images/balls-full-small-final.jpg new file mode 100644 index 0000000..afa3771 Binary files /dev/null and b/tests/images/balls-full-small-final.jpg differ diff --git a/tests/images/balls-full-small.jpg b/tests/images/balls-full-small.jpg new file mode 100644 index 0000000..3d4c877 Binary files /dev/null and b/tests/images/balls-full-small.jpg differ diff --git a/tests/images/balls-full.jpg b/tests/images/balls-full.jpg new file mode 100644 index 0000000..c6fa983 Binary files /dev/null and b/tests/images/balls-full.jpg differ diff --git a/tests/images/balls.jpg b/tests/images/balls.jpg new file mode 100644 index 0000000..c2dd9b6 Binary files /dev/null and b/tests/images/balls.jpg differ