Skip to content

Commit 2ccce5b

Browse files
authored
Merge pull request #17 from LukasBommes/dev
Dev
2 parents 6084f86 + b64d529 commit 2ccce5b

15 files changed

+503
-284
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.eggs
66
__pycache__/
77
/venv3.*/
8+
out-*/
89

910
*.tar
1011
a.out

Dockerfile

+33-15
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
1-
FROM ubuntu:18.04 AS builder
1+
FROM ubuntu:22.04 AS builder
22

33
WORKDIR /home/video_cap
44

5-
COPY install.sh /home/video_cap
6-
COPY ffmpeg_patch /home/video_cap/ffmpeg_patch/
5+
# Install build tools
6+
RUN apt-get update -qq --fix-missing && \
7+
apt-get upgrade -y && \
8+
apt-get install -y \
9+
wget \
10+
unzip \
11+
build-essential \
12+
cmake \
13+
git \
14+
pkg-config \
15+
autoconf \
16+
automake \
17+
git-core \
18+
python3-dev \
19+
python3-pip \
20+
python3-numpy \
21+
python3-pkgconfig && \
22+
rm -rf /var/lib/apt/lists/*
723

8-
# Install dependencies
24+
# Install OpenCV
25+
COPY install_opencv.sh /home/video_cap
926
RUN mkdir -p /home/video_cap && \
1027
cd /home/video_cap && \
11-
chmod +x install.sh && \
12-
./install.sh
28+
chmod +x install_opencv.sh && \
29+
./install_opencv.sh
1330

14-
# Install debugging tools
15-
RUN apt-get update && \
16-
apt-get -y install \
17-
gdb \
18-
python3-dbg
31+
# Install FFMPEG
32+
COPY install_ffmpeg.sh /home/video_cap
33+
COPY ffmpeg_patch /home/video_cap/ffmpeg_patch/
34+
RUN mkdir -p /home/video_cap && \
35+
cd /home/video_cap && \
36+
chmod +x install_ffmpeg.sh && \
37+
./install_ffmpeg.sh
1938

20-
FROM ubuntu:18.04
39+
FROM ubuntu:22.04
2140

2241
# install Python
2342
RUN apt-get update && \
@@ -44,7 +63,7 @@ RUN apt-get update && \
4463
libvdpau-dev \
4564
libvorbis-dev \
4665
libopus-dev \
47-
libdc1394-22-dev \
66+
libdc1394-dev \
4867
liblzma-dev && \
4968
rm -rf /var/lib/apt/lists/*
5069

@@ -72,7 +91,6 @@ COPY src /home/video_cap/src/
7291

7392
# Install Python package
7493
COPY vid.mp4 /home/video_cap
75-
RUN cd /home/video_cap && \
76-
python3 setup.py install
94+
RUN python3 setup.py install
7795

7896
CMD ["sh", "-c", "tail -f /dev/null"]

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License
22

3-
Copyright (c) 2010-2019 Google, Inc. http://angularjs.org
3+
Copyright (c) 2010-2022 Lukas Bommes
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+52-31
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,26 @@ The image below shows a video frame with extracted motion vectors overlaid,
1414

1515
![motion_vector_demo_image](mvs.png)
1616

17-
A usage example can be found in `test.py`.
17+
A usage example can be found in `extract_mvs.py`.
1818

1919

20-
## Installation
20+
## News
21+
22+
### Recent Changes
23+
24+
- Added unittests in `tests/tests.py`
25+
- Updated to Ubuntu 22.04, Python 3.10, and OpenCV 4.5.5
26+
- Provided a script to wrap Docker run command
27+
- Updated demo script with command line arguments for extraction and storing of motion vectors
28+
29+
### Looking for Contributors
30+
31+
The mv-extractor seems to be quite popular and I want to improve it. However, I do not have the time and resources to do this alone. Hence, I gladly welcome any community contributions.
32+
33+
One improvement that is on my mind, is the packaging and distribution of mv-extractor via PyPI. This requires creating a source distribution and building binary wheels for different target architectures and Python versions. Ideally, the build would be automated with a CI pipeline, e.g., GitHub Actions. It would be awesome, if someone is interested in helping me and getting this to work. In this case, please just open an issue and we can discuss the details.
34+
35+
36+
## Quickstart
2137

2238
### Step 1: Install Prerequisites
2339

@@ -30,58 +46,63 @@ Change into the desired installation directory on your machine and clone the sou
3046
git clone https://github.com/LukasBommes/mv-extractor.git mv_extractor
3147
```
3248

33-
### Step 3: Pull and Run Docker Image
49+
### Step 3: Extract Motion Vectors
3450

35-
Change into the `mv_extractor` directory and run the prebuilt Docker image
51+
Change into the `mv_extractor` directory and run the extraction script
3652
```
37-
sudo docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw lubo1994/mv-extractor:latest /bin/bash
53+
sudo ./run.sh python3 extract_mvs.py
3854
```
55+
This pulls a prebuild Docker image and runs the mv-extractor within this image.
3956

40-
<details>
41-
<summary>Alternative: Build Docker image locally</summary>
42-
43-
This step is not required and for faster installation, we recommend using the prebuilt image.
44-
If you still want to build the Docker image locally, you can do so by running the following command in the `mv_extractor` directory
57+
The extraction script provides several command line options, e.g., to store extracted motion vectors to disk, and to enable/disable graphical output. To see all command line options type
4558
```
46-
sudo docker build . --tag=mv_extractor
59+
sudo ./run.sh python3 extract_mvs.py -h
60+
```
61+
For example, if you want to store extracted frames and motion vectors to disk, you can do so by running
4762
```
48-
Note that building can take more than one hour.
49-
50-
Now, run the docker container with
63+
sudo ./run.sh python3 extract_mvs.py --dump
5164
```
52-
sudo docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw mv_extractor /bin/bash
53-
```
54-
55-
Building the image leaves some intermediate images behind which can be deleted via
65+
You can also open another video stream by specifying its file path or url, e.g.,
5666
```
57-
sudo docker rmi -f $(sudo docker images -f "dangling=true" -q)
67+
sudo ./run.sh python3 extract_mvs.py "another_video.mp4"
5868
```
59-
</details>
69+
Note, that the last command will fail because there is no file called `another_video.mp4`.
6070

6171

62-
### Step 4: Test Installation
72+
## Advanced Usage
6373

64-
Test if everything is installed succesfully by running the demo script
65-
```
66-
python3 test.py
67-
```
68-
If you encounter the error message "cannot open display: :1" or similar, you have to disable the X server access control by running
74+
### Run Tests
75+
76+
To test if everything is installed succesfully you can run the tests with
6977
```
70-
xhost +
78+
sudo ./run.sh python3 tests/tests.py
7179
```
72-
in a new terminal on the host machine (not inside the Docker container).
73-
80+
Confirm that all tests pass.
7481

75-
## Usage
82+
### Importing mv-extractor into Your Own Scripts
7683

7784
If you want to use the motion vector extractor in your own Python script import it via
7885
```
7986
from mv_extractor import VideoCap
8087
```
81-
You can then use it according to the example in `test.py`.
88+
You can then use it according to the example in `extract_mvs.py`.
8289

8390
Generally, a video file is opened by `VideoCap.open()` and frames, motion vectors, frame types and timestamps are read by calling `VideoCap.read()` repeatedly. Before exiting the program, the video file has to be closed by `VideoCap.release()`. For a more detailed explanation see the API documentation below.
8491

92+
### Building the Docker Image Locally
93+
94+
This step is not required and for faster installation, we recommend using the prebuilt image.
95+
If you still want to build the Docker image locally, you can do so by running the following command in the `mv_extractor` directory
96+
```
97+
sudo docker build . --tag=mv_extractor
98+
```
99+
Note that building can take more than one hour.
100+
101+
Now, run the docker container with
102+
```
103+
sudo docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw mv_extractor /bin/bash
104+
```
105+
85106

86107
## Python API
87108

extract_mvs.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import os
2+
import time
3+
from datetime import datetime
4+
import argparse
5+
6+
import numpy as np
7+
import cv2
8+
9+
from mv_extractor import VideoCap
10+
11+
def draw_motion_vectors(frame, motion_vectors):
12+
if len(motion_vectors) > 0:
13+
num_mvs = np.shape(motion_vectors)[0]
14+
for mv in np.split(motion_vectors, num_mvs):
15+
start_pt = (mv[0, 3], mv[0, 4])
16+
end_pt = (mv[0, 5], mv[0, 6])
17+
cv2.arrowedLine(frame, start_pt, end_pt, (0, 0, 255), 1, cv2.LINE_AA, 0, 0.1)
18+
return frame
19+
20+
21+
if __name__ == "__main__":
22+
23+
parser = argparse.ArgumentParser(description='Extract motion vectors from video.')
24+
parser.add_argument('video_url', type=str, nargs='?', default="vid.mp4", help='File path or url of the video stream')
25+
parser.add_argument('--preview', action=argparse.BooleanOptionalAction, default=True, help='Show a preview video with overlaid motion vectors')
26+
parser.add_argument('--verbose', action=argparse.BooleanOptionalAction, default=True, help='Show detailled text output')
27+
parser.add_argument('--dump', action=argparse.BooleanOptionalAction, default=False, help='Dump frames, motion vectors, frame types, and timestamps to output directory')
28+
args = parser.parse_args()
29+
30+
if args.dump:
31+
now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
32+
for child in ["frames", "motion_vectors"]:
33+
os.makedirs(os.path.join(f"out-{now}", child), exist_ok=True)
34+
35+
cap = VideoCap()
36+
37+
# open the video file
38+
ret = cap.open(args.video_url)
39+
40+
if not ret:
41+
raise RuntimeError(f"Could not open {args.video_url}")
42+
43+
if args.verbose:
44+
print("Sucessfully opened video file")
45+
46+
step = 0
47+
times = []
48+
49+
# continuously read and display video frames and motion vectors
50+
while True:
51+
if args.verbose:
52+
print("Frame: ", step, end=" ")
53+
54+
tstart = time.perf_counter()
55+
56+
# read next video frame and corresponding motion vectors
57+
ret, frame, motion_vectors, frame_type, timestamp = cap.read()
58+
59+
tend = time.perf_counter()
60+
telapsed = tend - tstart
61+
times.append(telapsed)
62+
63+
# if there is an error reading the frame
64+
if not ret:
65+
if args.verbose:
66+
print("No frame read. Stopping.")
67+
break
68+
69+
# print results
70+
if args.verbose:
71+
print("timestamp: {} | ".format(timestamp), end=" ")
72+
print("frame type: {} | ".format(frame_type), end=" ")
73+
74+
print("frame size: {} | ".format(np.shape(frame)), end=" ")
75+
print("motion vectors: {} | ".format(np.shape(motion_vectors)), end=" ")
76+
print("elapsed time: {} s".format(telapsed))
77+
78+
frame = draw_motion_vectors(frame, motion_vectors)
79+
80+
# store motion vectors, frames, etc. in output directory
81+
if args.dump:
82+
cv2.imwrite(os.path.join(f"out-{now}", "frames", f"frame-{step}.jpg"), frame)
83+
np.save(os.path.join(f"out-{now}", "motion_vectors", f"mvs-{step}.npy"), motion_vectors)
84+
with open(os.path.join(f"out-{now}", "timestamps.txt"), "a") as f:
85+
f.write(str(timestamp)+"\n")
86+
with open(os.path.join(f"out-{now}", "frame_types.txt"), "a") as f:
87+
f.write(frame_type+"\n")
88+
89+
90+
step += 1
91+
92+
if args.preview:
93+
cv2.imshow("Frame", frame)
94+
95+
# if user presses "q" key stop program
96+
if cv2.waitKey(1) & 0xFF == ord('q'):
97+
break
98+
99+
if args.verbose:
100+
print("average dt: ", np.mean(times))
101+
102+
cap.release()
103+
104+
# close the GUI window
105+
if args.preview:
106+
cv2.destroyAllWindows()

0 commit comments

Comments
 (0)