Reputation: 27573
I'm new to OpenCV, so apologies if this is a trivial question...
I'm writing an application that tracks the path of an object in real time. So far, I have successfully isolated the object and created a "trail" of its path using cv2.accumulateWeighted()
. Everything looks great in the preview window, but when I save the merged frame to a file, things aren't so good.
The result varies, but typically the saved frame has much less detail than the displayed frame. I've converted the input to grayscale, and often the written file has very "dim" features.
I believe only the final frame is written (multiplied by the alpha blend), rather than the accumulated image. Any ideas would be greatly appreciated.
Sample program to demonstrate the issue:
import cv2
#---- read the next frame from the capture device
def read_frame(cap):
ret, frame = cap.read()
if ret is False or frame is None:
return None
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return gray_frame
#---- setup components
cap = cv2.VideoCapture(index=0)
background_subtractor = cv2.createBackgroundSubtractorMOG2(
history=30, varThreshold=50, detectShadows=False
)
#---- prime the accumulator
frame = read_frame(cap)
merged_frame = frame.astype(float)
#---- capture some frames
while True:
frame = read_frame(cap)
mask = background_subtractor.apply(frame, learningRate=0.01)
foreground = cv2.bitwise_and(frame, frame, mask=mask)
cv2.accumulateWeighted(foreground, merged_frame, 0.1)
cv2.imshow('Acccumulator', merged_frame)
key = cv2.waitKey(1)
# press 'q' to quit and save the current frame
if key == ord('q') or key == ord('Q'):
cv2.imwrite('merged.png', merged_frame)
break
The following are images when moving my hand through the scene... You can see the path of my hand in the displayed image, along with some other background elements. In the saved image, only a very dim version of my hand in the final position is saved.
This is the displayed image (using screen capture):
This is the image written to disk (using imwrite()
):
Upvotes: 3
Views: 2879
Reputation: 32124
I guess you want to save merged_frame
as it shown by cv2.imshow
.
You may limit the upper value of merged_frame
to 1
, scale by 255
, and convert to uint8
type, before saving:
merged_frame = np.round(np.minimum(merged_frame, 1)*255).astype(np.uint8)
The type of merged_frame
is float64
.
When using cv2.imshow
for image of type float
, all the values above 1.0
are white (and below 0
are black).
Gray level of range [0, 1] is equivalent to range [0, 255] of uint8
type (0.5 is like 128).
When using cv2.imwrite
the image is converted to uint8
, but without clamping and scaling (simple cast to 255). The result is usually very dark.
In case you want to save the image as it shown, you need to clamp value to 1, then scale by 255.
You didn't post input samples, so I created synthetic input:
import numpy as np
import cv2
background_subtractor = cv2.createBackgroundSubtractorMOG2(
history=30, varThreshold=50, detectShadows=False
)
width, height = 640, 480
frame = np.full((height, width), 60, np.uint8)
merged_frame = frame.astype(float)
for n in range(100):
img = np.full((height, width, 3), 60, np.uint8)
cv2.putText(img, str(n), (width//2-100*len(str(n)), height//2+100), cv2.FONT_HERSHEY_DUPLEX, 10, (30, 255, 30), 20) # Green number
#frame = read_frame(cap)
frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mask = background_subtractor.apply(frame, learningRate=0.01)
foreground = cv2.bitwise_and(frame, frame, mask=mask)
cv2.accumulateWeighted(foreground, merged_frame, 0.1)
cv2.imshow('Acccumulator', merged_frame)
cv2.waitKey(10)
#merged_frame = cv2.normalize(merged_frame, merged_frame, 0, 255.0, cv2.NORM_MINMAX).astype(np.uint8) # Alternative approch - normalize between 0 and 255
merged_frame = np.round(np.minimum(merged_frame, 1)*255).astype(np.uint8)
cv2.imshow('merged_frame as uint8', merged_frame)
cv2.imwrite('merged.png', merged_frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
PNG image using imwrite
, without camping and scaling:
PNG image using imwrite
, with camping and scaling:
A better way for showing the image, is normalize the values to range [0, 1] before showing the image.
Example:
In the loop, after cv2.accumulateWeighted(foreground, merged_frame, 0.1)
:
norm_acccumulator = merged_frame.copy()
cv2.normalize(norm_acccumulator, norm_acccumulator, 0, 1.0, cv2.NORM_MINMAX)
cv2.imshow('Acccumulator', norm_acccumulator)
Upvotes: 3