7

I have a loop that modifies elements of a 2D numpy array water_depth with type float. The array contains water depth for each pixel and the range is usually between 0 to 1.5m. I would like to make a video out of this changing array: each iteration can be a frame in the video. I only found this link explaining a similar question and suggests using cv2 VideoWriter. The problem is that my numpy array is a float, not integer. Does this mean that I need to do some sort of pre-processing on my array in each iteration?

import numpy as np

water_depth = np.zeros((500,700), dtype=float)

for i in range(1000):
    random_locations = np.random.random_integers(200,450, size=(200, 2))
    for item in random_locations:
        water_depth[item[0], item[1]] += 0.1
        #add this array to the video
Behzad Jamali
  • 884
  • 2
  • 10
  • 23
  • 3
    Do you actually have OpenCV installed to be able to use it? Because that is one method but not the only way. The easiest way is probably just to save each image individually, and then run them through whatever you like to create a video (for command line tools, the most popular is probably `ffmpeg`). – alkasm Aug 19 '18 at 05:42
  • Yes I installed opencv 64bit yesterday and it works fine. I am fine with any methods. I thought the advantage of using opencv would be its speed. Isn't saving arrays into images costly? If not then any method would be fine. – Behzad Jamali Aug 19 '18 at 05:47
  • 3
    I guess but you could say the same about the video file itself, I mean, it holds all the information about the frames...anyways, if you'd like to make a video using `cv2.VideoWriter()`, then yes, you need to scale and convert your array. `VideoWriter()` needs RGB images (not single-channel grayscale images like your `water_depth`) and needs them to be 8-bit. You can scale them appropriately between 0 and 255 with `cv2.normalize()` and then you can merge that into a three-channel image with `cv2.merge()`. – alkasm Aug 19 '18 at 06:06
  • I guess your comment answers my question. I used `scipy.misc.toimage(water_depth, cmin=0.0, cmax=1.5).save('image\{0}.png'.format(i))` to save my array into image. `scipy.misc.toimage` takes care of the required pre-processing by just indicating the min and max of arrays. – Behzad Jamali Aug 19 '18 at 06:12
  • 2
    If you don't want to go to the trouble of installing OpenCV, have a look at my answer here https://stackoverflow.com/a/46850365/2836621 where I show how to write a video just using `ffmpeg`. It is C++ rather than Python, but it's only a single line. – Mark Setchell Aug 19 '18 at 07:29

1 Answers1

10

Note that working with OpenCV for video I/O purposes can sometimes be tricky. The library isn't built around supporting these kinds of operations, they're just included as a nicety. Typically, OpenCV will be built off ffmpeg support, and whether or not you have the same codecs to read/write videos as another person is somewhat arbitrary depending on your system. With that said, here's an example so you get the idea of exactly what preprocessing you might do:

import numpy as np
import cv2

# initialize water image
height = 500
width = 700
water_depth = np.zeros((height, width), dtype=float)

# initialize video writer
fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
fps = 30
video_filename = 'output.avi'
out = cv2.VideoWriter(video_filename, fourcc, fps, (width, height))

# new frame after each addition of water
for i in range(10):
    random_locations = np.random.random_integers(200,450, size=(200, 2))
    for item in random_locations:
        water_depth[item[0], item[1]] += 0.1
        #add this array to the video
        gray = cv2.normalize(water_depth, None, 255, 0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
        gray_3c = cv2.merge([gray, gray, gray])
        out.write(gray_3c)

# close out the video writer
out.release()

Note that I changed the number of iterations to 10 instead of 1000 just to make sure it worked. Here normalize(..., 255, 0, ...) scales your image so that the max value is 255 (white) and the min value is 0 (black). This means that when at first all your random points start dotting everything, they turn white. However once one point lands on top of another, that will be the brightest---twice as bright as all other points, so they'll immediately drop to be gray. If that's not what you want, you have to think if you'd have a maximum value that your image might be and assume that your images won't change brightness otherwise.

alkasm
  • 22,094
  • 5
  • 78
  • 94
  • 1
    This answer is complete and solved my problem. Is it also possible to make a colour video: zeros as white and the rest lets say from light blue to dark blue? – Behzad Jamali Aug 19 '18 at 06:28
  • 2
    @BehzadJamali, sure, you could do that---probably easiest in the HSV colorspace. You can check out my answers [here](https://stackoverflow.com/a/45071147/5087436) and [here](https://stackoverflow.com/a/46113436/5087436) to get some more familiarity with HSV. – alkasm Aug 19 '18 at 18:10