I have a PyQt5 GUI application. I read a frame from the camera to select a smaller area on it, and later I would like to display only that area. I correctly select the area, and store it in a QRect. But when I try to crop the frames to display and to process later, the displayed live video just sometimes works correctly. but most of the times it looks like on this picture. It becomes slanted, the right corner should be the left corner. And the pink circle should be in the middle of the video, so it is kinda wrong.
I have this Worker1 class, where I process the displayed frames.
class Worker1(QThread):
ImageUpdate = pyqtSignal(QImage)
def __init__(self, canvas, selection):
super().__init__()
self.ThreadActive = True
self.canvas = canvas
self.anim = None
self.selection = selection
def run(self):
Capture = cv.VideoCapture(0)
old_closest_contour = None
d_tresh = 300
P_color = (0, 255, 0)
global i
i = 0
while self.ThreadActive:
i += 1
ret, frame = Capture.read()
if ret:
if self.selection is None or self.selection.isEmpty():
cropped_frame = frame
else:
a = int(self.selection.x())
b = int(self.selection.y())
width = int(self.selection.width())
height = int(self.selection.height())
#print(str(a) + " " + str(b) + " " + str(width) + " " + str(height))
cropped_frame = frame[b: b + height, a: a + width].copy()
#print(cropped_frame.shape[1])
#print(cropped_frame.shape[0])
### other processing lines, those are not relevant in the displaying ###
Image = cv.cvtColor(cropped_frame, cv.COLOR_BGR2RGB)
img = cv.medianBlur(Image, 25)
cv.circle(img, (x, y), r, P_color, 1)
cv.circle(img, (x, y), 4, P_color, -1)
cv.circle(img, point, 0, (255, 0, 255), 10)
ConvertToQtFormat = QImage(img.data, img.shape[1], img.shape[0], QImage.Format_RGB888)
self.ImageUpdate.emit(ConvertToQtFormat)
Capture.release()
We have to set bytesPerLine
argument.
Replace ConvertToQtFormat = QImage(img.data, img.shape[1], img.shape[0], QImage.Format_RGB888)
with:
ConvertToQtFormat = QImage(img.data, img.shape[1], img.shape[0], img.strides[0], QImage.Format_RGB888)
img.strides[0]
applies the number of bytes in each line of img
.
When img.shape[1] = 100
, img.strides[0]
is usually equal 100*3
= 300
bytes when there are 3 bytes per pixel.
The reason that we have the strides
is that there are cases when lines are not continuous in memory, and the stride doesn't equal width * 3.
The equivalent parameter in QImage
object is bytesPerLine
.
By default, QImage object assumes that bytesPerLine
is a multiple of 4.
In case width*3 is not a multiple of 4, padding bytes are assumed to be present at the end of each line.
For example, when width = 101, bytesPerLine
is 304 instead of 303 (1 padding byte is assumed).
(The multiple of 4 assumption is originated from the BMP image format).
In our case:
img.shape[1]
is a multiple of 4, the image is going to look correct.img.shape[1]
is a not multiple of 4, the image is going to be "slanted".bytesPerLine
and NumPy strides
.The solution is setting the bytesPerLine
parameter (using an overloaded QImage
constructor).
In general, when converting from NumPy to QImage, we should set img.strides[0]
as bytesPerLine
argument.