简体   繁体   English

Python OpenCV:绘制矩形的鼠标回调

[英]Python OpenCV: mouse callback for drawing rectangle

I want to save an image from the video stream and then draw a rectangle onto the shown image to produce a region of interest.我想保存视频 stream 中的图像,然后在显示的图像上绘制一个矩形以生成感兴趣区域。 Later, save that ROI in a file.稍后,将该 ROI 保存在一个文件中。 I used opencv python grabcut example to use the setMouseCallback function. But I don't know what I'm doing incorrect as it is not giving the result I expect.我使用 opencv python grabcut 示例来使用 setMouseCallback function。但我不知道我做错了什么,因为它没有给出我期望的结果。 I would like to see the green rectangle drawn on the static image shown in mouse input window and the roi being saved to file.我想看到在mouse input window 中显示的 static 图像上绘制的绿色矩形,并将 roi 保存到文件中。 Please help debug this code or show a better approach:请帮助调试此代码或展示更好的方法:

import cv2

rect = (0,0,1,1)
rectangle = False
rect_over = False  
def onmouse(event,x,y,flags,params):
    global sceneImg,rectangle,rect,ix,iy,rect_over

    # Draw Rectangle
    if event == cv2.EVENT_LBUTTONDOWN:
        rectangle = True
        ix,iy = x,y

    elif event == cv2.EVENT_MOUSEMOVE:
        if rectangle == True:
            cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
            rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))

    elif event == cv2.EVENT_LBUTTONUP:
        rectangle = False
        rect_over = True
        cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
        rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))

        x1,y1,w,h = rect        
        roi = sceneImg[y1:y1+h, x1:x1+w]

        cv2.imwrite('roi.jpg', roi)

# Named window and mouse callback
cv2.namedWindow('video')
cv2.namedWindow('mouse input')
cv2.setMouseCallback('mouse input',onmouse)

camObj = cv2.VideoCapture(-1)
keyPressed = None
running = True
scene = False
# Start video stream
while running:
    readOK, frame = camObj.read()

    keyPressed = cv2.waitKey(5)
    if keyPressed == ord('s'):
        scene = True

        cv2.imwrite('sceneImg.jpg',frame)
        sceneImg = cv2.imread('sceneImg.jpg')

        cv2.destroyWindow('video')
        cv2.imshow('mouse input', sceneImg)

    elif keyPressed == ord('r'):
        scene = False
        cv2.destroyWindow('mouse input')

    elif keyPressed == ord('q'):
        running = False

    if not scene:
        cv2.imshow('video', frame)

cv2.destroyAllWindows()
camObj.release()

You need to reset the image everytime when the {event == cv2.EVENT_MOUSEMOVE:} called.每次调用 {event == cv2.EVENT_MOUSEMOVE:} 时都需要重置图像。

Your code should look something like this:您的代码应如下所示:

if event == cv2.EVENT_LBUTTONDOWN:
    rectangle = True
    ix,iy = x,y

elif event == cv2.EVENT_MOUSEMOVE:
    if rectangle == True:
        sceneImg = sceneImg2.copy()
        cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
        rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))


elif event == cv2.EVENT_LBUTTONUP:
    rectangle = False
    rect_over = True

    cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
    rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))

This is my current work around where I again render the mouse input window upon EVENT_LBUTTONUP .这是我当前的工作,我再次在EVENT_LBUTTONUP渲染mouse input窗口。 To avoid the bounding box showing up in the ROI saved to file I use a copy of the inputed scene:为了避免在保存到文件的 ROI 中出现边界框,我使用了输入场景的副本:

import cv2

rect = (0,0,1,1)
rectangle = False
rect_over = False  
def onmouse(event,x,y,flags,params):
    global sceneImg,rectangle,rect,ix,iy,rect_over, roi

    # Draw Rectangle
    if event == cv2.EVENT_LBUTTONDOWN:
        rectangle = True
        ix,iy = x,y

    elif event == cv2.EVENT_MOUSEMOVE:
        if rectangle == True:
#            cv2.rectangle(sceneCopy,(ix,iy),(x,y),(0,255,0),1)
            rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))

    elif event == cv2.EVENT_LBUTTONUP:
        rectangle = False
        rect_over = True

        sceneCopy = sceneImg.copy()
        cv2.rectangle(sceneCopy,(ix,iy),(x,y),(0,255,0),1)

        rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))       
        roi = sceneImg[rect[1]:rect[1]+rect[3], rect[0]:rect[0]+rect[2]]

        cv2.imshow('mouse input', sceneCopy)
        cv2.imwrite('roi.jpg', roi)

# Named window and mouse callback
cv2.namedWindow('mouse input')
cv2.setMouseCallback('mouse input',onmouse)
cv2.namedWindow('video')

camObj = cv2.VideoCapture(-1)
keyPressed = None
running = True
scene = False
# Start video stream
while running:
    readOK, frame = camObj.read()

    keyPressed = cv2.waitKey(5)
    if keyPressed == ord('s'):
        scene = True
        cv2.destroyWindow('video')

        cv2.imwrite('sceneImg.jpg',frame)
        sceneImg = cv2.imread('sceneImg.jpg')

        cv2.imshow('mouse input', sceneImg)

    elif keyPressed == ord('r'):
        scene = False
        cv2.destroyWindow('mouse input')

    elif keyPressed == ord('q'):
        running = False

    if not scene:
        cv2.imshow('video', frame)

cv2.destroyAllWindows()
camObj.release()

Thus, I can visualize the rectangle which is supposed to bound the ROI but I still don't know how to visualize the bounding box while the mouse left button is down and the mouse cursor is moving.因此,我可以可视化应该绑定 ROI 的矩形,但我仍然不知道如何在鼠标左键按下且鼠标光标移动时可视化边界框。 That visualization works in the grabcut example but I couldn't figure it out in my case.该可视化在抓取示例中有效,但在我的案例中我无法弄清楚。 Upon uncommenting the line for drawing rectangle during EVENT_MOUSEMOVE I get multiple rectangles drawn onto the image.EVENT_MOUSEMOVE期间取消注释绘制矩形的线后,我会在图像上绘制多个矩形。 If someone answers with a way to visualize a single rectangle as it is being created I can accept it.如果有人回答了一种在创建时可视化单个矩形的方法,我可以接受。

You can use built in function provided by opencv: 您可以使用opencv提供的内置功能:

roi = cv2.selectROI(image)

It creates/displays "ROI selection" window, shows frame. 它创建/显示“ ROI选择”窗口,显示框架。 Use mouse to select bounding box dragging mouse pointer from top-left to bottom-right corner. 使用鼠标选择边界框,将鼠标指针从左上角拖动到右下角。

The function has a couple of not required parameters: 该函数有几个不需要的参数:

selectROI(windowName, img[, showCrosshair[, fromCenter]]) -> retval
  • param windowName name of the window where selection process will be shown. param windowName将显示选择过程的窗口的名称。
  • param img image to select a ROI. 参数img图像以选择ROI。
  • param showCrosshair if true crosshair of selection rectangle will be shown. 如果显示选择矩形的真实十字线,则参数showCrosshair
  • param fromCenter if true center of selection will match initial mouse position. fromCenter参数,如果选择的真实中心将与鼠标的初始位置匹配。 In opposite case a corner of selection rectangle will correspond to the initial mouse position. 在相反的情况下,选择矩形的角将对应于鼠标的初始位置。

returns selected ROI or empty rect if selection canceled. 返回选定的ROI或空白矩形(如果取消选择)。

Controls: use space or enter to finish selection, use key c to cancel selection. 控制:使用spaceenter键完成选择,使用键c取消选择。

Overloaded function doesn't require windowName . 重载的函数不需要windowName

selectROI(img[, showCrosshair[, fromCenter]]) -> retval

Building on top of answer provided by @Marco167, I will just change one line as otherwise there's object reference problem.基于@Marco167 提供的答案,我将只更改一行,否则会出现 object 参考问题。

So, instead of sceneImg = sceneImg2.copy() I'd suggest sceneImg[:] = sceneImg2[:] , where sceneImg2 should be same, separately loaded image, like:因此,我建议sceneImg[:] = sceneImg2[:]而不是sceneImg = sceneImg2.copy() ,其中sceneImg2应该是相同的,单独加载的图像,例如:

sceneImg = cv2.imread('sceneImg.jpg')
sceneImg2 = cv2.imread('sceneImg.jpg')

Also, I've moved rectangle check to the condition.此外,我已将rectangle检查移至该条件。

This way on mouse move, you first redraw the original image (thus removing the existing rectangle) and draw rectangle.这种方式在鼠标移动时,首先重绘原始图像(从而删除现有矩形)并绘制矩形。 Moving the mouse by even a pixel you again redraw the original picture removing any rectangle and draw again one rectangle.将鼠标移动一个像素,您将再次重绘原始图片,删除任何矩形并再次绘制一个矩形。 At any given point there will be just one rectangle.在任何给定点,都只有一个矩形。

Yup, replying after over 7 years, just in case anyone would ever find it useful:)是的,7 年多后回复,以防万一有人发现它有用:)

Putting it all together:把它们放在一起:

if event == cv2.EVENT_LBUTTONDOWN:
    rectangle = True
    ix,iy = x,y

elif event == cv2.EVENT_MOUSEMOVE and rectangle :
    sceneImg[:] = sceneImg2[:]
    cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
    rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))


elif event == cv2.EVENT_LBUTTONUP:
    rectangle = False
    rect_over = True

    cv2.rectangle(sceneImg,(ix,iy),(x,y),(0,255,0),2)
    rect = (min(ix,x),min(iy,y),abs(ix-x),abs(iy-y))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM