jheddings
jheddings

Reputation: 27573

Saving images with OpenCV and accumulateWeighted(...)

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): Displayed Image

This is the image written to disk (using imwrite()): Saved Image

Upvotes: 3

Views: 2879

Answers (1)

Rotem
Rotem

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:
enter image description here

PNG image using imwrite, with camping and scaling:
enter image description here


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

Related Questions