I’m working on a face recognition app. I have a requirement to display a scaled image as large as possible within the client area of the parent widget. I also need to be able to convert between image coordinates and screen coordinates in order to outline the detected faces and detect mouse clicks on the faces displayed.
I was not able to figure out how to meet these various requirements using Qt6 so I reused the following code from another project in .net. Class ScalingImage contains the original image, scales it on demand, paints the scaled image using a supplied QPainter and calculates the transformations between screen and image coordinates.
It works as well as I need it to but I wonder how it might be implemented using Qt.
Any comments gratefully received. Thanks in advance.
from deepface import DeepFace
from PyQt6.QtCore import QPoint, QRect, Qt, pyqtSignal
from PyQt6.QtGui import QColor, QPainter, QPen, QPixmap
from PyQt6.QtWidgets import QApplication, QLabel, QMessageBox, QWidget
import sysclass ImageFile:
def __init__(self, path): self._path = path self._faces = [ Face(i, embedding['facial_area']['x'], embedding['facial_area']['y'], embedding['facial_area']['w'], embedding['facial_area']['h'], embedding['face_confidence']) for i, embedding in enumerate(DeepFace.represent(img_path=self._path, model_name='Facenet', detector_backend='opencv', enforce_detection=False)) if embedding['face_confidence'] >= 0.90 ] def getPath(self): return self._path def getFaces(self): return self._faces
class Face:
def __init__(self, id, x, y, w, h, confidence): self._id = id self._x = x self._y = y self._w = w self._h = h self._confidence = confidence def getBoundingRect(self): return QRect(self._x, self._y, self._w, self._h)
class ScalingImage:
def __init__(self, image): self._original_image = image def resize(self, screenSize): # Calculate scaling. scaleX = screenSize.width() / self._original_image.width() scaleY = screenSize.height() / self._original_image.height() self._scale = scaleX if (scaleX < scaleY) else scaleY scaledImageWidth = int(self._scale * self._original_image.width()) scaledImageHeight = int(self._scale * self._original_image.height()) self._scaledImage = self._original_image.scaled(scaledImageWidth, scaledImageHeight, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio, transformMode=Qt.TransformationMode.SmoothTransformation) # Calculate offsets. self._xofs = int((screenSize.width() - scaledImageWidth) / 2) if screenSize.width() > scaledImageWidth else 0 self._yofs = int((screenSize.height() - scaledImageHeight) / 2) if screenSize.height() > scaledImageHeight else 0 def drawPixmap(self, painter: QPainter): painter.drawPixmap(self._xofs, self._yofs, self._scaledImage) def screenToImage(self, p): return QPoint(int((p.x() - self._xofs) / self._scale), int((p.y() - self._yofs) / self._scale)) def imageToScreen(self, p): return QPoint(int((p.x() * self._scale) + self._xofs), int((p.y() * self._scale) + self._yofs)) def imageToScreen(self, r): return QRect(int((r.x() * self._scale) + self._xofs), int((r.y() * self._scale) + self._yofs), int(r.width() * self._scale), int(r.height() * self._scale))
class ScalingImageWidget(QWidget):
FaceClicked = pyqtSignal(Face) def __init__(self): super().__init__() self._scalingImage = None def setImageFile(self, imgf): self._imgf = imgf self._scalingImage = ScalingImage(QPixmap(self._imgf.getPath())) self.update() def resizeEvent(self, e): super().resizeEvent(e) if self._scalingImage is not None: self._scalingImage.resize(e.size()) def paintEvent(self, e): super().paintEvent(e) if self._scalingImage is not None: painter = QPainter(self) # Draw centered, scaled image. self._scalingImage.drawPixmap(painter) # Outline faces whitePen = QPen() whitePen.setColor(QColor('white')) whitePen.setWidth(2) painter.setPen(whitePen) for face in self._imgf.getFaces(): # need to convert image coordinates to screen coordinates to draw bounding rectangles. boundingRect = self._scalingImage.imageToScreen(face.getBoundingRect()) painter.drawRect(boundingRect) painter.end() def mousePressEvent(self, e): if self._scalingImage is not None: # need to convert screen coordinates to image coordinates to detect which face (if any) was clicked. pos = self._scalingImage.screenToImage(e.pos()) for face in self._imgf.getFaces(): if face.getBoundingRect().contains(pos.x(), pos.y()): QMessageBox.information(self, None, f'Face #{face._id} clicked.') break
imgf_path = r’D:\FaceDb\Friends\images\friends (1).jpg’
imgf = ImageFile(imgf_path)app = QApplication(sys.argv)
label = ScalingImageWidget()
label.setImageFile(imgf)
label.show()app.exec()