Drag-and-Drop на Python+OpenCV
Автор: Pavel_DatВ данной статье расскажу про простой Drag-and-Drop
на Python+OpenCV.
Немного теории, ведь в наше время без нее никуда🤓
Drag-and-drop () — способ оперирования элементами интерфейса в интерфейсах пользователя (как графическим, так и текстовым, где элементы GUI реализованы при помощи псевдографики) при помощи манипулятора «мышь» или сенсорного экрана.
Простыми словами:
Drag-and-Drop - это возможность захватить мышью элемент и перенести его.
Идея заключается в перемещении созданных квадратов на экране жестами руки. Так как нам потребуется как-то воспроизводить клик
, то представим, что кликом
будет служить соединение указательного и среднего пальцев. Теперь можно писать код.
Тут, я разберу весь код с нуля, но для полной ясности лучше прочитать мои предыдущие статьи😄
Все исходники можно найти на моем Github.
Установим все необходимые библиотеки:
pip install opencv-python pip install numpy pip install cvzone
В первую очередь создаем новый файл `HandTrackingModule.py`
import cv2 import mediapipe as mp import time import math class handDetector(): def __init__(self, mode=False, maxHands=2, modelComplexity=1, detectionCon=0.5, trackCon=0.5): self.mode = mode self.maxHands = maxHands self.modelComplexity = modelComplexity self.detectionCon = detectionCon self.trackCon = trackCon self.mpHands = mp.solutions.hands self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplexity, self.detectionCon, self.trackCon) self.mpDraw = mp.solutions.drawing_utils self.tipIds = [4, 8, 12, 16, 20] def findHands(self, img, draw=True): imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) self.results = self.hands.process(imgRGB) #print(results.multi_hand_landmarks) if self.results.multi_hand_landmarks: for handLms in self.results.multi_hand_landmarks: if draw: self.mpDraw.draw_landmarks(img, handLms, self.mpHands.HAND_CONNECTIONS) return img def findPosition(self, img, handNo=0, draw=True): xList = [] yList = [] bbox = [] self.lmList = [] if self.results.multi_hand_landmarks: myHand = self.results.multi_hand_landmarks[handNo] for id, lm in enumerate(myHand.landmark): #print(id, lm) h, w, c = img.shape cx, cy = int(lm.x*w), int(lm.y*h) xList.append(cx) yList.append(cy) #print(id, cx, cy) self.lmList.append([id, cx, cy]) if draw: cv2.circle(img, (cx, cy), 5, (255,0,255), cv2.FILLED) xmin, xmax = min(xList), max(xList) ymin, ymax = min(yList), max(yList) bbox = xmin, ymin, xmax, ymax if draw: cv2.rectangle(img, (bbox[0]-20, bbox[1]-20), (bbox[2]+20, bbox[3]+20), (0, 255, 0), 2) return self.lmList, bbox def findDistance(self, p1, p2, img, draw=True): x1, y1 = self.lmList[p1][1], self.lmList[p1][2] x2, y2 = self.lmList[p2][1], self.lmList[p2][2] cx, cy = (x1+x2)//2, (y1+y2)//2 if draw: cv2.circle(img, (x1,y1), 15, (255,0,255), cv2.FILLED) cv2.circle(img, (x2,y2), 15, (255,0,255), cv2.FILLED) cv2.line(img, (x1,y1), (x2,y2), (255,0,255), 3) cv2.circle(img, (cx,cy), 15, (255,0,255), cv2.FILLED) length = math.hypot(x2-x1, y2-y1) return length, img, [x1, y1, x2, y2, cx, cy] def fingersUp(self): fingers = [] # Thumb if self.lmList[self.tipIds[0]][1] < self.lmList[self.tipIds[0]-1][1]: fingers.append(1) else: fingers.append(0) # 4 Fingers for id in range(1,5): if self.lmList[self.tipIds[id]][2] < self.lmList[self.tipIds[id]-2][2]: fingers.append(1) else: fingers.append(0) return fingers def main(): pTime = 0 cTime = 0 cap = cv2.VideoCapture(0) detector = handDetector() while True: success, img = cap.read() img = detector.findHands(img) lmList = detector.findPosition(img) if len(lmList) != 0: print(lmList[1]) cTime = time.time() fps = 1. / (cTime - pTime) pTime = cTime cv2.putText(img, str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3) cv2.imshow("Image", img) cv2.waitKey(1) if __name__ == "__main__": main()
Данный класс мы использовали в Управление громкостью звука жестами на Python.
Можем создать новый файл, назовем его main.py
и будем писать всю логику.
Импортируем библиотеки:
import cv2 import HandTrackingModule as htm import cvzone import numpy as np
Запускаем камеру и присваиваем глобальным переменным значения:
cap = cv2.VideoCapture(0) cap.set(3, 1280) cap.set(4, 720) detector = htm.handDetector(detectionCon=0.8) colorR = (255, 0, 255) cx, cy, w, h = 100, 100, 200, 200
При подключении камеры могут возникнуть ошибки, поменяйте 0
из cap=cv2.VideoCapture(0)
на 1
или 2
.
cx, cy - координаты левой верхней точки нашего четырехугольника w, h - ширина и длина (в нашем случае будет квадрат)
Напишем класс, для более удобной дальнейшей работы с блоками:
class DragAndDrogRectangle(): def __init__(self, posCenter, size=[200, 200]): self.posCenter = posCenter self.size = size def update(self, cursor): cx, cy = self.posCenter w, h = self.size # If the index finger tip is in rectangle region if cx - w//2 < cursor[0] < cx + w//2 and cy - h//2 < cursor[1] < cy + h//2: self.posCenter = cursor rectList = [] for x in range(5): rectList.append(DragAndDrogRectangle([x*250+150, 150]))
Таким образом у нас будет не один квадрат, а 5. При необходимости можно добавлять или удалять их.
Запускаем бесконечный цикл (можно поставить остановку при необходимости):
while True: success, img = cap.read() img = cv2.flip(img, 1) img = detector.findHands(img) lmList, _ = detector.findPosition(img)
Рассмотри теперь изображение, где показаны "Hand Land Marks":
Так как ранее было установлено, что кликом
будет соединение указательного и среднего пальцев, то значит нам нужно считывать расстояние между точками 8
и 12
.
if len(lmList) != 0: length, _, _ = detector.findDistance(8, 12, img, draw=False) if length < 40: cursor = lmList[8][1:] # index finger tip landmark # call the update for rect in rectList: rect.update(cursor) # Draw imgNew = np.zeros_like(img, np.uint8) for rect in rectList: cx, cy = rect.posCenter w, h = rect.size cv2.rectangle(imgNew, (cx - w//2, cy - h//2), (cx + w//2, cy + h//2), colorR, cv2.FILLED) cvzone.cornerRect(imgNew, (cx - w//2, cy - h//2, w, h), 20, rt=0) out = img.copy() alpha = 0.1 mask = imgNew.astype(bool) out[mask] = cv2.addWeighted(img, alpha, imgNew, 1-alpha, 0)[mask] cv2.imshow("Image", out) cv2.waitKey(1)
Если клик
не работает и вы не можете перемещать блоки, тогда замените 40
на большее значение, например на 50
, и попробуйте снова:
if length < 40
Эта проблема возникает из-за того, что у каждого человека руки разные и расстояние между пальцами отличается🙄.
Если хотите поменять клик
или указатель
на другие пальцы, то на изображении "Hand Land Marks" можно выбрать те точки, которые будут за это отвечать. После чего необходимо поменять эти строки:
length, _, _ = detector.findDistance(8, 12, img, draw=False)
Вместо 8
и 12
, вписываем выбранные точки, которые будут отвечать за клик.
cursor = lmList[8][1:] # index finger tip landmark
Вместо 8
, вписываем ту точку, которая будет служить указателем (представим, что это курсор мыши).
Для перемещения квадратов (блоков) нужно удерживать клик
, т.е. два пальца должны быть соединены все время, пока мы хотим перемещать тот или иной объект.
Запускаем программу и тестируем🤞:
Ура! Все работает!
Вместо квадратов вы сможете добавить необходимые блоки и использовать Drag-and-Drop
для работы с ними =)