0

I'm currently working on a project that takes in a video file, reads individual frames as grayscale, normalizes them, thresholds them, and then outputs them as individual .jpg files. Below I have two functions, frameCapture() and frameCaptureMulti(). The former uses cv2.threshold and cv2.THRESH_OTSU and works as intended. The latter uses threshold_multiotsu() from skimage.filters and outputs completely black frames.

import cv2
import numpy as np
from skimage.filters import threshold_multiotsu
def frameCapture(fc_path):
    fc_vidObj = cv2.VideoCapture(fc_path) # Path to video file
    fc_count = 0                          # Used as counter variable
    fc_success = 1                        # Checks if the frames were extracted

    while fc_success:
        # vidObj object calls read
        fc_success, fc_image = fc_vidObj.read()
        fc_image = cv2.cvtColor(fc_image, cv2.COLOR_BGR2GRAY) # Convert the image to grayscale
        fc_image = cv2.GaussianBlur(fc_image,(5,5),0) # Gaussian noise filtering
        (threshold, fc_image) = cv2.threshold(fc_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # Normalize & Segment
        cv2.imwrite("frame%d.jpg" % fc_count, fc_image) # Saves the frames
        fc_count += 1
def frameCaptureMulti(fc_path):
    fc_vidObj = cv2.VideoCapture(fc_path) # Path to video file
    fc_count = 0                          # Used as counter variable
    fc_success = 1                        # Checks if the frames were extracted

    while fc_success:
        # vidObj object calls read
        fc_success, fc_image = fc_vidObj.read()
        fc_image = cv2.cvtColor(fc_image, cv2.COLOR_BGR2GRAY) # Convert the image to grayscale
        fc_image = cv2.GaussianBlur(fc_image,(5,5),0) # Gaussian noise filtering
        fc_thresholds = threshold_multiotsu(fc_image)
        fc_regions = np.digitize(fc_image, bins=fc_thresholds)
        cv2.imwrite("frame%d.jpg" % fc_count, fc_regions) # Saves the frames
        fc_count += 1

The driver code simply runs frameCaptureMulti() on a .mp4 file on my computer.

Here's what the frames look like:
frameCapture() output frameCapture() output

frameCaptureMulti() output: frameCaptureMulti() output

I am not sure why frameCaptureMulti() produces pure black frames. I only have a few weeks experience with Python (outside of learning about ten years ago) and even less with these particular libraries, so any help as to why my code isn't producing expected output is welcome.

Pages referenced for portions of code in frameCapture and frameCaptureMulti:
https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_multiotsu.html
https://www.geeksforgeeks.org/python-program-extract-frames-using-opencv/

  • Could you provide an example frame as an image that gives a black frame result? I think everything else is ancillary. Having said this, I think what's happening is that CV2 gives you a binary image that is correctly saved as a frame with 0 under the threshold and 255 (white) above it. Meanwhile, threshold_multiotsu and np.digitize return an image with values 0, 1, 2, all of which look black in the 0-255 range supported by jpeg. You could use skimage.exposure.rescale_intensity to map those values to e.g. 0, 127, 255. – Juan Jan 31 '20 at 05:50
  • Thank you! The implementation of skimage.exposure.rescale_intensity not only produced a proper image, but was an improvement in thresholding from the original image! This is the solution. If you'd like, please post it in the form of an answer so I can accept it and give you proper credit. – Anthony Hackett Jan 31 '20 at 17:23
  • Great to hear! I thought that might be it but couldn't be sure without testing it, so I was cautious. Glad to hear you're moving forward! – Juan Feb 02 '20 at 01:03

1 Answers1

1

I think what's happening is that CV2 gives you a binary image that is correctly saved as a frame with 0 under the threshold and 255 (white) above it. Meanwhile, threshold_multiotsu and np.digitize return an image with values 0, 1, 2, all of which look black in the 0-255 range supported by jpeg. You could use skimage.exposure.rescale_intensity to map those values to e.g. 0, 127, 255.

Juan
  • 5,433
  • 21
  • 23