0

I need to create a graphic that shows in real time RGB from a video. I know that I have to capture a frame and plot 3 channels in a graphic and in the axis x plot time. But I am having a error. Webcam opens normally and a blank graph is showed at the screen. Does someone has any ideas that how can I write this code?

import cv2
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

capture = cv2.VideoCapture(0)
ret, frame = capture.read()

fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)


while True:
    ret, frame = capture.read()
    cv2.imshow('Frame', frame)

    if not ret:
        break


    def animate(i):

        b, g, r = cv2.split(frame)
        xs = []
        ys = []
        for line in b:
            if len(line) > 1:
                x, y = line.split(',') #Getting the error: 'numpy.ndarray has no attribute split'.
                xs.append(x)
                ys.append(y)
        ax1.clear()
        ax1.plot(xs, ys)


    ani = FuncAnimation(fig, animate, interval=1000)
    plt.show()

    keyval = cv2.waitKey(1) & 0xFF

    if keyval == ord('q'):
        break


capture.release()
cv2.destroyAllWindows()

    
    

1 Answers1

1

You want to display an interactive graph while receiving webcam frames.

    1. You can use plt.ion(). The ion() method updates the graph while receiving inputs.
    • You may ask Why not using FuncAnimation?

      • From what I understand FuncAnimation method is not designed for while loop. FuncAnimation is useful for pre-computed frames. Here you can see the example1 and example2.
    1. How to get frames for each second?
    • We can calculate the fps (frame-per-second) value. For instance, if the fps value is 5, then 5 frames duration is equal to the 1 second.

    • We can count the frames using a variable and check whether the variable equals the fps. Then we add the frames.

      • if frame_count % fps == 0:
            b, g, r = cv2.split(frame)
        
            line = [line for line in zip(b, g, r) if len(line)]
        
            xs.append(second)
            blue.append(np.mean(line[0]))
            green.append(np.mean(line[1]))
            red.append(np.mean(line[2]))
        
      • You may ask Why not using time.time for calculating the seconds?

      • I think using fps is more reliable than using time.time. I wanted to guarantee to get frames for each second.

Result:

enter image description here

I've obtained the result from the command prompt. If you debug it, you may get multiple images instead of one updated image.

Update-1


If you want to separate the channels you can multiply the each calculated variance with a different coefficient:

blue.append(np.var(line[0])*0.02)
green.append(np.var(line[1])*0.03)
red.append(np.var(line[2])*0.04)

Result:

enter image description here

  • You can also use np.mean
blue.append(np.mean(line[0])*0.02)
green.append(np.mean(line[1])*0.03)
red.append(np.mean(line[2])*0.04)

Result:

enter image description here

Update-2


If you want to write the output data to the excel, you can use the xlsxwriter library.

You can install:


  • pip environment: pip install xlsxwriter
  • anaconda environment: conda install -c anaconda xlsxwriter

Three-steps:


  • Step1: Create worksheet:

    • book = Workbook('Channel.xlsx')
      sheet = book.add_worksheet()
      
  • Step2: Initialize row and column variables:

    • row = 0
      col = 0
      
  • Step3: Write

    • for s, b, g, r in zip(xs, blue, green, red):
          sheet.write(row, col, s)
          sheet.write(row + 1, col, b)
          sheet.write(row + 2, col, g)
          sheet.write(row + 3, col, r)
          col += 1
      

Output:

enter image description here

Update-3


    1. Time

  • In previous updates, the fps = cv2.CAP_PROP_FPS which was wrong. The correct usage is fps = capture.get(cv2.CAP_PROP_FPS). Now we are getting the webcam's fps.

    1. Speeding-up

  • We were using red, green, blue, xs list structures for storing frames and seconds for both displaying and writing to the excel file.

  • As frame data increases the list structures become a burden for real-time processing. Therefore one solution is dividing the displaying and writing operations.

  • For displaying: b_frame, g_frame, r_frame, and s_frame are used.

  • For writing to excel: blue, green, red, and xs are used.

  • The main advantage is now we can reduce the storage for displaying frames. Since we are storing in blue, green, red, and xs.

  • For instance: after two seconds delete the first frames.

    del b_frame[0]
    del g_frame[0]
    del r_frame[0]
    del s_frame[0]
    
  • Since the b_frame, g_frame, r_frame, and s_frame are no longer containing all the frames. The system speeds-up.

Update-4


VideoCapture blocks the application while the next frame is read, decode, and returned. Most probably this is the reason for the frozen camera.

One option is using VideoStream which process read, decode, and return actions concurrently by using queue structure.

To install imutils


  • For pip: pip install imutils
  • For anaconda: conda install -c conda-forge imutils

Example:


from imutils.video import VideoStream

vs = VideoStream().start()

while True:
    frame = vs.read()
    
    if frame is None:
        break
     
    .
    .


vs.stop()

I tested VideoStream and there were no frozen frames or pause during the application.

Code:


import cv2
import numpy as np
import matplotlib.pyplot as plt

from imutils.video import VideoStream
from xlsxwriter import Workbook

fig = plt.figure()

plt.ion()  # Set interactive mode on

xs = []
blue = []
red = []
green = []

b_frame = []
g_frame = []
r_frame = []
s_frame = []

# We will be using Video-capture to get the fps value.
capture = cv2.VideoCapture(0)
fps = capture.get(cv2.CAP_PROP_FPS)
capture.release()

# New module: VideoStream
vs = VideoStream().start()

frame_count = 0
second = 1

is_new_frame = False

while True:
    frame = vs.read()

    if frame is None:
        break

    if frame_count % int(fps) == 0:
        b, g, r = cv2.split(frame)

        is_new_frame = True  # New frame has come

        line = [line for line in zip(b, g, r) if len(line)]

        s_frame.append(second)
        b_frame.append(np.mean(line[0]) * 0.02)
        g_frame.append(np.mean(line[1]) * 0.03)
        r_frame.append(np.mean(line[2]) * 0.04)

        plt.plot(s_frame, b_frame, 'b', label='blue', lw=7)
        plt.plot(s_frame, g_frame, 'g', label='green', lw=4)
        plt.plot(s_frame, r_frame, 'r', label='red')
        plt.xlabel('seconds')
        plt.ylabel('mean')
        if frame_count == 0:
            plt.legend()
        plt.show()

        second += 1

    elif second > 2:

        if is_new_frame:

            if second == 3:
                blue.extend(b_frame)
                green.extend(g_frame)
                red.extend(r_frame)
                xs.extend(s_frame)
            else:
                blue.append(b_frame[len(b_frame)-1])
                green.append(g_frame[len(g_frame)-1])
                red.append(r_frame[len(r_frame)-1])
                xs.append(s_frame[len(s_frame)-1])

            del b_frame[0]
            del g_frame[0]
            del r_frame[0]
            del s_frame[0]

            is_new_frame = False  # we added the new frame to our list structure

    cv2.imshow('Frame', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

    frame_count += 1

cv2.destroyAllWindows()
capture.release()
vs.stop()

book = Workbook('Channel.xlsx')
sheet = book.add_worksheet()

row = 0
col = 0

sheet.write(row, col, 'Seconds')
sheet.write(row + 1, col, 'Blue mean')
sheet.write(row + 2, col, 'Green mean')
sheet.write(row + 3, col, 'Red mean')

col += 1

for s, b, g, r in zip(xs, blue, green, red):
    sheet.write(row, col, s)
    sheet.write(row + 1, col, b)
    sheet.write(row + 2, col, g)
    sheet.write(row + 3, col, r)
    col += 1

book.close()
Ahmet
  • 7,527
  • 3
  • 23
  • 47
  • Thank you for your answer! Let me explain better what I need. While webcam is opened, I want to show a graph that has at the axis 'x' time in seconds and at the axis 'y' pixel value . Three channels, three lines showing the variation of the channel beyond the time. The code that I wrote above I am trying to do that. Passing a empty list while loop while is running. But I don't know whether my logic is right or not. – Leonardo Guimarães Sep 15 '20 at 18:19
  • Do you want to display three channel's variation in a single graph? – Ahmet Sep 15 '20 at 19:56
  • Yes! Well, it might be in three graphs as well. But it would be better it was in a single graph. I need that because I am doing a chemical experiment which depends on color variation. – Leonardo Guimarães Sep 15 '20 at 20:09
  • I've updated my answer. Could you please tell me if it is ok or not? – Ahmet Sep 15 '20 at 20:43
  • Ahmet, thank you! At the axis 'y' works perfect to me. Make every sense to use the mean of the channels. But at the axis 'x' still have a problem. I need really seconds. Counting from zero and finish just when I press 'q'. I need a real time graph. The graph is static,It is not moving.It shows me just a 'frozen' frame and a graph from this specific frame but I need all frames. My experiment measures the mean of color channels in function of time so I need a real time graph which has a real counting frames in seconds at the axis 'x' I tried to use like i = 0 and xs.append(i) but is not working. – Leonardo Guimarães Sep 16 '20 at 01:25
  • I've updated my answer. Could you please tell me if it is ok or not? – Ahmet Sep 16 '20 at 11:13
  • It is working! I tried to use time.time as well but it didn't work. Your code is working but I had to add fig.canvas.draw() because plt.show was not working. I don't know why. Just three more questions: legend of the graph is not appearing, perhaps conditional frame_count == 1 would be equal zero? Lines are all together there is a way to separete them even pixel value are very close? Is there a way that I can get the data of the graph like a data frame pixel value x time? By the way, thank you very much for your support. – Leonardo Guimarães Sep 16 '20 at 17:45
  • I've updated my naswer again for the 1st and 2nd questions. You can see the results under the update title. For your 3rd question I need time to build but in the mean time, you may achieve what you want by lookin g at this [answer](https://stackoverflow.com/questions/7908636/possible-to-make-labels-appear-when-hovering-over-a-point-in-matplotlib/47166787). – Ahmet Sep 16 '20 at 19:28
  • Nice! I thought exactly like you! Multiply np.mean for a constant to separate the lines. Regarding 3rd question I mean to get data from the graph like at the end of the experiment I would have a excel sheet wiht pixel value versus time. I don't know if it happened with you as well but my webcam is with delay while running the interactive graph. Does it happen with you too? – Leonardo Guimarães Sep 16 '20 at 20:58
  • I've updated the answer, you can see the result of writing to the excel. – Ahmet Sep 17 '20 at 18:42
  • I fixed the problem that I had by adding time.sleep(0.01). So, I have realized that after 1, 2 minutes of recording webcam becomes so slow, very, very slow. I tried to record a experiment but I couldn't because webcam and graph became very slow(crazy delay) and after 2 minutes of recording and then graph and webcam closed together automatically. If I fix that I can use this code to make my experiment. Do you know what might be it? – Leonardo Guimarães Sep 20 '20 at 05:14
  • I've never tested for 2 minutes. I will be working on the problem today – Ahmet Sep 20 '20 at 06:24
  • I've updated my answer, could you please check the update-3 and below code? – Ahmet Sep 20 '20 at 23:08
  • I have tested and it is so much better but still has a delay. I need to record my experiment at least for 10 minutes because about in 5 minutes color starts to change. I tried today is so much better but still has a delay and after some time camera is like frozen.Read and freeze. Read and Freeze. It is faster than before but it is freezing yet. I could't record my experiment. Do you think if we delate more frames as it increases, might speeds up ? In your upadte code you delete just the initial frames, right? – Leonardo Guimarães Sep 21 '20 at 16:14
  • Yes I update the code by deleting initial frames in the `b_frame`, `g_frame`, `r_frame` and `s_frames`. So I think I found a solution for frozen frames, using `VideoStream`. I tested for 10 minutes and there weren't any frozen frame or delays. You can check it under the Update-4. – Ahmet Sep 21 '20 at 21:27
  • Did you get it? No frozen frames anymore? I tried to record an experiment right now with your update code but I couldn’t still. Recording is still freezing. I thought it was my computer but I am using a good alienware to code and record my experiments. So, I don’t know : ( It is hard. My master degree depends on that hahaha. Thanks for your supporting, bro. – Leonardo Guimarães Sep 22 '20 at 13:37
  • That's strange, I mean I did not have any frozen frames during my recording. Are you using windows? if so maybe it is better to stop background processes and then run the code via command-prompt via administrator rights. Still, you can do the same if you are using Linux. In mac, there was no problem. – Ahmet Sep 22 '20 at 13:45
  • Any delay?Weird. I am still having delay. You make a movement and take 2, 3s to read. Yes, I am using windows. I did what you wrote. Stoped bckground process and cod via prompt but recording is freezing yet. – Leonardo Guimarães Sep 22 '20 at 14:05
  • Do you know if there is another way to increase the fps? To stop delaying? I still have this problem. – Leonardo Guimarães Sep 30 '20 at 23:50