Python скрипт для пальмового жира. Версия 1
# -*- coding: cp1251 -*-
import os
import cv2
import numpy as np
from tkinter import (
Tk, Button, Label, Frame, filedialog, messagebox,
Toplevel, Scale, HORIZONTAL
)
from PIL import Image, ImageTk
# —————- resample compatibility —————-
try:
RESAMPLE = Image.Resampling.LANCZOS
except AttributeError:
RESAMPLE = Image.LANCZOS
# —————- Global calibrated state —————-
calibrated_lower = None
calibrated_upper = None
current_image = None
current_image_path = None
# —————- Predefined HSV ranges —————-
STRICT_RANGE = (np.array([5, 120, 120]), np.array([25, 255, 255]))
SOFT_RANGE = (np.array([0, 40, 40]), np.array([35, 255, 255]))
NUCLEAR_RANGE = (np.array([8, 150, 140]), np.array([18, 255, 255]))
# —————- Helpers —————-
def cv2_to_tk(img_bgr, max_size=(520, 520)):
rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
im = Image.fromarray(rgb)
im.thumbnail(max_size, RESAMPLE)
return ImageTk.PhotoImage(im)
def mask_to_tk(mask, max_size=(520, 520)):
pil = Image.fromarray(mask.astype(np.uint8))
pil = pil.convert(”RGB”)
pil.thumbnail(max_size, RESAMPLE)
return ImageTk.PhotoImage(pil)
def detect_by_hsv(img_bgr, lower, upper):
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
# morphology cleanup
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=1)
percent = (np.count_nonzero(mask) / mask.size) * 100.0
return percent, mask
# —————- HSV Calibrator —————-
class HSVCalibrator:
def __init__(self, parent, bgr_image):
self.parent = parent
self.img = bgr_image
self.win = Toplevel(parent)
self.win.title(”HSV Калибровка”)
# sliders
self.h_min = Scale(self.win, label=”H min”, from_=0, to=179, orient=HORIZONTAL, command=self.update)
self.h_min.set(0); self.h_min.pack(fill=”x”)
self.h_max = Scale(self.win, label=”H max”, from_=0, to=179, orient=HORIZONTAL, command=self.update)
self.h_max.set(179); self.h_max.pack(fill=”x”)
self.s_min = Scale(self.win, label=”S min”, from_=0, to=255, orient=HORIZONTAL, command=self.update)
self.s_min.set(0); self.s_min.pack(fill=”x”)
self.s_max = Scale(self.win, label=”S max”, from_=0, to=255, orient=HORIZONTAL, command=self.update)
self.s_max.set(255); self.s_max.pack(fill=”x”)
self.v_min = Scale(self.win, label=”V min”, from_=0, to=255, orient=HORIZONTAL, command=self.update)
self.v_min.set(0); self.v_min.pack(fill=”x”)
self.v_max = Scale(self.win, label=”V max”, from_=0, to=255, orient=HORIZONTAL, command=self.update)
self.v_max.set(255); self.v_max.pack(fill=”x”)
# preview
self.preview = Label(self.win)
self.preview.pack(pady=8)
self.update(None)
def update(self, event):
global calibrated_lower, calibrated_upper
lower = np.array([self.h_min.get(), self.s_min.get(), self.v_min.get()], dtype=np.uint8)
upper = np.array([self.h_max.get(), self.s_max.get(), self.v_max.get()], dtype=np.uint8)
calibrated_lower = lower
calibrated_upper = upper
# preview on downscaled image
img_small = cv2.resize(self.img, (600, 600), interpolation=cv2.INTER_AREA)
if max(self.img.shape[:2]) > 600 else self.img
_, mask = detect_by_hsv(img_small, lower, upper)
tk = mask_to_tk(mask)
self.preview.configure(image=tk)
self.preview.image = tk
# —————- Main GUI —————-
class PalmApp:
def __init__(self, root):
self.root = root
root.title(”Palm Detector — Triple + Calibrated”)
root.geometry(”1200×760”)
ctrl = Frame(root)
ctrl.pack(side=”top”, pady=8)
Button(ctrl, text=”Открыть изображение”, width=20, command=self.open_file).pack(side=”left”, padx=5)
Button(ctrl, text=”Строгий режим”, width=20, command=self.run_strict).pack(side=”left”, padx=5)
Button(ctrl, text=”Мягкий режим”, width=20, command=self.run_soft).pack(side=”left”, padx=5)
Button(ctrl, text=”Ядрёная пальма”, width=20, command=self.run_nuclear).pack(side=”left”, padx=5)
# ? Новая кнопка
Button(ctrl, text=”Калиброванный режим”, width=20, command=self.run_calibrated).pack(side=”left”, padx=5)
Button(ctrl, text=”Калибровка HSV”, width=20, command=self.open_calibrator).pack(side=”left”, padx=5)
# display
disp = Frame(root)
disp.pack(fill=”both”, expand=True, padx=6, pady=6)
self.left_label = Label(disp)
self.left_label.pack(side=”left”, padx=10, pady=10)
self.right_label = Label(disp)
self.right_label.pack(side=”right”, padx=10, pady=10)
self.img = None
self.last_mask = None
def open_file(self):
global current_image, current_image_path
f = filedialog.askopenfilename(
title=”Выберите изображение”,
filetypes=[
(”Images”, ”*.jpg *.jpeg *.png *.bmp *.tif *.tiff *.webp”),
(”All files”, ”*.*”)
]
)
if not f:
return
img = cv2.imread(f)
if img is None:
messagebox.showerror(”Ошибка”, ”Невозможно открыть файл”)
return
current_image = img
current_image_path = f
self.img = img
tk = cv2_to_tk(img)
self.left_label.configure(image=tk)
self.left_label.image = tk
self.right_label.configure(image=None)
self.right_label.image = None
self.last_mask = None
def show_result(self, lower, upper, title):
if self.img is None:
messagebox.showwarning(”Нет изображения”, ”Сначала откройте картинку”)
return
percent, mask = detect_by_hsv(self.img, lower, upper)
# highlight
overlay = self.img.copy()
overlay[mask > 0] = (0, 140, 255)
highlighted = cv2.addWeighted(overlay, 0.6, self.img, 0.4, 0)
tk = cv2_to_tk(highlighted)
self.right_label.configure(image=tk)
self.right_label.image = tk
self.last_mask = mask
messagebox.showinfo(title, f”Пальма: {percent:.2f}%”)
def run_strict(self):
self.show_result(*STRICT_RANGE, ”Строгий режим”)
def run_soft(self):
self.show_result(*SOFT_RANGE, ”Мягкий режим”)
def run_nuclear(self):
self.show_result(*NUCLEAR_RANGE, ”Ядрёная пальма”)
def run_calibrated(self):
global calibrated_lower, calibrated_upper
if calibrated_lower is None or calibrated_upper is None:
messagebox.showwarning(”Нет данных”, ”Сначала откройте калибровку и подвиньте ползунки.”)
return
self.show_result(calibrated_lower, calibrated_upper, ”Калиброванный режим”)
def open_calibrator(self):
global current_image
if current_image is None:
messagebox.showwarning(”Нет изображения”, ”Сначала откройте фотографию”)
return
HSVCalibrator(self.root, current_image)
# —————- Run —————-
def main():
root = Tk()
PalmApp(root)
root.mainloop()
if __name__ == ”__main__”:
main()
Версия 2
# -*- coding: cp1251 -*-
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import os
# ————- Параметры (строгий режим) ————-
# Эталонный цвет (центр) в RGB, вычисленный по образцам
palm_centroid_rgb = np.array([251.23151523, 96.63262181, 13.64952684], dtype=np.float32)
# Порог расстояния (в RGB-евклидовом пространстве). Меньше = строже.
threshold = 40.0
# ————- GUI ————-
root = tk.Tk()
root.title(”Palm Color Detector — strict”)
root.geometry(”1150×760”)
panel_original = tk.Label(root)
panel_original.pack(side=”left”, padx=8, pady=8)
panel_result = tk.Label(root)
panel_result.pack(side=”right”, padx=8, pady=8)
def cv2_to_tk(img, max_size=(520, 700)):
”"”Convert BGR OpenCV image to Tk PhotoImage, resized to max_size preserving aspect.”"”
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
im = Image.fromarray(rgb)
# масштабируем аккуратно, сохраняя пропорции
im.thumbnail(max_size, Image.ANTIALIAS)
return ImageTk.PhotoImage(im)
def detect_palm_strict(image_path):
img = cv2.imread(image_path)
if img is None:
messagebox.showerror(”Ошибка”, ”Не удалось открыть изображение.”)
return
# BGR -> RGB
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32)
# вычисляем расстояние в RGB (евклидово) до эталона
# reshape для векторизованного вычисления
h, w = rgb.shape[:2]
flat = rgb.reshape(-1, 3)
diffs = flat - palm_centroid_rgb[np.newaxis, :]
dists = np.linalg.norm(diffs, axis=1)
mask_flat = (dists <= threshold).astype(np.uint8) * 255
mask = mask_flat.reshape(h, w).astype(np.uint8)
# морфология (убираем шум)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=1)
# подсветка найденных пикселей
overlay = img.copy()
overlay[mask > 0] = (0, 140, 255) # BGR highlight (оранжево-красный)
highlighted = cv2.addWeighted(overlay, 0.6, img, 0.4, 0)
# процент (по всем пикселям изображения)
orange_pixels = int(cv2.countNonZero(mask))
total = h * w
percent = orange_pixels / total * 100.0
# создаём версию на белом фоне (объект на белом)
white_bg = np.ones_like(img, dtype=np.uint8) * 255
object_only = np.where(mask[:,:,None] > 0, img, white_bg)
# сохраняем результаты рядом с исходной картинкой
out_dir = os.path.dirname(image_path) if os.path.dirname(image_path) != ”" else ”.”
base = os.path.splitext(os.path.basename(image_path))[0]
mask_name = os.path.join(out_dir, f”{base}_mask_strict.png”)
highlighted_name = os.path.join(out_dir, f”{base}_palm_strict.jpg”)
white_name = os.path.join(out_dir, f”{base}_on_white_strict.jpg”)
cv2.imwrite(mask_name, mask)
cv2.imwrite(highlighted_name, highlighted)
cv2.imwrite(white_name, object_only)
# показать в GUI
orig_tk = cv2_to_tk(img)
res_tk = cv2_to_tk(highlighted)
panel_original.configure(image=orig_tk)
panel_original.image = orig_tk
panel_result.configure(image=res_tk)
panel_result.image = res_tk
messagebox.showinfo(”Готово”, f”Пальмового цвета: {percent:.2f}%nn”
f”Сохранено:n{mask_name}n{highlighted_name}n{white_name}”)
def open_file():
filepath = filedialog.askopenfilename(
title=”Выберите изображение”,
filetypes=[(”Images”, ”*.jpg *.jpeg *.png *.bmp”)]
)
if filepath:
detect_palm_strict(filepath)
# — Кнопки —
frm = tk.Frame(root)
frm.pack(side=”top”, fill=”x”, pady=6)
btn_open = tk.Button(frm, text=”Открыть изображение”, font=(”Arial”, 12), command=open_file)
btn_open.pack(side=”left”, padx=8)
lbl_info = tk.Label(frm, text=f”Строгий режим — порог = {threshold}”, font=(”Arial”, 11))
lbl_info.pack(side=”left”, padx=12)
root.mainloop()
python -m PyInstaller –onefile –windowed ^ –hidden-import=cv2 ^ –hidden-import=PIL ^ –hidden-import=PIL._tkinter_finder ^ –hidden-import=PIL.ImageTk ^ путь до файла.py

