В данной статье расскажу про простой 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
для работы с ними =)