Phần 3. Đọc và ghi video sử dụng OpenCV

0
9147
video_opencv

Đọc và ghi video trong OpenCV rất giống với đọc và ghi hình ảnh. Một video bản chất là một loạt các hình ảnh thường được gọi là khung hình. Bài viết này sẽ trình bày cách đọc, hiển thị và ghi video từ tệp, chuỗi hình ảnh và webcam. Một số lỗi có thể xảy ra trong quá trình này và cách giải quyết cũng sẽ được đề cập trong bài viết. Phần 3 gồm các nội dung chính sau:

1. Đọc video từ một tệp

2. Đọc một chuỗi hình ảnh

3. Đọc video từ webcam

4. Ghi video

5. Những lỗi có thể gặp khi đọc và ghi video

Trước tiên, hãy xem qua ví dụ code để đọc một tệp video. Code sẽ chứa các hàm cơ bản để đọc video và hiển thị nó.

Python

import cv2 

# Create a video capture object, in this case we are reading the video from a file
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')

if (vid_capture.isOpened() == False):
	print("Error opening the video file")
# Read fps and frame count
else:
	# Get frame rate information
	# You can replace 5 with CAP_PROP_FPS as well, they are enumerations
	fps = vid_capture.get(5)
	print('Frames per second : ', fps,'FPS')

	# Get frame count
	# You can replace 7 with CAP_PROP_FRAME_COUNT as well, they are enumerations
	frame_count = vid_capture.get(7)
	print('Frame count : ', frame_count)

while(vid_capture.isOpened()):
	# vid_capture.read() methods returns a tuple, first element is a bool 
	# and the second is frame
	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('Frame',frame)
		# 20 is in milliseconds, try to increase the value, say 50 and observe
		key = cv2.waitKey(20)
		
		if key == ord('q'):
			break
	else:
		break

# Release the video capture object
vid_capture.release()
cv2.destroyAllWindows()

C++

// Include Libraries
#include<opencv2/opencv.hpp>
#include<iostream>

// Namespace to nullify use of cv::function(); syntax
using namespace std;
using namespace cv;

int main()
{
	// initialize a video capture object
	VideoCapture vid_capture("Resources/Cars.mp4");

	// Print error message if the stream is invalid
	if (!vid_capture.isOpened())
	{
		cout << "Error opening video stream or file" << endl;
	}

	else
	{
		// Obtain fps and frame count by get() method and print
		// You can replace 5 with CAP_PROP_FPS as well, they are enumerations
		int fps = vid_capture.get(5);
		cout << "Frames per second :" << fps;

		// Obtain frame_count using opencv built in frame count reading method
		// You can replace 7 with CAP_PROP_FRAME_COUNT as well, they are enumerations
		int frame_count = vid_capture.get(7);
		cout << "  Frame count :" << frame_count;
	}


	// Read the frames to the last frame
	while (vid_capture.isOpened())
	{
		// Initialise frame matrix
		Mat frame;

	    // Initialize a boolean to check if frames are there or not
		bool isSuccess = vid_capture.read(frame);

		// If frames are present, show it
		if(isSuccess == true)
		{
			//display frames
			imshow("Frame", frame);
		}

		// If frames are not there, close it
		if (isSuccess == false)
		{
			cout << "Video camera is disconnected" << endl;
			break;
		}
		
		//wait 20 ms between successive frames and break the loop if key q is pressed
		int key = waitKey(20);
		if (key == 'q')
		{
			cout << "q key is pressed by the user. Stopping the video" << endl;
			break;
		}


	}
	// Release the video capture object
	vid_capture.release();
	destroyAllWindows();
	return 0;
}

Những hàm chính sẽ được sử dụng trong đọc và ghi video bằng OpenCV gồm:

– cv2.VideoCapture: Tạo đối tượng quay video, đối tượng này sẽ giúp phát trực tuyến hoặc hiển thị video.

– cv2.VideoWriter: Lưu video vào đường dẫn cụ thể.

– Một số hàm thông dụng liên quan đến xử lý video như cv2.imshow(), cv2.waitKey() và phương thức get() dùng để lấy các dữ liệu như chiều cao, độ rộng của từng frame và số frame trên giây cũng sẽ được giới thiệu trong bài viết.

Trong bài viết này, video tên “Cars.mp4” sẽ được dùng để làm ví dụ. Đầu tiên là khai báo thư viện OpenCV:

Python

# Import libraries
import cv2

C++

// Include Libraries
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

1. Đọc video từ một tệp

Cú pháp:

VideoCapture(path, apiPreference)

Tham số đầu tiên là tên và đường dẫn tới tệp video. Tham số thứ 2 là tuỳ chọn API.

Python

# Create a video capture object, in this case we are reading the video from a file
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')

C++

# Create a video capture object, in this case we are reading the video from a file
VideoCapture vid_capture("Resources/Cars.mp4");

Bây giờ chúng ta có một đối tượng quay video, chúng ta có thể sử dụng phương thức isOpened () để xác nhận tệp video đã được mở thành công. Phương thức isOpened () trả về một giá trị kiểu boolean cho biết luồng video có hợp lệ hay không. Nếu không hợp lệ sẽ nhận được thông báo lỗi. Thông báo lỗi có thể thể hiện nhiều vấn đề. Một trong số đó là toàn bộ video bị lỗi, hoặc một số khung hình bị hỏng. Giả sử tệp video đã được mở thành công, chúng ta có thể sử dụng phương thức get () để truy xuất dữ liệu quan trọng được liên kết với luồng video. Lưu ý rằng phương pháp này không áp dụng cho webcam. Phương thức get () nhận một đối số từ danh sách các tùy chọn được liệt kê ở đây. Ví dụ dưới đây sử dụng giá trị 5 và 7, tương ứng với tốc độ khung hình (CAP_PROP_FPS) và số lượng khung hình (CAP_PROP_FRAME_COUNT). Giá trị số hoặc tên đều có thể được sử dụng trong cú pháp.

Python

if (vid_capture.isOpened() == False):
	print("Error opening the video file")
else:
	# Get frame rate information

	fps = int(vid_capture.get(5))
	print("Frame Rate : ",fps,"frames per second")	

	# Get frame count
	frame_count = vid_capture.get(7)
	print("Frame count : ", frame_count)

C++

if (!vid_capture.isOpened())
	{
		cout << "Error opening video stream or file" << endl;
	}
else
	{
            // Obtain fps and frame count by get() method and print
		int fps = vid_capture.get(5):
		cout << "Frames per second :" << fps;
		frame_count = vid_capture.get(7);
		cout << "Frame count :" << frame_count;
	}

Sau khi truy xuất các thông số mong muốn của tệp video thì có thể đọc từng khung hình ảnh từ tệp. Điều này được thực hiện bằng cách tạo một vòng lặp và đọc từng khung hình từ luồng video bằng phương thức vid_capture.read ().

Phương thức vid_capture.read () trả về kết quả dạng tuple, trong đó phần tử đầu tiên kiểu boolean và phần tử tiếp theo là khung video thực tế. Khi phần tử đầu tiên là True có nghĩa là luồng video có chứa khung hình để đọc. Nếu có một khung hình để đọc, thì ta có thể sử dụng hàm imshow () để hiển thị khung hình hiện tại trong một cửa sổ, nếu không thì thoát khỏi vòng lặp. Lưu ý rằng cũng có thể sử dụng hàm waitKey () để tạm dừng trong 20ms giữa các khung hình video. Gọi hàm waitKey () cho phép theo dõi nhập liệu bàn phím từ người dùng. Trong trường hợp ví dụ thì nếu nhấn phím ‘q’ thì sẽ thoát khỏi vòng lặp.

Python

while(vid_capture.isOpened()):
	# vCapture.read() methods returns a tuple, first element is a bool 
	# and the second is frame

	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('Frame',frame)
		k = cv2.waitKey(20)
		# 113 is ASCII code for q key
		if k == 113:
			break
	else:
		break

C++

while (vid_capture.isOpened())
{
        // Initialize frame matrix
        Mat frame;
        // Initialize a boolean to check if frames are there or not
        bool isSuccess = vid_capture.read(frame);
        // If frames are present, show it
        if(isSuccess == true)
        {
            //display frames
            imshow("Frame", frame);
        }

        // If frames are not there, close it
        if (isSuccess == false)
        {
            cout << "Video camera is disconnected" << endl;
            break;
        }        
//wait 20 ms between successive frames and break the loop if key q is pressed
        int key = waitKey(20);
            if (key == 'q')
        {
            cout << "q key is pressed by the user. Stopping the video" << endl;
            break;
        }
    }

Khi luồng video được xử lý hoàn toàn hoặc sử dụng bàn phím thoát khỏi vòng lặp, đối tượng quay video sẽ được giải phóng (vid_capture) và đóng cửa sổ.

Python

# Release the objects
vid_capture.release()
cv2.destroyAllWindows()

C++

// Release video capture object
vid_capture.release();
destroyAllWindows();

2. Đọc một chuỗi hình ảnh

Xử lý khung hình ảnh từ một chuỗi hình ảnh tương tự với việc xử lý khung hình từ một luồng video. Chỉ cần chỉ định các tệp hình ảnh đang được đọc.

Trong ví dụ ở dưới,

– Tiếp tục sử dụng đối tượng quay video

– Thay vì chỉ định một tệp video, thì chỉ cần chỉ định một chuỗi hình ảnh

+ Sử dụng ký hiệu được hiển thị bên dưới (Cars%04d.jpg), trong đó %04d là quy ước đặt tên theo dãy gồm bốn chữ số (ví dụ: Cars0001.jpg, Cars0002.jpg, Cars0003.jpg, v.v.).

+ Nếu đã chỉ định “Race_Cars_%02d.jpg” thì sẽ tìm kiếm các tệp có dạng: (Race_Cars_01.jpg, Race_Cars_02.jpg, Race_Cars_03.jpg, …).

Python

vid_capture = cv2.VideoCapture('Resources/Image_sequence/Cars%04d.jpg')

C++

VideoCapture vid_capture("Resources/Image_sequence/Cars%04d.jpg");

3. Đọc video từ webcam

Đọc video từ webcam cũng gần tương tự như đọc từ tệp. Thay vì chỉ định đường dẫn cho tệp video hoặc chuỗi hình ảnh, người dùng chỉ cần khai báo chỉ số thiết bị webcam như sau:

– Nếu thiết bị có một webcam tích hợp, thì chỉ số thiết bị sẽ là ‘0’.

– Nếu có nhiều webcam được kết nối với thiết bị, thì chỉ số thiết bị được liên kết với mỗi webcam bổ sung sẽ tăng lên (ví dụ: 1, 2, v.v.).

Python

vid_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)

CAP_DSHOW là tùy chọn API quay video khác.

C++

VideoCapture vid_capture(0);

4. Ghi video

Cũng giống như đọc video, chúng ta có thể ghi video bắt nguồn từ bất kỳ nguồn nào (tệp video, chuỗi hình ảnh hoặc webcam). Để ghi một tệp video:

– Lấy chiều cao và chiều rộng của khung hình ảnh, sử dụng phương thức get ().

– Khởi tạo đối tượng quay video (đã giới thiệu ở trên), để đọc luồng video vào bộ nhớ (có thể là tệp, chuỗi ảnh hoặc webcam).

– Tạo một đối tượng ghi video.

– Sử dụng đối tượng ghi video để thực hiện ghi video vào địa chỉ cụ thể.

Đầu tiên, sử dụng phương thức get () để lấy chiều rộng và chiều cao khung hình video.

Python

# Obtain frame size information using get() method
frame_width = int(vid_capture.get(3))
frame_height = int(vid_capture.get(4))
frame_size = (frame_width,frame_height)
fps = 20

C++

// Obtain frame size information using get() method
Int frame_width = static_cast<int>(vid_capture.get(3));
int frame_height = static_cast<int>(vid_capture.get(4));
Size frame_size(frame_width, frame_height);
int fps = 20;

Để ghi tệp video, trước tiên cần tạo một đối tượng ghi video từ lớp VideoWriter () với cú pháp như sau:

VideoWriter(filename, apiPreference, fourcc, fps, frameSize[, isColor])

Lớp VideoWriter () nhận các đối số sau:

– filename: tên đường dẫn cho tệp video đầu ra

– apiPreference: Mã nhận dạng phụ trợ API

– Fourcc: Mã 4 ký tự của codec, được sử dụng để nén khung hình

– fps: Tốc độ khung hình của luồng video đã tạo

– frame_size: Kích thước của khung video

isColor: Nếu khác 0 sẽ mã hóa các khung màu. Nếu không, nó sẽ hoạt động với các khung ảnh xám (hiện chỉ được hỗ trợ trên Windows).

Codec video chỉ định cách nén luồng video. Nó chuyển đổi video không nén sang định dạng nén hoặc ngược lại. Để tạo định dạng AVI hoặc MP4, hãy sử dụng các thông số kỹ thuật fourcc sau:

  • AVI: cv2.VideoWriter_fourcc (‘M’, ‘J’, ‘P’, ‘G’)
  • MP4: cv2.VideoWriter_fourcc (* ‘XVID’) Hai đối số đầu vào tiếp theo chỉ định tốc độ khung hình trong FPS và kích thước khung hình (chiều rộng, chiều cao).

Python

# Initialize video writer object
output = cv2.VideoWriter('Resources/output_video_from_file.avi', cv2.VideoWriter_fourcc('M','J','P','G'), 20, frame_size)

C++

//Initialize video writer object
VideoWriter output("Resources/output.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'),frames_per_second, frame_size);

Lúc này một đối tượng ghi video đã được khởi tạo, tiếp theo sử dụng nó để ghi tệp video vào đĩa, từng khung hình một. Trong ví dụ là đang ghi tệp video AVI vào đĩa, với tốc độ 20 khung hình / giây.

Python

while(vid_capture.isOpened()):
    # vid_capture.read() methods returns a tuple, first element is a bool 
    # and the second is frame

    ret, frame = vid_capture.read()
    if ret == True:
           # Write the frame to the output files
           output.write(frame)
    else:
         print(‘Stream disconnected’)
           break

C++

while (vid_capture.isOpened())
{
        // Initialize frame matrix
        Mat frame;

          // Initialize a boolean to check if frames are there or not
        bool isSuccess = vid_capture.read(frame);

        // If frames are not there, close it
        if (isSuccess == false)
        {
            cout << "Stream disconnected" << endl;
            break;
        }


            // If frames are present
        if(isSuccess == true)
        {
            //display frames
            output.write(frame);
                  // display frames
                  imshow("Frame", frame);

                  // wait for 20 ms between successive frames and break        
                  // the loop if key q is pressed
                  int key = waitKey(20);
                  if (key == ‘q’)
                  {
                      cout << "Key q key is pressed by the user. 
                      Stopping the video" << endl;
                      break;
                  }
        }
 }

Cuối cùng giải phóng các đối tượng quay video và ghi video.

Python

# Release the objects
vid_capture.release()
output.release()

C++

// Release the objects
vid_capture.release();
output.release();

5. Những lỗi có thể gặp khi đọc và ghi video

Đọc video

Trong khi đọc các khung hình, có thể sẽ bị lỗi nếu đường dẫn sai hoặc tệp bị hỏng hoặc thiếu khung. Đó là lý do tại sao sử dụng câu lệnh if bên trong vòng lặp while. Bằng cách này, code sẽ chỉ xử lý khi có khung hình. Sau đây là một ví dụ về lỗi trong trường hợp này.

cap_gstreamer.cpp:890: error: (-2) GStreamer: unable to start pipeline  in function

Lỗi đường dẫn:

Khi cung cấp sai đường dẫn đến video, thì video đó sẽ không hiển thị bất kỳ lỗi hoặc cảnh báo nào khi sử dụng lớp VideoCapture (). Các vấn đề sẽ phát sinh khi cố gắng thực hiện bất kỳ thao tác nào trên các khung hình video. Đối với điều này, có thể sử dụng một khối if đơn giản để kiểm tra xem đã đọc tệp video hay chưa như trong ví dụ trên. Nếu lỗi sẽ có thông báo sau.

Error opening the video file

Lỗi khi ghi video

Trong bước này có thể xảy ra nhiều lỗi khác nhau. Phổ biến nhất là lỗi kích thước khung hình và lỗi tùy chọn api. Nếu kích thước khung hình không giống với video, thì mặc dù chúng ta nhận được tệp video tại thư mục đầu ra, nhưng nó sẽ bị trống. Nếu đang sử dụng phương pháp hình dạng NumPy để truy xuất kích thước khung hình, hãy nhớ đảo ngược đầu ra vì OpenCV sẽ trả về chiều cao x chiều rộng x kênh. Nếu nó đang gặp lỗi tùy chọn api có thể cần phải chuyển cờ CAP_ANY trong đối số VideoCapture (). Trong ví dụ đang sử dụng CAP_DHOW để tránh các cảnh báo được tạo ra.

Những thông báo lỗi có thể xuất hiện

Khi CAP_DSHOW không hợp lệ

[WARN:0]…cap_msmf.cpp(438) …. terminating async callback

Khi kích thước khung hình bị sai

cv2.error: OpenCV(4.5.2) :-1: error: (-5:Bad argument) in function ‘VideoWriter’

> Overload resolution failed:

>  – Can’t parse ‘frameSize’. Sequence item with index 0 has a wrong type

>  – VideoWriter() missing required argument ‘frameSize’ (pos 5)

>  – VideoWriter() missing required argument ‘params’ (pos 5)

>  – VideoWriter() missing required argument ‘frameSize’ (pos 5)

Như vậy bài viết này đã giới thiệu cách đọc và hiển thị video từ ba nguồn khác nhau, bằng cách sử dụng đối tượng quay video. Ngoài ra còn giới thiệu cách sử dụng đối tượng quay video để truy xuất những dữ liệu quan trọng của video. Bài viết cũng đã trình bày cách ghi các video vào địa chỉ cụ thể, bằng cách sử dụng một đối tượng ghi video.

Ở bài viết tiếp theo, chúng ta sẽ cùng tìm hiểu một số thao tác thay đổi kích thước ảnh với OpenCV.

Biên dịch: Thảo Nguyễn

Để cập nhật tin tức công nghệ mới nhất và các sản phẩm của công ty AIoT JSC, vui lòng truy cập link: http://aiots.vn hoặc linhkienaiot.com

0 0 Phiếu bầu
Article Rating
Subscribe
Notify of
guest
0 Comments
Phản hồi nội tuyến
Xem tất cả các bình luận