Frame Subtraction để phát hiện chuyển động trong video

  Jun 14, 2019      2m
   

OpenCV - tut 16: Frame Subtraction

Frame Subtraction để phát hiện chuyển động trong video

Môi trường "hành sự"

  • Linux (bài viết sử dụng Ubuntu 16.04)
  • OpenCV (bài viết sử dụng OpenCV 4.0.0)
  • Python (bài viết sử dụng Python 3.5.5)
  • FFMPEG (bài viết sử dụng FFMPEG 4.0)
  • Video mẫu để xử lý: bài viết không cung cấp sẵn!

Frame Subtraction

Ý tưởng của Frame Subtraction (tạm dịch: trừ khung hình) rất đơn giản, đó là ta trừ hai khung hình với nhau sau đó chuẩn hóa về miền giá trị mong muốn. Ví dụ: Nếu muốn trực quan hóa sự khác biệt giữa hai ảnh thì ta có thể lấy giá trị tuyệt đối. Hoặc cũng có thể áp dụng các kiểu chuẩn hóa khác tùy mục đích bạn muốn xử lý là gì.

Khi áp dụng Frame Subtraction cho video thì ta sẽ lấy 2 khung hình liền kề (pair) trừ cho nhau để tìm điểm khác biệt. Nói về bản chất của video thì có thể xem nó là một chồng ảnh, bởi video chính là việc trình chiếu các khung ảnh theo thời gian. Do đó, việc áp dụng xử lý ảnh trên video sẽ là việc ta áp dụng xử lý ảnh trên từng khung hình (frame) bằng cách lần lượt lặp cho đến hết tất cả khung hình có trong video.

Các thông tin trên cho ta biết cách thao tác với video ở mức luận lý. Thực chất một file video thực thụ (ví dụ: đuôi .mp4) nó sẽ cần phải nén lại nữa. Bởi nếu ta lưu thô hoàn toàn thì dung lượng của video sẽ rất lớn. Giải thuật nén ảnh / nén video là cả một chủ đề trong Xử lý ảnh đó nha chứ không có đơn giản đâu.

Trong bài viết này, sau khi trừ hai ảnh Minh sẽ "visualize" lên thành hình ảnh do đó mình sẽ chuẩn hóa bằng cách lấy giá trị tuyệt đối cho đơn giản. Khi sử dụng OpenCV chỉ cần đúng một hàm sau: cv2.absdiff (https://docs.opencv.org/4.0.0/d2/de8/group__core__array.html#ga6fef31bc8c4071cbc114a758a2b79c14)

Hiện thực giải thuật Background Subtraction trên ảnh màu

Tiết mục chém gió đến rồi :">. Có thể bỏ qua các dòng này để đến ngay với source code bên dưới cũng được (hehe). Đại loại lần này mình sẽ cung cấp các điểm mới sau, đó là:

  • Đọc video trong OpenCV: điểm mấu chốt chính là cv2.VideoCapture và lặp trên cap.read(). Vòng lặp các frame ảnh kết thúc khi hết video hoặc bị lỗi. Khá là dễ hiểu, mà nếu có không hiểu thì thôi, trong mỗi vòng lặp ta sẽ có khung hình để thực hiện xử lý.
  • Để lưu frame liền trước thì ta chỉ việc cho nó vào biến last_gray để lưu trữ. Đương nhiên là ở frame đầu tiên ta làm quái gì có frame liền trước để mà xử lý :)). Có thể bỏ qua frame đầu nè cho nó đơn giản.
  • Hàm tính sự khác biệt trong OpenCV: trừ điểm với điểm giữa 2 ảnh, sau đó lấy trị tuyệt đối. Tài liệu tham khảo cv2.absdiff có đính kèm cả link ở phần trước, tha hồ đọc ngập mặt :v.
  • Cuối cùng là hàm print_image để in áo cặp, ý lộn, cặp ảnh cạnh nhau. Để khi ta ghép đống hình này lại sẽ ra video kết quả sinh đờ ộng (sinh động) :))). Cách làm tạo ảnh đen thui kích thước bề ngang gấp đôi ảnh gốc, sau đó lấy ảnh gốc dán bên trái, lấy ảnh khác biệt dán bên phải. Do ảnh diff chỉ có một kênh, nên mình phải dán 3 lần trên 3 kênh màu để nó hiển thị thành màu xám!
  • Lưu ý BỰ: sửa biến INPUT_VIDEO thành đường dẫn (tương đối hay tuyệt đối) đến video ếch, í lộn, video mấy ông muốn xử lý nhá. Còn OUTPUT_IMG là thư mục sẽ tự động được tạo mới nếu nó chưa tồn tại và ảnh kết quả sẽ được in vèo vèo vô đây.
  • Bước ghép video: Tham khảo bài viết này và dùng lệnh này nha, hiểu hôn:
ffmpeg -framerate 24 -f image2 -start_number 1 -i out_my_video/img_%*.jpg -crf 10 -q:v 5  -pix_fmt yuv420p out_video.mp4

frame_sub.py

import os
import cv2
import numpy as np

INPUT_VIDEO = 'my_video.mp4'
OUTPUT_IMG = 'out_my_video'
os.makedirs(OUTPUT_IMG, exist_ok=True)

def print_image(img, frame_diff):
    """
    Place images side-by-side
    """
    new_img = np.zeros([img.shape[0], img.shape[1]*2, img.shape[2]]) # [height, width*2, channel]
    new_img[:, :img.shape[1], :] = img         # place color image on the left side
    new_img[:, img.shape[1]:, 0] = frame_diff  # place gray image on the right side
    new_img[:, img.shape[1]:, 1] = frame_diff
    new_img[:, img.shape[1]:, 2] = frame_diff
    return new_img

def main(video_path):
    cap = cv2.VideoCapture(video_path) # https://docs.opencv.org/4.0.0/d8/dfe/classcv_1_1VideoCapture.html
    last_gray = None
    idx = -1
    while(True):
        ret, frame = cap.read() # read frames
        idx += 1
        if not ret:
            print('Stopped reading the video (%s)' % video_path)
            break
        
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # convert color image to gray
        
        if last_gray is None:
            last_gray = gray
            continue        
        
        diff = cv2.absdiff(gray, last_gray) # frame difference! https://docs.opencv.org/4.0.0/d2/de8/group__core__array.html#ga6fef31bc8c4071cbc114a758a2b79c14
        cv2.imwrite(os.path.join(OUTPUT_IMG, 'img_%06d.jpg' % idx), print_image(frame, diff))
        last_gray = gray
        print('Done image @ %d...' % idx)
        pass
    pass

if __name__ == "__main__":
    print('Running frame difference algorithm on %s' % INPUT_VIDEO)
    main(video_path=INPUT_VIDEO)
    print('* Follow me @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/minhng.info/' + "\x1b[0m")
    print('* Join GVGroup for discussion @ ' + "\x1b[1;%dm" % (34) + 'https://www.facebook.com/groups/ip.gvgroup/' + "\x1b[0m")
    print('* Thank you ^^~')
    
    print('[NOTE] Run the following command to turn you images in to video:')
    print('ffmpeg -framerate 24 -f image2 -start_number 1 -i out_my_video/img_%*.jpg -crf 10 -q:v 5  -pix_fmt yuv420p out_video.mp4')

Video kết quả:


Cảm ơn bạn đã theo dõi bài viết. Hãy kết nối với tớ nhé!


Danh sách bài viết series OpenCV: