5MP Motion Camera JPEG Image and Movie Catcher  1.1.1.1
A CGI interface to capture and display pictures and movies using OpenCV.
How to Build a CGI That Does Image Processing for a 5 Mega-pixel Motion Activated WiFi Camera

This project demonstrates how to receive images from this camera and process them into still images and movies using OpenCV and FFMPEG on Raspbian Buster (recently rebranded as Raspberry Pi OS). The camera sends the following JSON message via HTTP POST:

{
"body": [
{
"name": "battery_percent",
"value": "96"
},
{
"name": "5MP_MC",
"frames": 0,
"content_type": "image/jpeg",
"value": ""
}
]
}

The battery data lands at the top of the image browsing page.

image_browsing.jpg
image browsing page

For more information about this page see the Camera Settings Project. The rest of the data are used to process the value payload. (It has been elided for brevity. Imagine a very long base64 encoded string in its place.) The name field tells us which camera sent the data. If you have multiple cameras simply give them distinct names and all will be well. The frames counter lets us know if we should expect a single JPEG or multiples. Frames are numbered from zero so 0 frames yields one picture. If we get multiples we split them apart and order them by frame number. If we get the reserved value of 255 that tells us we've received an AVI/MJPEG movie. If it passes magic number and FFMPEG validation we convert it to MP4 and save it as a movie. If it does not, we attempt to save as many frames as we can and fallback to saving frame numbered images. See mjpeg_utils::save_avi_frames for how FFMPEG is used within OpenCV to accomplish all this image processing.

Building

If you haven't already:

sudo apt-get install build-essential libcurl-dev libmagic-dev

It's widely known that the hardest part of using OpenCV is getting it and its dependencies installed. Once you get past that it's a pleasure to use. I built this project on a Raspberry Pi model 4B running the latest (June 2020) Raspbian (now Raspberry Pi OS). I did that because I wanted the project to be accessible to those on a limited budget. The Pi is famously affordable and it has more than enough capability for the tasks at hand. That said, if you have access to a laptop running Ubuntu you might find that a better option. To begin the building, install Clang and LLVM. I copied these directions from https://apt.llvm.org/ so be sure to check there first to see if the directions have changed. BTW: You may be tempted to build with GCC; if you get it to work you're a better build master than me. After that, install OpenCV and it's dependencies. Lastly run cmake in the usual way in the project root folder.

Installing the CGI

On Raspbian, as root, do the following;

  1. Copy the CGI binary you built (jpeg_catcher) with cmake into /usr/lib/cgi-bin/ If you renamed the project in CMakeLists.txt the binary will have a new name as well. Be sure to reflect this name in HTTP_HOST_URL over in the camera project's platformio.ini.
  2. Enable the CGI module.
    sudo a2enmod cgi

Configuring the Web Root

-1. As root, open /etc/apache2/sites-available/000-default.conf and paste the following in just above the closing </VirtualHost> tag.

<Directory /var/www/html/motion_camera>
Options +Indexes
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
<IfModule mod_autoindex.c>
IndexOptions IgnoreCase FancyIndexing HTMLTable SuppressHTMLPreamble FoldersFirst VersionSort NameWidth=* DescriptionWidth=* XHTML IconHeight=16 IconWidth=16
IndexIgnore ..
IndexOrderDefault Descending Name
IndexStyleSheet ./fancy-index/style.css
HeaderName ./fancy-index/header.html
ReadmeName ./fancy-index/footer.html
# IGNORE THESE FILES
IndexIgnoreReset ON
IndexIgnore fancy-index
# DEFAULT ICON
DefaultIcon ./fancy-index/icons/file-text.svg
AddIcon ./fancy-index/icons/back.svg ..
AddIcon ./fancy-index/icons/file-directory.svg ^^DIRECTORY^^
# https://github.com/file-icons/source
AddIcon ./fancy-index/icons/file-media.svg .jpg .jpeg
AddIcon ./fancy-index/icons/Video.svg .avi .mp4
# https://upload.wikimedia.org/wikipedia/commons/d/da/Battery-303889.svg
AddIcon ./fancy-index/icons/battery.svg .pwr
AddDescription "MPEG Layer 4 Format" .mp4
AddDescription "Joint Photographics Experts Group" .jpg .jpeg .jpe .jfif
AddDescription "Audio Video Interleave - Motion JPEG" .avi
AddDescription "Camera battery power available" .pwr
</IfModule>
</Directory>

-2. As root, make the directory /var/www/html/motion_camera and copy the contents of this project's [web_root](web_root) folder into it.

-3. Fix the ownership, group, and permissions.

sudo mkdir /var/www/html/motion_camera
cd /var/www/html/motion_camera
sudo chown -R www-data:www-data
sudo find . -type f -exec chmod 0644 {} \;
sudo find . -type d -exec chmod 0755 {} \;

-4. When you're done /var/www/html/motion_camera will contain:

ls -al /var/www/html/motion_camera/fancy-index/
total 72
drwxr-xr-x 3 www-data www-data 4096 Jun 9 09:37 .
drwxr-xr-x 3 www-data www-data 40960 Jun 15 09:06 ..
-rw-r--r-- 1 www-data www-data 66 Jun 5 15:53 footer.html
-rw-r--r-- 1 www-data www-data 295 Jun 5 16:05 header.html
drwxr-xr-x 2 www-data www-data 4096 Jun 8 15:35 icons
-rw-r--r-- 1 www-data www-data 6322 Jun 8 15:48 script.js
-rw-r--r-- 1 www-data www-data 3170 Jun 9 09:37 style.css

-5. You can now test the configuration by pointing your browser to http://your_server:4444/motion_camera

Debugging

One of the handy things about the design of the CGI interface is that all error messages should be written to the standard error stream; stderr. The web server writes these into it's error log; /var/log/apache2/error.log. If this CGI encounters and error it will write a useful message about it to this file. To turn off this output edit debug_output.hpp and comment out DEBUG_OUTPUT

Do I Have to Use Apache?

Certainly not. The CGI interface standard is supported by many different HTTP servers. You should have little difficulty adapting this project to work with your chosen web server.

Resources

There are also tools you may find useful to you in other projects. For example;

  • Http_parser::parse_query_string stores the HTML query string in a std::map<std::string, std::vector<std::string>> From this you can create a project that handles HTML GET requests.
  • Http_parser::parse_multipart_form stores multipart/form-data encoded HTTP message bodies in two std::map<std::string, std::vector<std::string>> - one for variables and another for files. From this you can create a project where forms are used to control a robot/embedded system or your Raspberry Pi. To help you get started there is an everything-but-the-kitchen-sink HTML form. Edit the <form> tag at the top and change the action attribute to reflect your server name.

Credits

  • Thank you to Vestride for the Fancy Index project from which I built the web page in this project.
  • Thank you to the OpenCV project. I used one of the samples to learn how to do the image processing in this project.
  • Thank you to hjiang for the jsonxx project. I tested several C++ JSON libraries including jsoncpp before deciding to use jsonxx for its simplicity and speed.
  • Thank you to Ian Darwin and Christos Zoulas the inventor and maintainer (respectively) of all things magic number. I've been using the file(1) command all my life and was delighted to find the development library for it so easy to use.

Bugs, Issues, and Pull Requests

If you find a bug please create an issue. If you'd like to contribute please send a pull request.

References

The following were helpful references in the development of this project.