【问题标题】:Creating trackbars to scroll large image in OpenCV Python在 OpenCV Python 中创建轨迹栏以滚动大图像
【发布时间】:2015-04-20 04:23:14
【问题描述】:

我正在尝试在 OpenCv python 创建的窗口中创建滚动条。我知道我需要实现代码来处理滚动/平移过程,但我不知道从哪里开始,我到处找。我必须在 OpenCV 窗口中创建滚动条,而不是使用其他一些 GUI 窗口框架。下面是我用来加载图像和缩放图像的代码(有效)。任何帮助表示赞赏。请不要让我参考关于创建轨迹栏的 opencv 文档,我已经阅读了它,但它根本没有帮助。谢谢!

import cv2
import cv2.cv as cv
import numpy as np

cv.NamedWindow('image', cv.CV_WINDOW_AUTOSIZE)
cv.NamedWindow('Control Window', cv.CV_WINDOW_AUTOSIZE)


print " Zoom In-Out demo "
print " Press u to zoom "
print " Press d to zoom "

img = cv2.imread('picture.jpg')


while(1):
    h,w = img.shape[:2]

    cv2.imshow('image',img)
    k = cv2.waitKey(10)

    if k==27 :
        break

    elif k == ord('u'):  # Zoom in, make image double size
        img = cv2.pyrUp(img,dstsize = (2*w,2*h))

    elif k == ord('d'):  # Zoom down, make image half the size
        img = cv2.pyrDown(img,dstsize = (w/2,h/2))

cv2.destroyAllWindows()

【问题讨论】:

  • 你能解释一下为什么你不能把图像放在一个 gui 窗口中吗?
  • 因为我正在对图像进行图像处理,例如获取像素信息,如果我将其封装在 GUI 框架提供的小部件或窗口中,我将失去这种能力

标签: python opencv window scrollbars


【解决方案1】:

我也有同样的需求,所以今天我从头开始创建了一个类,用于处理鼠标点击、平移和缩放 OpenCV 窗口。它的工作原理是这样的:

  1. 右键向上或向下拖动以进行缩放
  2. 右击鼠标使视图居中
  3. 拖动 x 和 y 轨迹条进行滚动
  4. 初始化时,您可以选择传入一个函数,当用户左键单击像素时将调用该函数

(据我所知,OpenCV 无法读取鼠标滚轮,也无法创建垂直轨迹栏,因此用户体验有点不直观,但它可以工作。)

# -*- coding: utf-8 -*-
import cv2
import numpy as np

class PanZoomWindow(object):
    """ Controls an OpenCV window. Registers a mouse listener so that:
        1. right-dragging up/down zooms in/out
        2. right-clicking re-centers
        3. trackbars scroll vertically and horizontally 
    You can open multiple windows at once if you specify different window names.
    You can pass in an onLeftClickFunction, and when the user left-clicks, this 
    will call onLeftClickFunction(y,x), with y,x in original image coordinates."""
    def __init__(self, img, windowName = 'PanZoomWindow', onLeftClickFunction = None):
        self.WINDOW_NAME = windowName
        self.H_TRACKBAR_NAME = 'x'
        self.V_TRACKBAR_NAME = 'y'
        self.img = img
        self.onLeftClickFunction = onLeftClickFunction
        self.TRACKBAR_TICKS = 1000
        self.panAndZoomState = PanAndZoomState(img.shape, self)
        self.lButtonDownLoc = None
        self.mButtonDownLoc = None
        self.rButtonDownLoc = None
        cv2.namedWindow(self.WINDOW_NAME, cv2.WINDOW_NORMAL)
        self.redrawImage()
        cv2.setMouseCallback(self.WINDOW_NAME, self.onMouse)
        cv2.createTrackbar(self.H_TRACKBAR_NAME, self.WINDOW_NAME, 0, self.TRACKBAR_TICKS, self.onHTrackbarMove)
        cv2.createTrackbar(self.V_TRACKBAR_NAME, self.WINDOW_NAME, 0, self.TRACKBAR_TICKS, self.onVTrackbarMove)
    def onMouse(self,event, x,y,_ignore1,_ignore2):
        """ Responds to mouse events within the window. 
        The x and y are pixel coordinates in the image currently being displayed.
        If the user has zoomed in, the image being displayed is a sub-region, so you'll need to
        add self.panAndZoomState.ul to get the coordinates in the full image."""
        if event == cv2.EVENT_MOUSEMOVE:
            return
        elif event == cv2.EVENT_RBUTTONDOWN:
            #record where the user started to right-drag
            self.mButtonDownLoc = np.array([y,x])
        elif event == cv2.EVENT_RBUTTONUP and self.mButtonDownLoc is not None:
            #the user just finished right-dragging
            dy = y - self.mButtonDownLoc[0]
            pixelsPerDoubling = 0.2*self.panAndZoomState.shape[0] #lower = zoom more
            changeFactor = (1.0+abs(dy)/pixelsPerDoubling)
            changeFactor = min(max(1.0,changeFactor),5.0)
            if changeFactor < 1.05:
                dy = 0 #this was a click, not a draw. So don't zoom, just re-center.
            if dy > 0: #moved down, so zoom out.
                zoomInFactor = 1.0/changeFactor
            else:
                zoomInFactor = changeFactor
#            print("zoomFactor: %s"%zoomFactor)
            self.panAndZoomState.zoom(self.mButtonDownLoc[0], self.mButtonDownLoc[1], zoomInFactor)
        elif event == cv2.EVENT_LBUTTONDOWN:
            #the user pressed the left button. 
            coordsInDisplayedImage = np.array([y,x])
            if np.any(coordsInDisplayedImage < 0) or np.any(coordsInDisplayedImage > self.panAndZoomState.shape[:2]):
                print("you clicked outside the image area")
            else:
                print("you clicked on %s within the zoomed rectangle"%coordsInDisplayedImage)
                coordsInFullImage = self.panAndZoomState.ul + coordsInDisplayedImage
                print("this is %s in the actual image"%coordsInFullImage)
                print("this pixel holds %s, %s"%(self.img[coordsInFullImage[0],coordsInFullImage[1]]))
                if self.onLeftClickFunction is not None:
                    self.onLeftClickFunction(coordsInFullImage[0],coordsInFullImage[1])
        #you can handle other mouse click events here
    def onVTrackbarMove(self,tickPosition):
        self.panAndZoomState.setYFractionOffset(float(tickPosition)/self.TRACKBAR_TICKS)
    def onHTrackbarMove(self,tickPosition):
        self.panAndZoomState.setXFractionOffset(float(tickPosition)/self.TRACKBAR_TICKS)
    def redrawImage(self):
        pzs = self.panAndZoomState
        cv2.imshow(self.WINDOW_NAME, self.img[pzs.ul[0]:pzs.ul[0]+pzs.shape[0], pzs.ul[1]:pzs.ul[1]+pzs.shape[1]])

class PanAndZoomState(object):
    """ Tracks the currently-shown rectangle of the image.
    Does the math to adjust this rectangle to pan and zoom."""
    MIN_SHAPE = np.array([50,50])
    def __init__(self, imShape, parentWindow):
        self.ul = np.array([0,0]) #upper left of the zoomed rectangle (expressed as y,x)
        self.imShape = np.array(imShape[0:2])
        self.shape = self.imShape #current dimensions of rectangle
        self.parentWindow = parentWindow
    def zoom(self,relativeCy,relativeCx,zoomInFactor):
        self.shape = (self.shape.astype(np.float)/zoomInFactor).astype(np.int)
        #expands the view to a square shape if possible. (I don't know how to get the actual window aspect ratio)
        self.shape[:] = np.max(self.shape) 
        self.shape = np.maximum(PanAndZoomState.MIN_SHAPE,self.shape) #prevent zooming in too far
        c = self.ul+np.array([relativeCy,relativeCx])
        self.ul = (c-self.shape/2).astype(np.int)
        self._fixBoundsAndDraw()
    def _fixBoundsAndDraw(self):
        """ Ensures we didn't scroll/zoom outside the image. 
        Then draws the currently-shown rectangle of the image."""
#        print("in self.ul: %s shape: %s"%(self.ul,self.shape))
        self.ul = np.maximum(0,np.minimum(self.ul, self.imShape-self.shape))
        self.shape = np.minimum(np.maximum(PanAndZoomState.MIN_SHAPE,self.shape), self.imShape-self.ul)
#        print("out self.ul: %s shape: %s"%(self.ul,self.shape))
        yFraction = float(self.ul[0])/max(1,self.imShape[0]-self.shape[0])
        xFraction = float(self.ul[1])/max(1,self.imShape[1]-self.shape[1])
        cv2.setTrackbarPos(self.parentWindow.H_TRACKBAR_NAME, self.parentWindow.WINDOW_NAME,int(xFraction*self.parentWindow.TRACKBAR_TICKS))
        cv2.setTrackbarPos(self.parentWindow.V_TRACKBAR_NAME, self.parentWindow.WINDOW_NAME,int(yFraction*self.parentWindow.TRACKBAR_TICKS))
        self.parentWindow.redrawImage()
    def setYAbsoluteOffset(self,yPixel):
        self.ul[0] = min(max(0,yPixel), self.imShape[0]-self.shape[0])
        self._fixBoundsAndDraw()
    def setXAbsoluteOffset(self,xPixel):
        self.ul[1] = min(max(0,xPixel), self.imShape[1]-self.shape[1])
        self._fixBoundsAndDraw()
    def setYFractionOffset(self,fraction):
        """ pans so the upper-left zoomed rectange is "fraction" of the way down the image."""
        self.ul[0] = int(round((self.imShape[0]-self.shape[0])*fraction))
        self._fixBoundsAndDraw()
    def setXFractionOffset(self,fraction):
        """ pans so the upper-left zoomed rectange is "fraction" of the way right on the image."""
        self.ul[1] = int(round((self.imShape[1]-self.shape[1])*fraction))
        self._fixBoundsAndDraw()

if __name__ == "__main__":
    infile = "./testImage.png"
    myImage = cv2.imread(infile,cv2.IMREAD_ANYCOLOR)
    window = PanZoomWindow(myImage, "test window")
    key = -1
    while key != ord('q') and key != 27: # 27 = escape key
        #the OpenCV window won't display until you call cv2.waitKey()
        key = cv2.waitKey(5) #User can press 'q' or ESC to exit.
    cv2.destroyAllWindows()

【讨论】:

  • 弄乱你的代码我意识到关闭窗口(不是通过 q 或 ESC)离开 python 并且脚本仍在后台运行。如果您将 while 循环更改为 while key != ord('q') and key != 27 and cv2.getWindowProperty(window.WINDOW_NAME, 0) &gt;= 0:,则此问题已修复。
  • 这适用于 python3,一旦打印语句被修复。此外,您可以通过将self.imShape = np.array(imShape) 替换为self.imShape = np.array(imShape[0:2]) 来添加彩色图像支持。
  • 谢谢@Masterfool,我刚刚编辑了它。
  • 在修改print --> print()/ --> // for python3 之后,这个类在jupyter notebook 中效果很好。但是当我从命令行运行时,捕获右键单击存在问题。右键按下事件被捕获,但右键按下被拦截,因为从 opencv 弹出一个小上下文菜单。知道如何解决这个问题吗?
  • RBUTTONDOWNRBUTTONUP ---> MBUTTONDOWNMBUTTONUP 切换控制事件是一个简单的解决方法
【解决方案2】:

因为我正在对图像进行图像处理,例如获取像素信息,如果我将其封装在 GUI 框架提供的小部件或窗口中,我将失去这种能力

这不是真的。您可以在完成处理后随时更新图像。例如查看herehere especially
这些示例在 OpenCv 中处理图像并将它们放入 PyQt gui 框架中。我相信你可以用其他 Gui 框架做类似的事情(我找不到 Tkinter 的任何东西)。我想我以前见过 wxPython 集成。

在制作程序时,请务必显示图像的副本。这样,图像对象将继续变化,您只需更新 Gui 中的图像即可。例如,这里有一些伪代码:

image=Image("myimage.png")
image.resize(100,400)
img=QImage(image)#similar to how pyqt would work
img.show()
image.invert_colors()
img=QImage(image)
img.show()

当然,这不是你真正要写的,它是一个概念的抽象。

编辑:在这种情况下,我将渲染视频(请参阅 this examplehere),然后将图像作为单独的对象,然后使用 pyqt 渲染(再次作为第三个对象)。要捕捉鼠标点击的位置,请查看this question,最后,将该点引用到第二个对象,即 OpenCV 图像。

【讨论】:

  • 感谢您的意见。我目前正在使用 opencv 捕获和处理相机的视频源,然后我一直在将其转换为适合 pyqt 的形式,就像您建议的那样。问题是我需要拍摄当前帧的快照,显示快照并单击像素以检索其 hsv 值。我还没有尝试将它保存在同一个 pyqt 窗口中;我刚刚创建了一个新的 opencv 窗口。我将尝试在转换为 pyqt 图像后查看快照,看看是否仍然可以获得像素信息。如果可以的话,我会尝试添加滚动条。
猜你喜欢
  • 2022-01-05
  • 2019-12-24
  • 2021-02-09
  • 1970-01-01
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多