英文:
Pyqt5 image coordinates
问题
我在QLabel上显示图像。我需要图像坐标/像素坐标,但是我使用鼠标单击事件只能得到QLabel坐标。
例如,我的图像是800753,我的QLabel几何位置是(701, 451)。我读取到的坐标是(701, 451),但我需要图像坐标(800753)。
def resimac(self):
filename = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
self.image = QtGui.QImage(filename[0])
self.pixmap = QtGui.QPixmap.fromImage(self.image)
self.resim1.setPixmap(self.pixmap)
self.resim1.mousePressEvent = self.getPixel
def getPixel(self, event):
x = event.pos().x()
y = event.pos().y()
print("X=", x, " y=", y)
希望这有所帮助。
英文:
I display images with Qlabel.I need image coordinates/pixel coordinates but, I use mouseclickevent its show me only Qlabel coordinates.
for examples my image is 800753 and my Qlabel geometry is (701,451).I reads coordinates in (701,451) but I need image coordinates in (800753)
def resimac(self):
filename= QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
self.image=QtGui.QImage(filename[0])
self.pixmap=QtGui.QPixmap.fromImage(self.image)
self.resim1.setPixmap(self.pixmap)
self.resim1.mousePressEvent=self.getPixel
def getPixel(self, event):
x = event.pos().x()
y = event.pos().y()
print("X=",x," y= ",y)
答案1
得分: 4
由于您没有提供一个最小可重现的示例,我将假设您可能正在设置scaledContents
属性,但这也可能不是真的(如果您为标签设置了最大或固定大小)。
您的答案还存在一些其他严重问题,我将在本答案的末尾进行讨论。
必须将点映射到像素图坐标
当将像素图设置为QLabel时,Qt会自动调整标签的大小以适应其内容。
但是,除非标签具有某些大小限制:小于像素图的最大/固定大小,并/或QLabel具有设置为True的scaledContents
属性,如上所述。请注意,如果其任何祖先具有某些大小限制(例如,主窗口具有最大大小,或者最大化到小于窗口所需空间的屏幕),也会发生这种情况。
在任何这些情况下,mousePressEvent显然会根据小部件而不是像素图为您提供坐标。
首先,即使它似乎不那么重要,您仍需要考虑每个小部件可能具有一些内容边距:即使事件发生在其实际内容之外的这些边距区域内,小部件仍将接收到这些事件,因此您必须考虑这个方面,并确保事件发生在小部件内容的实际几何形状内(在这种情况下,是像素图)。如果是这样,您将需要将事件位置转换为该矩形以获取其相对于像素图的位置。
然后,如果scaledContents
属性为True,图像将缩放到标签的当前可用大小(这也意味着其纵横比将不会保持不变),因此您需要缩放位置。
这只是数学问题:计算图像大小与(标签的内容)之间的比例,然后使用该比例值乘以该值。
# 单击小部件的水平中心
mouseX = 100
pixmapWidth = 400
widgetWidth = 200
xRatio = pixmapWidth / widgetWidth
# xRatio = 2.0
pixmapX = mouseX * xRatio
# 结果的“x”是像素图的水平中心
# pixmapX = 200
另一方面,如果内容未经缩放,您还需要考虑QLabel的alignment
属性;通常它是左对齐并且垂直居中的,但这取决于操作系统,当前使用的样式和本地化(考虑从右到左的书写语言)。这意味着如果图像小于可用大小,将在其边距内留下一些空白空间,您必须意识到这一点。
在以下示例中,我试图解决所有这些问题(我必须诚实地说,我不是100%确定,因为由于各种原因,最多可能存在1像素的容忍度,主要涉及基于整数坐标和DPI意识)。请注意,与您的方法不同,我没有覆盖mousePressEvent
,而是使用了一个event filter,我将在之后解释原因。
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout(self)
self.getImageButton = QtWidgets.QPushButton('Select')
layout.addWidget(self.getImageButton)
self.getImageButton.clicked.connect(self.resimac)
self.resim1 = QtWidgets.QLabel()
layout.addWidget(self.resim1)
self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
# 我假设以下情况...
self.resim1.setScaledContents(True)
self.resim1.setFixedSize(701,451)
# 安装事件过滤器以“捕获”鼠标事件(以及其他事件)
self.resim1.installEventFilter(self)
def resimac(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
if not filename:
return
self.resim1.setPixmap(QtGui.QPixmap(filename))
def eventFilter(self, source, event):
# 如果源是我们的QLabel,它具有有效的像素图,并且事件是左键单击,请继续尝试获取事件位置
if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and
event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
self.getClickedPosition(event.pos())
return super().eventFilter(source, event)
def getClickedPosition(self, pos):
# 考虑小部件内容边距
contentsRect = QtCore.QRectF(self.resim1.contentsRect())
if pos not in contentsRect:
# 在小部件边距之外,忽略!
return
# 调整位置以适应内容边距
pos -= contentsRect.topLeft()
pixmapRect = self.resim1.pixmap().rect()
if self.resim1.hasScaledContents():
x = pos.x() * pixmapRect.width() / contentsRect.width()
y = pos.y() * pixmapRect.height() / contentsRect.height()
pos = QtCore.QPoint(x, y)
else:
align = self.resim1.alignment()
# 由于历史原因,QRect(基于整数值)将right()返回为(left+width-1)和bottom()返回为(top+
<details>
<summary>英文:</summary>
Since you didn't provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), I'm going to assume that you're probably setting the [`scaledContents`](https://doc.qt.io/qt-5/qlabel.html#scaledContents-prop) property, but that could also be not true (in case you set a maximum or fixed size for the label).
There are some other serious issues about your answer, I'll address them at the end of this answer.
The point has to be *mapped* to the pixmap coordinates
===
When setting a pixmap to a QLabel, Qt automatically resizes the label to its contents.
Well, it does it unless the label has some size constrains: a maximum/fixed size that is smaller than the pixmap, and/or the QLabel has the `scaledContents` property set to True as written above. Note that this also happens if any of its ancestors has some size constraints (for example, the main window has a maximum size, or it's maximized to a screen smaller than the space the window needs).
In any of those cases, the mousePressEvent will obviously give you the coordinates based on the widget, *not* on the pixmap.
First of all, even if it doesn't seem to be that important, you'll have to consider that every widget can have some [contents margins](https://doc.qt.io/qt-5/qwidget.html#contentsMargins): the widget will still receive events that happen *inside the area of those margins*, even if they are outside its actual contents, so you'll have to consider that aspect, and ensure that the event happens within the real geometry of the widget contents (in this case, the pixmap). If that's true, you'll have to translate the event position to that rectangle to get its position according to the pixmap.
Then, if the `scaledContents` property is true, the image will be scaled to the current available size of the label (which also means that its aspect ratio will not be maintained), so you'll need to *scale* the position.
This is just a matter of math: compute the proportion between the image size and the (contents of the) label, then multiply the value using that proportion.
# click on the horizontal center of the widget
mouseX = 100
pixmapWidth = 400
widgetWidth = 200
xRatio = pixmapWidth / widgetWidth
# xRatio = 2.0
pixmapX = mouseX * xRatio
# the resulting "x" is the horizontal center of the pixmap
# pixmapX = 200
On the other hand, if the contents are not scaled you'll have to consider the QLabel [`alignment`](https://doc.qt.io/qt-5/qlabel.html#alignment-prop) property; it is usually aligned on the left and vertically centered, but that depends on the OS, the style currently in use *and the localization* (consider right-to-left writing languages). This means that if the image is smaller than the available size, there will be some empty space within its margins, and you'll have to be aware of that.
In the following example I'm trying to take care about all of that (I'd have to be honest, I'm not 100% sure, as there *might* be some 1-pixel tolerance due to various reasons, most regarding integer-based coordinates and DPI awareness).
Note that instead of overwriting `mousePressEvent` as you did, I'm using an [event filter](https://doc.qt.io/qt-5/qobject.html#eventFilter), I'll explain the reason for it afterwards.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def init(self):
QtWidgets.QWidget.init(self)
layout = QtWidgets.QGridLayout(self)
self.getImageButton = QtWidgets.QPushButton('Select')
layout.addWidget(self.getImageButton)
self.getImageButton.clicked.connect(self.resimac)
self.resim1 = QtWidgets.QLabel()
layout.addWidget(self.resim1)
self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
# I'm assuming the following...
self.resim1.setScaledContents(True)
self.resim1.setFixedSize(701,451)
# install an event filter to "capture" mouse events (amongst others)
self.resim1.installEventFilter(self)
def resimac(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
if not filename:
return
self.resim1.setPixmap(QtGui.QPixmap(filename))
def eventFilter(self, source, event):
# if the source is our QLabel, it has a valid pixmap, and the event is
# a left click, proceed in trying to get the event position
if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and
event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
self.getClickedPosition(event.pos())
return super().eventFilter(source, event)
def getClickedPosition(self, pos):
# consider the widget contents margins
contentsRect = QtCore.QRectF(self.resim1.contentsRect())
if pos not in contentsRect:
# outside widget margins, ignore!
return
# adjust the position to the contents margins
pos -= contentsRect.topLeft()
pixmapRect = self.resim1.pixmap().rect()
if self.resim1.hasScaledContents():
x = pos.x() * pixmapRect.width() / contentsRect.width()
y = pos.y() * pixmapRect.height() / contentsRect.height()
pos = QtCore.QPoint(x, y)
else:
align = self.resim1.alignment()
# for historical reasons, QRect (which is based on integer values),
# returns right() as (left+width-1) and bottom as (top+height-1),
# and so their opposite functions set/moveRight and set/moveBottom
# take that into consideration; using a QRectF can prevent that; see:
# https://doc.qt.io/qt-5/qrect.html#right
# https://doc.qt.io/qt-5/qrect.html#bottom
pixmapRect = QtCore.QRectF(pixmapRect)
# the pixmap is not left aligned, align it correctly
if align & QtCore.Qt.AlignRight:
pixmapRect.moveRight(contentsRect.x() + contentsRect.width())
elif align & QtCore.Qt.AlignHCenter:
pixmapRect.moveLeft(contentsRect.center().x() - pixmapRect.width() / 2)
# the pixmap is not top aligned (note that the default for QLabel is
# Qt.AlignVCenter, the vertical center)
if align & QtCore.Qt.AlignBottom:
pixmapRect.moveBottom(contentsRect.y() + contentsRect.height())
elif align & QtCore.Qt.AlignVCenter:
pixmapRect.moveTop(contentsRect.center().y() - pixmapRect.height() / 2)
if not pos in pixmapRect:
# outside image margins, ignore!
return
# translate coordinates to the image position and convert it back to
# a QPoint, which is integer based
pos = (pos - pixmapRect.topLeft()).toPoint()
print('X={}, Y={}'.format(pos.x(), pos.y()))
if name == 'main':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Now. A couple of suggestions.
Don't overwrite existing child object methods with [other] object's instance attributes
---
There are various reasons for which this is not a good idea, and, while dealing with Qt, the most important of them is that Qt uses **function caching** for *virtual functions*; this means that as soon as a virtual is called the *first* time, that function will **always** be called in the future. While your approach *could* work in simple cases (especially if the overwriting happens within the parent's `__init__`), it's usually prone to unexpected behavior that's difficult to debug if you're not very careful.
And that's exactly your case: I suppose that `resimac` is not called upon parent instantiation and until after some other event (possibly a clicked button) happens. **But** if the user, for some reason, clicks on the label *before* a new pixmap is loaded, your supposedly overwritten method will **never** get called: at that time, you've not overwritten it yet, so the user clicks the label, Qt calls the QLabel's base class `mousePressEvent` implementation, and then that method will *always* be called from that point on, no matter if you try to overwrite it.
To work around that, you have at least 3 options:
1. use an event filter (as the example above); an event filter is something that "captures" events of a widgets and allows you to observe (and interact) with it; you can also decide to propagate that event to the widget's parent or not (that's mostly the case of key/mouse events: if a widget isn't "interested" about one of those events, it "tells" its parent to care about it); this is the simplest method, but it can become hard to implement and debug for complex cases;
2. subclass the widget and manually add it to your GUI within your code;
3. subclass it and "promote" the widget if you're using Qt's Designer;
You don't need to use a QImage for a QLabel.
---
This is not that an issue, it's just a suggestion: QPixmap already uses (*sort of*) `fromImage` within its C++ code when constructing it with a path as an argument, so there's no need for that.
Always, *always* provide usable, Minimal Reproducible Example code.
---
See:
- https://stackoverflow.com/help/how-to-ask
- https://stackoverflow.com/help/minimal-reproducible-example
It could take time, even hours to get an "MRE", but it's worth it: there'll always somebody that could answer you, but doesn't want to or couldn't dig into your code for various reasons (mostly because it's incomplete, vague, inusable, lacking context, or even too expanded). If, for *any* reason, there'll be just *that* one user, you'll be losing your occasion to solve your problem. Be patient, carefully prepare your questions, and you'll probably get plenty of interactions and useful insight from it.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论