chore: import upstream snapshot with attribution
@@ -0,0 +1,4 @@
|
||||
High Level GUI and Media (highgui module) {#tutorial_table_of_content_highgui}
|
||||
=========================================
|
||||
|
||||
Content has been moved to this page: @ref tutorial_table_of_content_app
|
||||
@@ -0,0 +1,4 @@
|
||||
Image Input and Output (imgcodecs module) {#tutorial_table_of_content_imgcodecs}
|
||||
=========================================
|
||||
|
||||
Content has been moved to this page: @ref tutorial_table_of_content_app
|
||||
@@ -0,0 +1,4 @@
|
||||
Video Input and Output (videoio module) {#tutorial_table_of_content_videoio}
|
||||
=========================================
|
||||
|
||||
Content has been moved to this page: @ref tutorial_table_of_content_app
|
||||
@@ -0,0 +1,94 @@
|
||||
Handling Animated Image Files {#tutorial_animations}
|
||||
===========================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Suleyman Turkmen (with help of ChatGPT) |
|
||||
| Compatibility | OpenCV >= 4.11 |
|
||||
|
||||
Goal
|
||||
----
|
||||
In this tutorial, you will learn how to:
|
||||
|
||||
- Use `cv::imreadanimation` to load frames from animated image files.
|
||||
- Understand the structure and parameters of the `cv::Animation` structure.
|
||||
- Display individual frames from an animation.
|
||||
- Use `cv::imwriteanimation` to write `cv::Animation` to a file.
|
||||
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/imgcodecs/animations.cpp)
|
||||
|
||||
- **Code at a glance:**
|
||||
@include samples/cpp/tutorial_code/imgcodecs/animations.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/imgcodecs/animations.py)
|
||||
|
||||
- **Code at a glance:**
|
||||
@include samples/python/tutorial_code/imgcodecs/animations.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
## Initializing the Animation Structure
|
||||
|
||||
Initialize a `cv::Animation` structure to hold the frames from the animated image file.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/imgcodecs/animations.cpp init_animation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/imgcodecs/animations.py init_animation
|
||||
@end_toggle
|
||||
|
||||
## Loading Frames
|
||||
|
||||
Use `cv::imreadanimation` to load frames from the specified file. Here, we load all frames from an animated WebP image.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/imgcodecs/animations.cpp read_animation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/imgcodecs/animations.py read_animation
|
||||
@end_toggle
|
||||
|
||||
## Displaying Frames
|
||||
|
||||
Each frame in the `animation.frames` vector can be displayed as a standalone image. This loop iterates through each frame, displaying it in a window with a short delay to simulate the animation.
|
||||
|
||||
> **Note:** Frame durations in `cv::Animation` are expressed in milliseconds.
|
||||
> When displaying frames manually using `cv::waitKey`, make sure to use the corresponding duration value to preserve the original animation timing.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/imgcodecs/animations.cpp show_animation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/imgcodecs/animations.py show_animation
|
||||
@end_toggle
|
||||
|
||||
## Saving Animation
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/imgcodecs/animations.cpp write_animation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/imgcodecs/animations.py write_animation
|
||||
@end_toggle
|
||||
|
||||
## Summary
|
||||
|
||||
The `cv::imreadanimation` and `cv::imwriteanimation` functions make it easy to work with animated image files by loading frames into a `cv::Animation` structure, allowing frame-by-frame processing.
|
||||
With these functions, you can load, process, and save frames from animated image files like GIF, AVIF, APNG, and WebP.
|
||||
@@ -0,0 +1,106 @@
|
||||
Using Wayland highgui-backend in Ubuntu {#tutorial_wayland_ubuntu}
|
||||
=======================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_intelperc}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Kumataro |
|
||||
| Compatibility | OpenCV >= 4.10 |
|
||||
| ^ | Ubuntu 24.04 |
|
||||
|
||||
Goal
|
||||
-----
|
||||
This tutorial is to use Wayland highgui-backend in Ubuntu 24.04.
|
||||
|
||||
Wayland highgui-backend is experimental implementation.
|
||||
|
||||
Setup
|
||||
-----
|
||||
- Setup Ubuntu 24.04.
|
||||
- `sudo apt install build-essential git cmake` to build OpenCV.
|
||||
- `sudo apt install libwayland-dev wayland-protocols libxkbcommon-dev` to enable Wayland highgui-backend.
|
||||
- (Option) `sudo apt install ninja-build` (or remove `-GNinja` option for cmake command).
|
||||
- (Option) `sudo apt install libwayland-egl1` to enable Wayland EGL library.
|
||||
|
||||
Get OpenCV from GitHub
|
||||
----------------------
|
||||
|
||||
```bash
|
||||
mkdir work
|
||||
cd work
|
||||
git clone --depth=1 https://github.com/opencv/opencv.git
|
||||
```
|
||||
|
||||
@note
|
||||
`--depth=1` option is to limit downloading commits. If you want to see more commit history, please remove this option.
|
||||
|
||||
Build/Install OpenCV with Wayland highgui-backend
|
||||
-------------------------------------------------
|
||||
|
||||
Run `cmake` with `-DWITH_WAYLAND=ON` option to configure OpenCV.
|
||||
|
||||
```bash
|
||||
cmake -S opencv -B build4-main -DWITH_WAYLAND=ON -GNinja
|
||||
```
|
||||
|
||||
If succeeded, Wayland Client/Cursor/Protocols and Xkbcommon versions are shown. Wayland EGL is option.
|
||||
|
||||
```plaintext
|
||||
--
|
||||
-- GUI: Wayland
|
||||
-- Wayland: (Experimental) YES
|
||||
-- Wayland Client: YES (ver 1.22.0)
|
||||
-- Wayland Cursor: YES (ver 1.22.0)
|
||||
-- Wayland Protocols: YES (ver 1.34)
|
||||
-- Xkbcommon: YES (ver 1.6.0)
|
||||
-- Wayland EGL(Option): YES (ver 18.1.0)
|
||||
-- GTK+: NO
|
||||
-- VTK support: NO
|
||||
```
|
||||
|
||||
Run `cmake --build` to build, and `sudo cmake --install` to install into your system.
|
||||
|
||||
```bash
|
||||
cmake --build build4-main
|
||||
sudo cmake --install build4-main
|
||||
sudo ldconfig
|
||||
```
|
||||
|
||||
Simple Application to try Wayland highgui-backend
|
||||
-------------------------------------------------
|
||||
Try this code, so you can see name of currentUIFrramework() and OpenCV logo window with Wayland highgui-backend.
|
||||
|
||||
|
||||
```bash
|
||||
// g++ main.cpp -o a.out -I /usr/local/include/opencv4 -lopencv_core -lopencv_highgui -lopencv_imgcodecs
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
std::cout << "cv::currentUIFramework() returns " << cv::currentUIFramework() << std::endl;
|
||||
|
||||
cv::Mat src;
|
||||
src = cv::imread("opencv-logo.png");
|
||||
|
||||
cv::namedWindow("src");
|
||||
|
||||
int key = 0;
|
||||
do
|
||||
{
|
||||
cv::imshow("src", src );
|
||||
key = cv::waitKey(50);
|
||||
} while( key != 'q' );
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Limitation/Known problem
|
||||
------------------------
|
||||
- cv::moveWindow() is not implemented. ( See. https://github.com/opencv/opencv/issues/25478 )
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,93 @@
|
||||
Using Creative Senz3D and other Intel RealSense SDK compatible depth sensors {#tutorial_intelperc}
|
||||
=======================================================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_orbbec_uvc}
|
||||
@next_tutorial{tutorial_wayland_ubuntu}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Alessandro de Oliveira Faria |
|
||||
| Compatibility | OpenCV >= 4.5.5 |
|
||||
|
||||

|
||||
|
||||
**Note**: This tutorial is partially obsolete since PerC SDK has been replaced with RealSense SDK
|
||||
|
||||
Depth sensors compatible with Intel® RealSense SDK are supported through VideoCapture
|
||||
class. Depth map, RGB image and some other formats of output can be retrieved by using familiar
|
||||
interface of VideoCapture.
|
||||
|
||||
In order to use depth sensor with OpenCV you should do the following preliminary steps:
|
||||
|
||||
-# Install Intel RealSense SDK 2.0 (from here <https://github.com/IntelRealSense/librealsense>).
|
||||
|
||||
-# Configure OpenCV with Intel RealSense SDK support by setting WITH_LIBREALSENSE flag in
|
||||
CMake. If Intel RealSense SDK is found in install folders OpenCV will be built with
|
||||
Intel Realsense SDK library (see a status LIBREALSENSE in CMake log).
|
||||
|
||||
-# Build OpenCV.
|
||||
|
||||
VideoCapture can retrieve the following data:
|
||||
|
||||
-# data given from depth generator:
|
||||
- CAP_INTELPERC_DEPTH_MAP - each pixel is a 16-bit integer. The value indicates the
|
||||
distance from an object to the camera's XY plane or the Cartesian depth. (CV_16UC1)
|
||||
- CAP_INTELPERC_UVDEPTH_MAP - each pixel contains two 32-bit floating point values in
|
||||
the range of 0-1, representing the mapping of depth coordinates to the color
|
||||
coordinates. (CV_32FC2)
|
||||
- CAP_INTELPERC_IR_MAP - each pixel is a 16-bit integer. The value indicates the
|
||||
intensity of the reflected laser beam. (CV_16UC1)
|
||||
|
||||
-# data given from RGB image generator:
|
||||
- CAP_INTELPERC_IMAGE - color image. (CV_8UC3)
|
||||
|
||||
In order to get depth map from depth sensor use VideoCapture::operator \>\>, e. g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture( CAP_REALSENSE );
|
||||
for(;;)
|
||||
{
|
||||
Mat depthMap;
|
||||
capture >> depthMap;
|
||||
|
||||
if( waitKey( 30 ) >= 0 )
|
||||
break;
|
||||
}
|
||||
@endcode
|
||||
For getting several data maps use VideoCapture::grab and VideoCapture::retrieve, e.g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture(CAP_REALSENSE);
|
||||
for(;;)
|
||||
{
|
||||
Mat depthMap;
|
||||
Mat image;
|
||||
Mat irImage;
|
||||
|
||||
capture.grab();
|
||||
|
||||
capture.retrieve( depthMap, CAP_INTELPERC_DEPTH_MAP );
|
||||
capture.retrieve( image, CAP_INTELPERC_IMAGE );
|
||||
capture.retrieve( irImage, CAP_INTELPERC_IR_MAP);
|
||||
|
||||
if( waitKey( 30 ) >= 0 )
|
||||
break;
|
||||
}
|
||||
@endcode
|
||||
For setting and getting some property of sensor\` data generators use VideoCapture::set and
|
||||
VideoCapture::get methods respectively, e.g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture(CAP_REALSENSE);
|
||||
capture.set( CAP_INTELPERC_DEPTH_GENERATOR | CAP_PROP_INTELPERC_PROFILE_IDX, 0 );
|
||||
cout << "FPS " << capture.get( CAP_INTELPERC_DEPTH_GENERATOR+CAP_PROP_FPS ) << endl;
|
||||
@endcode
|
||||
Since two types of sensor's data generators are supported (image generator and depth generator),
|
||||
there are two flags that should be used to set/get property of the needed generator:
|
||||
|
||||
- CAP_INTELPERC_IMAGE_GENERATOR -- a flag for access to the image generator properties.
|
||||
- CAP_INTELPERC_DEPTH_GENERATOR -- a flag for access to the depth generator properties. This
|
||||
flag value is assumed by default if neither of the two possible values of the property is set.
|
||||
|
||||
For more information please refer to the example of usage
|
||||
[videocapture_realsense.cpp](https://github.com/opencv/opencv/tree/4.x/samples/cpp/videocapture_realsense.cpp)
|
||||
in opencv/samples/cpp folder.
|
||||
@@ -0,0 +1,144 @@
|
||||
Using Kinect and other OpenNI compatible depth sensors {#tutorial_kinect_openni}
|
||||
======================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_video_write}
|
||||
@next_tutorial{tutorial_orbbec_astra_openni}
|
||||
|
||||
|
||||
Depth sensors compatible with OpenNI (Kinect, XtionPRO, ...) are supported through VideoCapture
|
||||
class. Depth map, BGR image and some other formats of output can be retrieved by using familiar
|
||||
interface of VideoCapture.
|
||||
|
||||
In order to use depth sensor with OpenCV you should do the following preliminary steps:
|
||||
|
||||
-# Install OpenNI library (from here <http://www.openni.org/downloadfiles>) and PrimeSensor Module
|
||||
for OpenNI (from here <https://github.com/avin2/SensorKinect>). The installation should be done
|
||||
to default folders listed in the instructions of these products, e.g.:
|
||||
@code{.text}
|
||||
OpenNI:
|
||||
Linux & MacOSX:
|
||||
Libs into: /usr/lib
|
||||
Includes into: /usr/include/ni
|
||||
Windows:
|
||||
Libs into: c:/Program Files/OpenNI/Lib
|
||||
Includes into: c:/Program Files/OpenNI/Include
|
||||
PrimeSensor Module:
|
||||
Linux & MacOSX:
|
||||
Bins into: /usr/bin
|
||||
Windows:
|
||||
Bins into: c:/Program Files/Prime Sense/Sensor/Bin
|
||||
@endcode
|
||||
If one or both products were installed to the other folders, the user should change
|
||||
corresponding CMake variables OPENNI_LIB_DIR, OPENNI_INCLUDE_DIR or/and
|
||||
OPENNI_PRIME_SENSOR_MODULE_BIN_DIR.
|
||||
|
||||
-# Configure OpenCV with OpenNI support by setting WITH_OPENNI flag in CMake. If OpenNI is found
|
||||
in install folders OpenCV will be built with OpenNI library (see a status OpenNI in CMake log)
|
||||
whereas PrimeSensor Modules can not be found (see a status OpenNI PrimeSensor Modules in CMake
|
||||
log). Without PrimeSensor module OpenCV will be successfully compiled with OpenNI library, but
|
||||
VideoCapture object will not grab data from Kinect sensor.
|
||||
|
||||
-# Build OpenCV.
|
||||
|
||||
VideoCapture can retrieve the following data:
|
||||
|
||||
-# data given from depth generator:
|
||||
- CAP_OPENNI_DEPTH_MAP - depth values in mm (CV_16UC1)
|
||||
- CAP_OPENNI_POINT_CLOUD_MAP - XYZ in meters (CV_32FC3)
|
||||
- CAP_OPENNI_DISPARITY_MAP - disparity in pixels (CV_8UC1)
|
||||
- CAP_OPENNI_DISPARITY_MAP_32F - disparity in pixels (CV_32FC1)
|
||||
- CAP_OPENNI_VALID_DEPTH_MASK - mask of valid pixels (not occluded, not shaded etc.)
|
||||
(CV_8UC1)
|
||||
|
||||
-# data given from BGR image generator:
|
||||
- CAP_OPENNI_BGR_IMAGE - color image (CV_8UC3)
|
||||
- CAP_OPENNI_GRAY_IMAGE - gray image (CV_8UC1)
|
||||
|
||||
In order to get depth map from depth sensor use VideoCapture::operator \>\>, e. g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture( CAP_OPENNI );
|
||||
for(;;)
|
||||
{
|
||||
Mat depthMap;
|
||||
capture >> depthMap;
|
||||
|
||||
if( waitKey( 30 ) >= 0 )
|
||||
break;
|
||||
}
|
||||
@endcode
|
||||
For getting several data maps use VideoCapture::grab and VideoCapture::retrieve, e.g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture(0); // or CAP_OPENNI
|
||||
for(;;)
|
||||
{
|
||||
Mat depthMap;
|
||||
Mat bgrImage;
|
||||
|
||||
capture.grab();
|
||||
|
||||
capture.retrieve( depthMap, CAP_OPENNI_DEPTH_MAP );
|
||||
capture.retrieve( bgrImage, CAP_OPENNI_BGR_IMAGE );
|
||||
|
||||
if( waitKey( 30 ) >= 0 )
|
||||
break;
|
||||
}
|
||||
@endcode
|
||||
For setting and getting some property of sensor\` data generators use VideoCapture::set and
|
||||
VideoCapture::get methods respectively, e.g. :
|
||||
@code{.cpp}
|
||||
VideoCapture capture( CAP_OPENNI );
|
||||
capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_VGA_30HZ );
|
||||
cout << "FPS " << capture.get( CAP_OPENNI_IMAGE_GENERATOR+CAP_PROP_FPS ) << endl;
|
||||
@endcode
|
||||
Since two types of sensor's data generators are supported (image generator and depth generator),
|
||||
there are two flags that should be used to set/get property of the needed generator:
|
||||
|
||||
- CAP_OPENNI_IMAGE_GENERATOR -- A flag for access to the image generator properties.
|
||||
- CAP_OPENNI_DEPTH_GENERATOR -- A flag for access to the depth generator properties. This flag
|
||||
value is assumed by default if neither of the two possible values of the property is not set.
|
||||
|
||||
Some depth sensors (for example XtionPRO) do not have image generator. In order to check it you can
|
||||
get CAP_OPENNI_IMAGE_GENERATOR_PRESENT property.
|
||||
@code{.cpp}
|
||||
bool isImageGeneratorPresent = capture.get( CAP_PROP_OPENNI_IMAGE_GENERATOR_PRESENT ) != 0; // or == 1
|
||||
@endcode
|
||||
Flags specifying the needed generator type must be used in combination with particular generator
|
||||
property. The following properties of cameras available through OpenNI interfaces are supported:
|
||||
|
||||
- For image generator:
|
||||
|
||||
- CAP_PROP_OPENNI_OUTPUT_MODE -- Three output modes are supported: CAP_OPENNI_VGA_30HZ
|
||||
used by default (image generator returns images in VGA resolution with 30 FPS),
|
||||
CAP_OPENNI_SXGA_15HZ (image generator returns images in SXGA resolution with 15 FPS) and
|
||||
CAP_OPENNI_SXGA_30HZ (image generator returns images in SXGA resolution with 30 FPS, the
|
||||
mode is supported by XtionPRO Live); depth generator's maps are always in VGA resolution.
|
||||
|
||||
- For depth generator:
|
||||
|
||||
- CAP_PROP_OPENNI_REGISTRATION -- Flag that registers the remapping depth map to image map
|
||||
by changing depth generator's view point (if the flag is "on") or sets this view point to
|
||||
its normal one (if the flag is "off"). The registration process’s resulting images are
|
||||
pixel-aligned,which means that every pixel in the image is aligned to a pixel in the depth
|
||||
image.
|
||||
|
||||
Next properties are available for getting only:
|
||||
|
||||
- CAP_PROP_OPENNI_FRAME_MAX_DEPTH -- A maximum supported depth of Kinect in mm.
|
||||
- CAP_PROP_OPENNI_BASELINE -- Baseline value in mm.
|
||||
- CAP_PROP_OPENNI_FOCAL_LENGTH -- A focal length in pixels.
|
||||
- CAP_PROP_FRAME_WIDTH -- Frame width in pixels.
|
||||
- CAP_PROP_FRAME_HEIGHT -- Frame height in pixels.
|
||||
- CAP_PROP_FPS -- Frame rate in FPS.
|
||||
|
||||
- Some typical flags combinations "generator type + property" are defined as single flags:
|
||||
|
||||
- CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE = CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_OUTPUT_MODE
|
||||
- CAP_OPENNI_DEPTH_GENERATOR_BASELINE = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_BASELINE
|
||||
- CAP_OPENNI_DEPTH_GENERATOR_FOCAL_LENGTH = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_FOCAL_LENGTH
|
||||
- CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_REGISTRATION
|
||||
|
||||
For more information please refer to the example of usage
|
||||
[videocapture_openni.cpp](https://github.com/opencv/opencv/tree/4.x/samples/cpp/videocapture_openni.cpp) in
|
||||
opencv/samples/cpp folder.
|
||||
@@ -0,0 +1,204 @@
|
||||
Using Orbbec Astra 3D cameras {#tutorial_orbbec_astra_openni}
|
||||
======================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_kinect_openni}
|
||||
@next_tutorial{tutorial_orbbec_uvc}
|
||||
|
||||
|
||||
### Introduction
|
||||
|
||||
This tutorial is devoted to the Astra Series of Orbbec 3D cameras (https://www.orbbec.com/products/structured-light-camera/astra-series/).
|
||||
That cameras have a depth sensor in addition to a common color sensor. The depth sensors can be read using
|
||||
the open source OpenNI API with @ref cv::VideoCapture class. The video stream is provided through the regular
|
||||
camera interface.
|
||||
|
||||
### Installation Instructions
|
||||
|
||||
In order to use the Astra camera's depth sensor with OpenCV you should do the following steps:
|
||||
|
||||
-# Download the latest version of Orbbec OpenNI SDK (from here <https://www.orbbec.com/developers/openni-sdk/>).
|
||||
Unzip the archive, choose the build according to your operating system and follow installation
|
||||
steps provided in the Readme file.
|
||||
|
||||
-# For instance, if you use 64bit GNU/Linux run:
|
||||
@code{.bash}
|
||||
$ cd Linux/OpenNI-Linux-x64-2.3.0.63/
|
||||
$ sudo ./install.sh
|
||||
@endcode
|
||||
When you are done with the installation, make sure to replug your device for udev rules to take
|
||||
effect. The camera should now work as a general camera device. Note that your current user should
|
||||
belong to group `video` to have access to the camera. Also, make sure to source `OpenNIDevEnvironment` file:
|
||||
@code{.bash}
|
||||
$ source OpenNIDevEnvironment
|
||||
@endcode
|
||||
To verify that the source command works and OpenNI library and header files can be found, run the following
|
||||
command and you should see something similar in your terminal:
|
||||
@code{.bash}
|
||||
$ echo $OPENNI2_INCLUDE
|
||||
/home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Include
|
||||
$ echo $OPENNI2_REDIST
|
||||
/home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Redist
|
||||
@endcode
|
||||
If the above two variables are empty, then you need to source `OpenNIDevEnvironment` again.
|
||||
|
||||
@note Orbbec OpenNI SDK version 2.3.0.86 and newer does not provide `install.sh` any more.
|
||||
You can use the following script to initialize environment:
|
||||
@code{.text}
|
||||
# Check if user is root/running with sudo
|
||||
if [ `whoami` != root ]; then
|
||||
echo Please run this script with sudo
|
||||
exit
|
||||
fi
|
||||
|
||||
ORIG_PATH=`pwd`
|
||||
cd `dirname $0`
|
||||
SCRIPT_PATH=`pwd`
|
||||
cd $ORIG_PATH
|
||||
|
||||
if [ "`uname -s`" != "Darwin" ]; then
|
||||
# Install UDEV rules for USB device
|
||||
cp ${SCRIPT_PATH}/orbbec-usb.rules /etc/udev/rules.d/558-orbbec-usb.rules
|
||||
echo "usb rules file install at /etc/udev/rules.d/558-orbbec-usb.rules"
|
||||
fi
|
||||
|
||||
OUT_FILE="$SCRIPT_PATH/OpenNIDevEnvironment"
|
||||
echo "export OPENNI2_INCLUDE=$SCRIPT_PATH/../sdk/Include" > $OUT_FILE
|
||||
echo "export OPENNI2_REDIST=$SCRIPT_PATH/../sdk/libs" >> $OUT_FILE
|
||||
chmod a+r $OUT_FILE
|
||||
echo "exit"
|
||||
@endcode
|
||||
|
||||
@note The last tried version `2.3.0.86_202210111154_4c8f5aa4_beta6` does not work correctly with
|
||||
modern Linux, even after libusb rebuild as recommended by the instruction. The last know good
|
||||
configuration is version 2.3.0.63 (tested with Ubuntu 18.04 amd64). It's not provided officially
|
||||
with the downloading page, but published by Orbbec technical support on Orbbec community forum
|
||||
[here](https://3dclub.orbbec3d.com/t/universal-download-thread-for-astra-series-cameras/622).
|
||||
|
||||
-# Now you can configure OpenCV with OpenNI support enabled by setting the `WITH_OPENNI2` flag in CMake.
|
||||
You may also like to enable the `BUILD_EXAMPLES` flag to get a code sample working with your Astra camera.
|
||||
Run the following commands in the directory containing OpenCV source code to enable OpenNI support:
|
||||
@code{.bash}
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -DWITH_OPENNI2=ON ..
|
||||
@endcode
|
||||
If the OpenNI library is found, OpenCV will be built with OpenNI2 support. You can see the status of OpenNI2
|
||||
support in the CMake log:
|
||||
@code{.text}
|
||||
-- Video I/O:
|
||||
-- DC1394: YES (2.2.6)
|
||||
-- FFMPEG: YES
|
||||
-- avcodec: YES (58.91.100)
|
||||
-- avformat: YES (58.45.100)
|
||||
-- avutil: YES (56.51.100)
|
||||
-- swscale: YES (5.7.100)
|
||||
-- avresample: NO
|
||||
-- GStreamer: YES (1.18.1)
|
||||
-- OpenNI2: YES (2.3.0)
|
||||
-- v4l/v4l2: YES (linux/videodev2.h)
|
||||
@endcode
|
||||
|
||||
-# Build OpenCV:
|
||||
@code{.bash}
|
||||
$ make
|
||||
@endcode
|
||||
|
||||
### Code
|
||||
|
||||
The Astra Pro camera has two sensors -- a depth sensor and a color sensor. The depth sensor
|
||||
can be read using the OpenNI interface with @ref cv::VideoCapture class. The video stream is
|
||||
not available through OpenNI API and is only provided via the regular camera interface.
|
||||
So, to get both depth and color frames, two @ref cv::VideoCapture objects should be created:
|
||||
|
||||
@snippetlineno samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp Open streams
|
||||
|
||||
The first object will use the OpenNI2 API to retrieve depth data. The second one uses the
|
||||
Video4Linux2 interface to access the color sensor. Note that the example above assumes that
|
||||
the Astra camera is the first camera in the system. If you have more than one camera connected,
|
||||
you may need to explicitly set the proper camera number.
|
||||
|
||||
Before using the created VideoCapture objects you may want to set up stream parameters by setting
|
||||
objects' properties. The most important parameters are frame width, frame height and fps.
|
||||
For this example, we’ll configure width and height of both streams to VGA resolution, which is
|
||||
the maximum resolution available for both sensors, and we’d like both stream parameters to be the
|
||||
same for easier color-to-depth data registration:
|
||||
|
||||
@snippetlineno samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp Setup streams
|
||||
|
||||
For setting and retrieving some property of sensor data generators use @ref cv::VideoCapture::set and
|
||||
@ref cv::VideoCapture::get methods respectively, e.g. :
|
||||
|
||||
@snippetlineno samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp Get properties
|
||||
|
||||
The following properties of cameras available through OpenNI interface are supported for the depth
|
||||
generator:
|
||||
|
||||
- @ref cv::CAP_PROP_FRAME_WIDTH -- Frame width in pixels.
|
||||
- @ref cv::CAP_PROP_FRAME_HEIGHT -- Frame height in pixels.
|
||||
- @ref cv::CAP_PROP_FPS -- Frame rate in FPS.
|
||||
- @ref cv::CAP_PROP_OPENNI_REGISTRATION -- Flag that registers the remapping depth map to image map
|
||||
by changing the depth generator's viewpoint (if the flag is "on") or sets this view point to
|
||||
its normal one (if the flag is "off"). The registration process’ resulting images are
|
||||
pixel-aligned, which means that every pixel in the image is aligned to a pixel in the depth
|
||||
image.
|
||||
- @ref cv::CAP_PROP_OPENNI2_MIRROR -- Flag to enable or disable mirroring for this stream. Set to 0
|
||||
to disable mirroring
|
||||
|
||||
Next properties are available for getting only:
|
||||
|
||||
- @ref cv::CAP_PROP_OPENNI_FRAME_MAX_DEPTH -- A maximum supported depth of the camera in mm.
|
||||
- @ref cv::CAP_PROP_OPENNI_BASELINE -- Baseline value in mm.
|
||||
|
||||
After the VideoCapture objects have been set up, you can start reading frames from them.
|
||||
|
||||
@note
|
||||
OpenCV's VideoCapture provides synchronous API, so you have to grab frames in a new thread
|
||||
to avoid one stream blocking while another stream is being read. VideoCapture is not a
|
||||
thread-safe class, so you need to be careful to avoid any possible deadlocks or data races.
|
||||
|
||||
As there are two video sources that should be read simultaneously, it’s necessary to create two
|
||||
threads to avoid blocking. Example implementation that gets frames from each sensor in a new thread
|
||||
and stores them in a list along with their timestamps:
|
||||
|
||||
@snippetlineno samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp Read streams
|
||||
|
||||
VideoCapture can retrieve the following data:
|
||||
|
||||
-# data given from the depth generator:
|
||||
- @ref cv::CAP_OPENNI_DEPTH_MAP - depth values in mm (CV_16UC1)
|
||||
- @ref cv::CAP_OPENNI_POINT_CLOUD_MAP - XYZ in meters (CV_32FC3)
|
||||
- @ref cv::CAP_OPENNI_DISPARITY_MAP - disparity in pixels (CV_8UC1)
|
||||
- @ref cv::CAP_OPENNI_DISPARITY_MAP_32F - disparity in pixels (CV_32FC1)
|
||||
- @ref cv::CAP_OPENNI_VALID_DEPTH_MASK - mask of valid pixels (not occluded, not shaded, etc.)
|
||||
(CV_8UC1)
|
||||
|
||||
-# data given from the color sensor is a regular BGR image (CV_8UC3).
|
||||
|
||||
When new data are available, each reading thread notifies the main thread using a condition variable.
|
||||
A frame is stored in the ordered list -- the first frame in the list is the earliest captured,
|
||||
the last frame is the latest captured. As depth and color frames are read from independent sources
|
||||
two video streams may become out of sync even when both streams are set up for the same frame rate.
|
||||
A post-synchronization procedure can be applied to the streams to combine depth and color frames into
|
||||
pairs. The sample code below demonstrates this procedure:
|
||||
|
||||
@snippetlineno samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp Pair frames
|
||||
|
||||
In the code snippet above the execution is blocked until there are some frames in both frame lists.
|
||||
When there are new frames, their timestamps are being checked -- if they differ more than a half of
|
||||
the frame period then one of the frames is dropped. If timestamps are close enough, then two frames
|
||||
are paired. Now, we have two frames: one containing color information and another one -- depth information.
|
||||
In the example above retrieved frames are simply shown with cv::imshow function, but you can insert
|
||||
any other processing code here.
|
||||
|
||||
In the sample images below you can see the color frame and the depth frame representing the same scene.
|
||||
Looking at the color frame it's hard to distinguish plant leaves from leaves painted on a wall,
|
||||
but the depth data makes it easy.
|
||||
|
||||

|
||||

|
||||
|
||||
The complete implementation can be found in
|
||||
[openni_orbbec_astra.cpp](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/videoio/openni_orbbec_astra/openni_orbbec_astra.cpp)
|
||||
in `samples/cpp/tutorial_code/videoio` directory.
|
||||
@@ -0,0 +1,136 @@
|
||||
Using Orbbec 3D cameras (UVC) {#tutorial_orbbec_uvc}
|
||||
====================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_orbbec_astra_openni}
|
||||
@next_tutorial{tutorial_intelperc}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Jinyue Chen |
|
||||
| Compatibility | OpenCV >= 4.10 |
|
||||
|
||||
### Introduction
|
||||
This tutorial is devoted to the Orbbec 3D cameras based on UVC protocol. For the use of the older
|
||||
Orbbec 3D cameras which depends on OpenNI, please refer to the
|
||||
[previous tutorial](https://github.com/opencv/opencv/blob/4.x/doc/tutorials/app/orbbec_astra_openni.markdown).
|
||||
|
||||
Unlike working with the OpenNI based Astra 3D cameras which requires OpenCV built with OpenNI2 SDK,
|
||||
Orbbec SDK is not required to be installed for accessing Orbbec UVC 3D cameras via OpenCV. By using
|
||||
`cv::VideoCapture` class, users get the stream data from 3D cameras, similar to working with USB
|
||||
cameras. The calibration and alignment of the depth map and color image are done internally.
|
||||
|
||||
### Instructions
|
||||
|
||||
In order to use the 3D cameras with OpenCV. You can refer to [Get Started](https://opencv.org/get-started/)
|
||||
to install OpenCV.
|
||||
|
||||
Note since 4.11 on, Mac OS users need to compile OpenCV from source with flag
|
||||
`-DOBSENSOR_USE_ORBBEC_SDK=ON` in order to use the cameras:
|
||||
```bash
|
||||
cmake -DOBSENSOR_USE_ORBBEC_SDK=ON ..
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
By default, when `-DOBSENSOR_USE_ORBBEC_SDK=ON` is enabled, OrbbecSDK v2 is used (i.e., `ORBBEC_SDK_VERSION` defaults to `2`); it supports the entire Orbbec Gemini 330 series.
|
||||
|
||||
If you need legacy cameras such as Orbbec Femto, Gemini2XL, or Astra+, switch to OrbbecSDK v1 with the flag `-DORBBEC_SDK_VERSION=1`:
|
||||
```bash
|
||||
cmake -DOBSENSOR_USE_ORBBEC_SDK=ON -DORBBEC_SDK_VERSION=1 ..
|
||||
make -j
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@add_toggle_python
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/blob/4.x/samples/python/videocapture_obsensor.py)
|
||||
@include samples/python/videocapture_obsensor.py
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_cpp
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/blob/4.x/samples/cpp/videocapture_obsensor.cpp)
|
||||
@include samples/cpp/videocapture_obsensor.cpp
|
||||
@end_toggle
|
||||
|
||||
### Code Explanation
|
||||
|
||||
#### Python
|
||||
|
||||
- **Open Orbbec Depth Sensor**:
|
||||
Using `cv.VideoCapture(0, cv.CAP_OBSENSOR)` to attempt to open the first Orbbec depth sensor device.
|
||||
If the camera fails to open, the program will exit and display an error message.
|
||||
|
||||
- **Loop to Grab and Process Data**:
|
||||
In an infinite loop, the code continuously grabs data from the camera. The `orbbec_cap.grab()`
|
||||
method attempts to grab a frame.
|
||||
|
||||
- **Process BGR Image**:
|
||||
Using `orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_BGR_IMAGE)` to retrieve the BGR image data.
|
||||
If successfully retrieved, the BGR image is displayed in a window using `cv.imshow("BGR", bgr_image)`.
|
||||
|
||||
- **Process Depth Image**:
|
||||
Using `orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_DEPTH_MAP)` to retrieve the depth image data.
|
||||
If successfully retrieved, the depth image is first normalized to a range of 0 to 255, then a
|
||||
false color image is applied, and the result is displayed in a window using `cv.imshow("DEPTH", color_depth_map)`.
|
||||
|
||||
- **Keyboard Interrupt**:
|
||||
Using `cv.pollKey()` to detect keyboard events. If a key is pressed, the loop breaks and
|
||||
the program ends.
|
||||
|
||||
- **Release Resources**:
|
||||
After exiting the loop, the camera resources are released using `orbbec_cap.release()`.
|
||||
|
||||
#### C++
|
||||
|
||||
- **Open Orbbec Depth Sensor**:
|
||||
Using `VideoCapture obsensorCapture(0, CAP_OBSENSOR)` to attempt to open the first Orbbec depth
|
||||
sensor device. If the camera fails to open, an error message is displayed, and the program exits.
|
||||
|
||||
- **Retrieve Camera Intrinsic Parameters**:
|
||||
Using `obsensorCapture.get()` to retrieve the intrinsic parameters of the camera, including focal
|
||||
lengths (`fx`, `fy`) and principal points (`cx`, `cy`).
|
||||
|
||||
- **Loop to Grab and Process Data**:
|
||||
In an infinite loop, the code continuously grabs data from the camera. The `obsensorCapture.grab()`
|
||||
method attempts to grab a frame.
|
||||
|
||||
- **Process BGR Image**:
|
||||
Using `obsensorCapture.retrieve(image, CAP_OBSENSOR_BGR_IMAGE)` to retrieve the BGR image data.
|
||||
If successfully retrieved, the BGR image is displayed in a window using `imshow("BGR", image)`.
|
||||
|
||||
- **Process Depth Image**:
|
||||
Using `obsensorCapture.retrieve(depthMap, CAP_OBSENSOR_DEPTH_MAP)` to retrieve the depth image data.
|
||||
If successfully retrieved, the depth image is normalized and a false color image is applied, then
|
||||
the result is displayed in a window using `imshow("DEPTH", adjDepthMap)`. The retrieved depth
|
||||
values are in millimeters and are truncated to a range between 300 and 5000 (millimeter).
|
||||
This fixed range can be interpreted as a truncation based on the depth camera's depth range,
|
||||
removing invalid pixels on the depth map.
|
||||
|
||||
- **Overlay Depth Map on BGR Image**:
|
||||
Convert the depth map to an 8-bit image, resize it to match the BGR image size, and overlay it
|
||||
on the BGR image with a specified transparency (`alpha`). The overlaid image is displayed in
|
||||
a window using `imshow("DepthToColor", image)`.
|
||||
|
||||
- **Keyboard Interrupt**:
|
||||
Using `pollKey()` to detect keyboard events. If a key is pressed, the loop breaks and the program ends.
|
||||
|
||||
- **Release Resources**:
|
||||
After exiting the loop, the camera resources are released.
|
||||
### Results
|
||||
#### Python
|
||||
|
||||

|
||||
|
||||
#### C++
|
||||
|
||||

|
||||
|
||||
### Note
|
||||
- Mac users need sudo privileges to execute the code.
|
||||
- **Firmware**: If you’re using an Orbbec UVC 3D camera, please ensure your camera’s firmware is updated to the latest version to avoid potential compatibility issues. For more details, see [Orbbec’s Release Notes](https://github.com/orbbec/OrbbecSDK_v2/releases).
|
||||
@@ -0,0 +1,107 @@
|
||||
Reading Geospatial Raster files with GDAL {#tutorial_raster_io_gdal}
|
||||
=========================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_trackbar}
|
||||
@next_tutorial{tutorial_video_input_psnr_ssim}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Marvin Smith |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Geospatial raster data is a heavily used product in Geographic Information Systems and
|
||||
Photogrammetry. Raster data typically can represent imagery and Digital Elevation Models (DEM). The
|
||||
standard library for loading GIS imagery is the Geographic Data Abstraction Library [(GDAL)](http://www.gdal.org). In this
|
||||
example, we will show techniques for loading GIS raster formats using native OpenCV functions. In
|
||||
addition, we will show some an example of how OpenCV can use this data for novel and interesting
|
||||
purposes.
|
||||
|
||||
Goals
|
||||
-----
|
||||
|
||||
The primary objectives for this tutorial:
|
||||
|
||||
- How to use OpenCV [imread](@ref imread) to load satellite imagery.
|
||||
- How to use OpenCV [imread](@ref imread) to load SRTM Digital Elevation Models
|
||||
- Given the corner coordinates of both the image and DEM, correlate the elevation data to the
|
||||
image to find elevations for each pixel.
|
||||
- Show a basic, easy-to-implement example of a terrain heat map.
|
||||
- Show a basic use of DEM data coupled with ortho-rectified imagery.
|
||||
|
||||
To implement these goals, the following code takes a Digital Elevation Model as well as a GeoTiff
|
||||
image of San Francisco as input. The image and DEM data is processed and generates a terrain heat
|
||||
map of the image as well as labels areas of the city which would be affected should the water level
|
||||
of the bay rise 10, 50, and 100 meters.
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@include cpp/tutorial_code/imgcodecs/GDAL_IO/gdal-image.cpp
|
||||
|
||||
How to Read Raster Data using GDAL
|
||||
----------------------------------
|
||||
|
||||
This demonstration uses the default OpenCV imread function. The primary difference is that in order
|
||||
to force GDAL to load the image, you must use the appropriate flag.
|
||||
@snippet cpp/tutorial_code/imgcodecs/GDAL_IO/gdal-image.cpp load1
|
||||
When loading digital elevation models, the actual numeric value of each pixel is essential and
|
||||
cannot be scaled or truncated. For example, with image data a pixel represented as a double with a
|
||||
value of 1 has an equal appearance to a pixel which is represented as an unsigned character with a
|
||||
value of 255. With terrain data, the pixel value represents the elevation in meters. In order to
|
||||
ensure that OpenCV preserves the native value, use the GDAL flag in imread with the ANYDEPTH flag.
|
||||
@snippet cpp/tutorial_code/imgcodecs/GDAL_IO/gdal-image.cpp load2
|
||||
If you know beforehand the type of DEM model you are loading, then it may be a safe bet to test the
|
||||
Mat::type() or Mat::depth() using an assert or other mechanism. NASA or DOD specification documents
|
||||
can provide the input types for various elevation models. The major types, SRTM and DTED, are both
|
||||
signed shorts.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
### Lat/Lon (Geographic) Coordinates should normally be avoided
|
||||
|
||||
The Geographic Coordinate System is a spherical coordinate system, meaning that using them with
|
||||
Cartesian mathematics is technically incorrect. This demo uses them to increase the readability and
|
||||
is accurate enough to make the point. A better coordinate system would be Universal Transverse
|
||||
Mercator.
|
||||
|
||||
### Finding the corner coordinates
|
||||
|
||||
One easy method to find the corner coordinates of an image is to use the command-line tool gdalinfo.
|
||||
For imagery which is ortho-rectified and contains the projection information, you can use the [USGS
|
||||
EarthExplorer](http://http://earthexplorer.usgs.gov).
|
||||
@code{.bash}
|
||||
\f$> gdalinfo N37W123.hgt
|
||||
|
||||
Driver: SRTMHGT/SRTMHGT File Format
|
||||
Files: N37W123.hgt
|
||||
Size is 3601, 3601
|
||||
Coordinate System is:
|
||||
GEOGCS["WGS 84",
|
||||
DATUM["WGS_1984",
|
||||
|
||||
... more output ...
|
||||
|
||||
Corner Coordinates:
|
||||
Upper Left (-123.0001389, 38.0001389) (123d 0' 0.50"W, 38d 0' 0.50"N)
|
||||
Lower Left (-123.0001389, 36.9998611) (123d 0' 0.50"W, 36d59'59.50"N)
|
||||
Upper Right (-121.9998611, 38.0001389) (121d59'59.50"W, 38d 0' 0.50"N)
|
||||
Lower Right (-121.9998611, 36.9998611) (121d59'59.50"W, 36d59'59.50"N)
|
||||
Center (-122.5000000, 37.5000000) (122d30' 0.00"W, 37d30' 0.00"N)
|
||||
|
||||
... more output ...
|
||||
@endcode
|
||||
Results
|
||||
-------
|
||||
|
||||
Below is the output of the program. Use the first image as the input. For the DEM model, download
|
||||
the SRTM file located at the USGS here.
|
||||
[<http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip>](http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -0,0 +1,13 @@
|
||||
Application utils (highgui, imgcodecs, videoio modules) {#tutorial_table_of_content_app}
|
||||
=======================================================
|
||||
|
||||
- @subpage tutorial_trackbar
|
||||
- @subpage tutorial_raster_io_gdal
|
||||
- @subpage tutorial_video_input_psnr_ssim
|
||||
- @subpage tutorial_video_write
|
||||
- @subpage tutorial_kinect_openni
|
||||
- @subpage tutorial_orbbec_astra_openni
|
||||
- @subpage tutorial_orbbec_uvc
|
||||
- @subpage tutorial_intelperc
|
||||
- @subpage tutorial_wayland_ubuntu
|
||||
- @subpage tutorial_animations
|
||||
@@ -0,0 +1,142 @@
|
||||
Adding a Trackbar to our applications! {#tutorial_trackbar}
|
||||
======================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@next_tutorial{tutorial_raster_io_gdal}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Ana Huamán |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
|
||||
- In the previous tutorials (about @ref tutorial_adding_images and the @ref tutorial_basic_linear_transform)
|
||||
you might have noted that we needed to give some **input** to our programs, such
|
||||
as \f$\alpha\f$ and \f$beta\f$. We accomplished that by entering this data using the Terminal.
|
||||
- Well, it is time to use some fancy GUI tools. OpenCV provides some GUI utilities (**highgui** module)
|
||||
for you. An example of this is a **Trackbar**.
|
||||
|
||||

|
||||
|
||||
- In this tutorial we will just modify our two previous programs so that they get the input
|
||||
information from the trackbar.
|
||||
|
||||
Goals
|
||||
-----
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
- Add a Trackbar in an OpenCV window by using @ref cv::createTrackbar
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
Let's modify the program made in the tutorial @ref tutorial_adding_images. We will let the user enter the
|
||||
\f$\alpha\f$ value by using the Trackbar.
|
||||
|
||||
@add_toggle_cpp
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp)
|
||||
@include cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java)
|
||||
@include java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py)
|
||||
@include python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
We only analyze the code that is related to Trackbar:
|
||||
|
||||
- First, we load two images, which are going to be blended.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py load
|
||||
@end_toggle
|
||||
|
||||
- To create a trackbar, first we have to create the window in which it is going to be located. So:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp window
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java window
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py window
|
||||
@end_toggle
|
||||
|
||||
- Now we can create the Trackbar:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp create_trackbar
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java create_trackbar
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py create_trackbar
|
||||
@end_toggle
|
||||
|
||||
Note the following (C++ code):
|
||||
- Our Trackbar has a label **TrackbarName**
|
||||
- The Trackbar is located in the window named **Linear Blend**
|
||||
- The Trackbar values will be in the range from \f$0\f$ to **alpha_slider_max** (the minimum
|
||||
limit is always **zero**).
|
||||
- The numerical value of Trackbar is stored in **alpha_slider**
|
||||
- Whenever the user moves the Trackbar, the callback function **on_trackbar** is called
|
||||
|
||||
Finally, we have to define the callback function **on_trackbar** for C++ and Python code, using an anonymous inner class listener in Java
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp on_trackbar
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/highgui/trackbar/AddingImagesTrackbar.java on_trackbar
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/highgui/trackbar/AddingImagesTrackbar.py on_trackbar
|
||||
@end_toggle
|
||||
|
||||
Note that (C++ code):
|
||||
- We use the value of **alpha_slider** (integer) to get a double value for **alpha**.
|
||||
- **alpha_slider** is updated each time the trackbar is displaced by the user.
|
||||
- We define *src1*, *src2*, *dist*, *alpha*, *alpha_slider* and *beta* as global variables,
|
||||
so they can be used everywhere.
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
- Our program produces the following output:
|
||||
|
||||

|
||||
|
||||
- As a manner of practice, you can also add two trackbars for the program made in
|
||||
@ref tutorial_basic_linear_transform. One trackbar to set \f$\alpha\f$ and another for set \f$\beta\f$. The output might
|
||||
look like:
|
||||
|
||||

|
||||
@@ -0,0 +1,203 @@
|
||||
Video Input with OpenCV and similarity measurement {#tutorial_video_input_psnr_ssim}
|
||||
==================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_raster_io_gdal}
|
||||
@next_tutorial{tutorial_video_write}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
Today it is common to have a digital video recording system at your disposal. Therefore, you will
|
||||
eventually come to the situation that you no longer process a batch of images, but video streams.
|
||||
These may be of two kinds: real-time image feed (in the case of a webcam) or prerecorded and hard
|
||||
disk drive stored files. Luckily OpenCV treats these two in the same manner, with the same C++
|
||||
class. So here's what you'll learn in this tutorial:
|
||||
|
||||
- How to open and read video streams
|
||||
- Two ways for checking image similarity: PSNR and SSIM
|
||||
|
||||
The source code
|
||||
---------------
|
||||
|
||||
As a test case where to show off these using OpenCV I've created a small program that reads in two
|
||||
video files and performs a similarity check between them. This is something you could use to check
|
||||
just how well a new video compressing algorithms works. Let there be a reference (original) video
|
||||
like [this small Megamind clip
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/data/Megamind.avi) and [a compressed
|
||||
version of it ](https://github.com/opencv/opencv/tree/4.x/samples/data/Megamind_bugy.avi).
|
||||
You may also find the source code and these video file in the
|
||||
`samples/data` folder of the OpenCV source library.
|
||||
|
||||
@add_toggle_cpp
|
||||
@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py
|
||||
@end_toggle
|
||||
|
||||
How to read a video stream (online-camera or offline-file)?
|
||||
-----------------------------------------------------------
|
||||
|
||||
Essentially, all the functionalities required for video manipulation is integrated in the @ref cv::VideoCapture
|
||||
C++ class. This on itself builds on the FFmpeg open source library. This is a basic
|
||||
dependency of OpenCV so you shouldn't need to worry about this. A video is composed of a succession
|
||||
of images, we refer to these in the literature as frames. In case of a video file there is a *frame
|
||||
rate* specifying just how long is between two frames. While for the video cameras usually there is a
|
||||
limit of just how many frames they can digitize per second, this property is less important as at
|
||||
any time the camera sees the current snapshot of the world.
|
||||
|
||||
The first task you need to do is to assign to a @ref cv::VideoCapture class its source. You can do
|
||||
this either via the @ref cv::VideoCapture::VideoCapture or its @ref cv::VideoCapture::open function. If this argument is an
|
||||
integer then you will bind the class to a camera, a device. The number passed here is the ID of the
|
||||
device, assigned by the operating system. If you have a single camera attached to your system its ID
|
||||
will probably be zero and further ones increasing from there. If the parameter passed to these is a
|
||||
string it will refer to a video file, and the string points to the location and name of the file.
|
||||
For example, to the upper source code a valid command line is:
|
||||
@code{.bash}
|
||||
video/Megamind.avi video/Megamind_bug.avi 35 10
|
||||
@endcode
|
||||
We do a similarity check. This requires a reference and a test case video file. The first two
|
||||
arguments refer to this. Here we use a relative address. This means that the application will look
|
||||
into its current working directory and open the video folder and try to find inside this the
|
||||
*Megamind.avi* and the *Megamind_bug.avi*.
|
||||
@code{.cpp}
|
||||
const string sourceReference = argv[1],sourceCompareWith = argv[2];
|
||||
|
||||
VideoCapture captRefrnc(sourceReference);
|
||||
// or
|
||||
VideoCapture captUndTst;
|
||||
captUndTst.open(sourceCompareWith);
|
||||
@endcode
|
||||
To check if the binding of the class to a video source was successful or not use the @ref cv::VideoCapture::isOpened
|
||||
function:
|
||||
@code{.cpp}
|
||||
if ( !captRefrnc.isOpened())
|
||||
{
|
||||
cout << "Could not open reference " << sourceReference << endl;
|
||||
return -1;
|
||||
}
|
||||
@endcode
|
||||
Closing the video is automatic when the objects destructor is called. However, if you want to close
|
||||
it before this you need to call its @ref cv::VideoCapture::release function. The frames of the video are just
|
||||
simple images. Therefore, we just need to extract them from the @ref cv::VideoCapture object and put
|
||||
them inside a *Mat* one. The video streams are sequential. You may get the frames one after another
|
||||
by the @ref cv::VideoCapture::read or the overloaded \>\> operator:
|
||||
@code{.cpp}
|
||||
Mat frameReference, frameUnderTest;
|
||||
captRefrnc >> frameReference;
|
||||
captUndTst.read(frameUnderTest);
|
||||
@endcode
|
||||
The upper read operations will leave empty the *Mat* objects if no frame could be acquired (either
|
||||
cause the video stream was closed or you got to the end of the video file). We can check this with a
|
||||
simple if:
|
||||
@code{.cpp}
|
||||
if( frameReference.empty() || frameUnderTest.empty())
|
||||
{
|
||||
// exit the program
|
||||
}
|
||||
@endcode
|
||||
A read method is made of a frame grab and a decoding applied on that. You may call explicitly these
|
||||
two by using the @ref cv::VideoCapture::grab and then the @ref cv::VideoCapture::retrieve functions.
|
||||
|
||||
Videos have many-many information attached to them besides the content of the frames. These are
|
||||
usually numbers, however in some case it may be short character sequences (4 bytes or less). Due to
|
||||
this to acquire these information there is a general function named @ref cv::VideoCapture::get that returns double
|
||||
values containing these properties. Use bitwise operations to decode the characters from a double
|
||||
type and conversions where valid values are only integers. Its single argument is the ID of the
|
||||
queried property. For example, here we get the size of the frames in the reference and test case
|
||||
video file; plus the number of frames inside the reference.
|
||||
@code{.cpp}
|
||||
Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH),
|
||||
(int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)),
|
||||
|
||||
cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height
|
||||
<< " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;
|
||||
@endcode
|
||||
When you are working with videos you may often want to control these values yourself. To do this
|
||||
there is a @ref cv::VideoCapture::set function. Its first argument remains the name of the property you want to
|
||||
change and there is a second of double type containing the value to be set. It will return true if
|
||||
it succeeds and false otherwise. Good examples for this is seeking in a video file to a given time
|
||||
or frame:
|
||||
@code{.cpp}
|
||||
captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // go to the 1.2 second in the video
|
||||
captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // go to the 10th frame of the video
|
||||
// now a read operation would read the frame at the set position
|
||||
@endcode
|
||||
For properties you can read and change look into the documentation of the @ref cv::VideoCapture::get and
|
||||
@ref cv::VideoCapture::set functions.
|
||||
|
||||
### Image similarity - PSNR and SSIM
|
||||
|
||||
We want to check just how imperceptible our video converting operation went, therefore we need a
|
||||
system to check frame by frame the similarity or differences. The most common algorithm used for
|
||||
this is the PSNR (aka **Peak signal-to-noise ratio**). The simplest definition of this starts out
|
||||
from the *mean squared error*. Let there be two images: I1 and I2; with a two dimensional size i and
|
||||
j, composed of c number of channels.
|
||||
|
||||
\f[MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2}\f]
|
||||
|
||||
Then the PSNR is expressed as:
|
||||
|
||||
\f[PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right)\f]
|
||||
|
||||
Here the \f$MAX_I\f$ is the maximum valid value for a pixel. In case of the simple single byte image
|
||||
per pixel per channel this is 255. When two images are the same the MSE will give zero, resulting in
|
||||
an invalid divide by zero operation in the PSNR formula. In this case the PSNR is undefined and as
|
||||
we'll need to handle this case separately. The transition to a logarithmic scale is made because the
|
||||
pixel values have a very wide dynamic range. All this translated to OpenCV and a function looks
|
||||
like:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-psnr
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-psnr
|
||||
@end_toggle
|
||||
|
||||
Typically result values are anywhere between 30 and 50 for video compression, where higher is
|
||||
better. If the images significantly differ you'll get much lower ones like 15 and so. This
|
||||
similarity check is easy and fast to calculate, however in practice it may turn out somewhat
|
||||
inconsistent with human eye perception. The **structural similarity** algorithm aims to correct
|
||||
this.
|
||||
|
||||
Describing the methods goes well beyond the purpose of this tutorial. For that I invite you to read
|
||||
the article introducing it. Nevertheless, you can get a good image of it by looking at the OpenCV
|
||||
implementation below.
|
||||
|
||||
@note
|
||||
SSIM is described more in-depth in the: "Z. Wang, A. C. Bovik, H. R. Sheikh and E. P.
|
||||
Simoncelli, "Image quality assessment: From error visibility to structural similarity," IEEE
|
||||
Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004." article.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-mssim
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-mssim
|
||||
@end_toggle
|
||||
|
||||
This will return a similarity index for each channel of the image. This value is between zero and
|
||||
one, where one corresponds to perfect fit. Unfortunately, the many Gaussian blurring is quite
|
||||
costly, so while the PSNR may work in a real time like environment (24 frames per second) this will
|
||||
take significantly more than to accomplish similar performance results.
|
||||
|
||||
Therefore, the source code presented at the start of the tutorial will perform the PSNR measurement
|
||||
for each frame, and the SSIM only for the frames where the PSNR falls below an input value. For
|
||||
visualization purpose we show both images in an OpenCV window and print the PSNR and MSSIM values to
|
||||
the console. Expect to see something like:
|
||||
|
||||

|
||||
|
||||
You may observe a runtime instance of this on the [YouTube here](https://www.youtube.com/watch?v=iOcNljutOgg).
|
||||
|
||||
@youtube{iOcNljutOgg}
|
||||
@@ -0,0 +1,166 @@
|
||||
Creating a video with OpenCV {#tutorial_video_write}
|
||||
============================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_video_input_psnr_ssim}
|
||||
@next_tutorial{tutorial_kinect_openni}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
Whenever you work with video feeds you may eventually want to save your image processing result in a
|
||||
form of a new video file. For simple video outputs you can use the OpenCV built-in @ref cv::VideoWriter
|
||||
class, designed for this.
|
||||
|
||||
- How to create a video file with OpenCV
|
||||
- What type of video files you can create with OpenCV
|
||||
- How to extract a given color channel from a video
|
||||
|
||||
As a simple demonstration I'll just extract one of the BGR color channels of an input video file
|
||||
into a new video. You can control the flow of the application from its console line arguments:
|
||||
|
||||
- The first argument points to the video file to work on
|
||||
- The second argument may be one of the characters: R G B. This will specify which of the channels
|
||||
to extract.
|
||||
- The last argument is the character Y (Yes) or N (No). If this is no, the codec used for the
|
||||
input video file will be the same as for the output. Otherwise, a window will pop up and allow
|
||||
you to select yourself the codec to use.
|
||||
|
||||
For example, a valid command line would look like:
|
||||
@code{.bash}
|
||||
video-write.exe video/Megamind.avi R Y
|
||||
@endcode
|
||||
The source code
|
||||
---------------
|
||||
|
||||
You may also find the source code and these video file in the
|
||||
`samples/cpp/tutorial_code/videoio/video-write/` folder of the OpenCV source library or [download it
|
||||
from here ](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/videoio/video-write/video-write.cpp).
|
||||
|
||||
@include cpp/tutorial_code/videoio/video-write/video-write.cpp
|
||||
|
||||
The structure of a video
|
||||
------------------------
|
||||
|
||||
For start, you should have an idea of just how a video file looks. Every video file in itself is a
|
||||
container. The type of the container is expressed in the files extension (for example *avi*, *mov*
|
||||
or *mkv*). This contains multiple elements like: video feeds, audio feeds or other tracks (like for
|
||||
example subtitles). How these feeds are stored is determined by the codec used for each one of them.
|
||||
In case of the audio tracks commonly used codecs are *mp3* or *aac*. For the video files the list is
|
||||
somehow longer and includes names such as *XVID*, *DIVX*, *H264* or *LAGS* (*Lagarith Lossless
|
||||
Codec*). The full list of codecs you may use on a system depends on just what one you have
|
||||
installed.
|
||||
|
||||

|
||||
|
||||
As you can see things can get really complicated with videos. However, OpenCV is mainly a computer
|
||||
vision library, not a video stream, codec and write one. Therefore, the developers tried to keep
|
||||
this part as simple as possible. Due to this OpenCV for video containers supports only the *avi*
|
||||
extension, its first version. A direct limitation of this is that you cannot save a video file
|
||||
larger than 2 GB. Furthermore you can only create and expand a single video track inside the
|
||||
container. No audio or other track editing support here. Nevertheless, any video codec present on
|
||||
your system might work. If you encounter some of these limitations you will need to look into more
|
||||
specialized video writing libraries such as *FFmpeg* or codecs as *HuffYUV*, *CorePNG* and *LCL*. As
|
||||
an alternative, create the video track with OpenCV and expand it with sound tracks or convert it to
|
||||
other formats by using video manipulation programs such as *VirtualDub* or *AviSynth*.
|
||||
|
||||
The VideoWriter class
|
||||
-----------------------
|
||||
|
||||
The content written here builds on the assumption you
|
||||
already read the @ref tutorial_video_input_psnr_ssim tutorial and you know how to read video files. To create a
|
||||
video file you just need to create an instance of the @ref cv::VideoWriter class. You can specify
|
||||
its properties either via parameters in the constructor or later on via the @ref cv::VideoWriter::open function.
|
||||
Either way, the parameters are the same: 1. The name of the output that contains the container type
|
||||
in its extension. At the moment only *avi* is supported. We construct this from the input file, add
|
||||
to this the name of the channel to use, and finish it off with the container extension.
|
||||
@code{.cpp}
|
||||
const string source = argv[1]; // the source file name
|
||||
string::size_type pAt = source.find_last_of('.'); // Find extension point
|
||||
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi"; // Form the new name with container
|
||||
@endcode
|
||||
-# The codec to use for the video track. Now all the video codecs have a unique short name of
|
||||
maximum four characters. Hence, the *XVID*, *DIVX* or *H264* names. This is called a four
|
||||
character code. You may also ask this from an input video by using its *get* function. Because
|
||||
the *get* function is a general function it always returns double values. A double value is
|
||||
stored on 64 bits. Four characters are four bytes, meaning 32 bits. These four characters are
|
||||
coded in the lower 32 bits of the *double*. A simple way to throw away the upper 32 bits would
|
||||
be to just convert this value to *int*:
|
||||
@code{.cpp}
|
||||
VideoCapture inputVideo(source); // Open input
|
||||
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC)); // Get Codec Type- Int form
|
||||
@endcode
|
||||
OpenCV internally works with this integer type and expect this as its second parameter. Now to
|
||||
convert from the integer form to string we may use two methods: a bitwise operator and a union
|
||||
method. The first one extracting from an int the characters looks like (an "and" operation, some
|
||||
shifting and adding a 0 at the end to close the string):
|
||||
@code{.cpp}
|
||||
char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
|
||||
@endcode
|
||||
You can do the same thing with the *union* as:
|
||||
@code{.cpp}
|
||||
union { int v; char c[5];} uEx ;
|
||||
uEx.v = ex; // From Int to char via union
|
||||
uEx.c[4]='\0';
|
||||
@endcode
|
||||
The advantage of this is that the conversion is done automatically after assigning, while for
|
||||
the bitwise operator you need to do the operations whenever you change the codec type. In case
|
||||
you know the codecs four character code beforehand, you can use the *CV_FOURCC* macro to build
|
||||
the integer:
|
||||
@code{.cpp}
|
||||
CV_FOURCC('P','I','M,'1') // this is an MPEG1 codec from the characters to integer
|
||||
@endcode
|
||||
If you pass for this argument minus one then a window will pop up at runtime that contains all
|
||||
the codec installed on your system and ask you to select the one to use:
|
||||
|
||||

|
||||
|
||||
-# The frame per second for the output video. Again, here I keep the input videos frame per second
|
||||
by using the *get* function.
|
||||
-# The size of the frames for the output video. Here too I keep the input videos frame size per
|
||||
second by using the *get* function.
|
||||
-# The final argument is an optional one. By default is true and says that the output will be a
|
||||
colorful one (so for write you will send three channel images). To create a gray scale video
|
||||
pass a false parameter here.
|
||||
|
||||
Here it is, how I use it in the sample:
|
||||
@code{.cpp}
|
||||
VideoWriter outputVideo;
|
||||
Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH), //Acquire input size
|
||||
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
|
||||
outputVideo.open(NAME , ex, inputVideo.get(CAP_PROP_FPS),S, true);
|
||||
@endcode
|
||||
Afterwards, you use the @ref cv::VideoWriter::isOpened() function to find out if the open operation succeeded or
|
||||
not. The video file automatically closes when the *VideoWriter* object is destroyed. After you open
|
||||
the object with success you can send the frames of the video in a sequential order by using the
|
||||
@ref cv::VideoWriter::write function of the class. Alternatively, you can use its overloaded operator \<\< :
|
||||
@code{.cpp}
|
||||
outputVideo.write(res); //or
|
||||
outputVideo << res;
|
||||
@endcode
|
||||
Extracting a color channel from an BGR image means to set to zero the BGR values of the other
|
||||
channels. You can either do this with image scanning operations or by using the split and merge
|
||||
operations. You first split the channels up into different images, set the other channels to zero
|
||||
images of the same size and type and finally merge them back:
|
||||
@code{.cpp}
|
||||
split(src, spl); // process - extract only the correct channel
|
||||
for( int i =0; i < 3; ++i)
|
||||
if (i != channel)
|
||||
spl[i] = Mat::zeros(S, spl[0].type());
|
||||
merge(spl, res);
|
||||
@endcode
|
||||
Put all this together and you'll get the upper source code, whose runtime result will show something
|
||||
around the idea:
|
||||
|
||||

|
||||
|
||||
You may observe a runtime instance of this on the [YouTube
|
||||
here](https://www.youtube.com/watch?v=jpBwHxsl1_0).
|
||||
|
||||
@youtube{jpBwHxsl1_0}
|
||||
@@ -0,0 +1,334 @@
|
||||
Camera calibration With OpenCV {#tutorial_camera_calibration}
|
||||
==============================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_camera_calibration_square_chess}
|
||||
@next_tutorial{tutorial_real_time_pose}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 4.0 |
|
||||
|
||||
|
||||
Cameras have been around for a very long time. However, with the introduction of the cheap *pinhole*
|
||||
cameras in the late 20th century, they became a common occurrence in our everyday life.
|
||||
Unfortunately, this low cost comes with a trade-off: significant distortion. Luckily, these are
|
||||
constants and with a calibration and some remapping we can correct this. Furthermore, with
|
||||
calibration you may also determine the relation between the camera's natural units (pixels) and the
|
||||
real world units (for example millimeters).
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
For distortion, OpenCV takes into account both radial and tangential factors. For the radial
|
||||
factor one uses the following formulas:
|
||||
|
||||
\f[r^2 = x^2 + y^2\f]
|
||||
|
||||
\f[x_{distorted} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\
|
||||
y_{distorted} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6)\f]
|
||||
|
||||
So for an undistorted pixel point at \f$(x,y)\f$ coordinates, its position on the distorted image
|
||||
will be \f$(x_{distorted} y_{distorted})\f$. The presence of the radial distortion manifests in form
|
||||
of the "barrel" or "fish-eye" effect.
|
||||
|
||||
Tangential distortion occurs because the image taking lenses are not perfectly parallel to the
|
||||
imaging plane. It can be represented via the formulas:
|
||||
|
||||
\f[x_{distorted} = x + [ 2p_1xy + p_2(r^2+2x^2)] \\
|
||||
y_{distorted} = y + [ p_1(r^2+ 2y^2)+ 2p_2xy]\f]
|
||||
|
||||
So we have five distortion parameters which in OpenCV are presented as one row matrix with 5
|
||||
columns:
|
||||
|
||||
\f[distortion\_coefficients=(k_1 \hspace{10pt} k_2 \hspace{10pt} p_1 \hspace{10pt} p_2 \hspace{10pt} k_3)\f]
|
||||
|
||||
Now for the unit conversion we use the following formula:
|
||||
|
||||
\f[\left [ \begin{matrix} x \\ y \\ w \end{matrix} \right ] = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ] \left [ \begin{matrix} X \\ Y \\ Z \end{matrix} \right ]\f]
|
||||
|
||||
Here the presence of \f$w\f$ is explained by the use of homography coordinate system (and \f$w=Z\f$). The
|
||||
unknown parameters are \f$f_x\f$ and \f$f_y\f$ (camera focal lengths) and \f$(c_x, c_y)\f$ which are the optical
|
||||
centers expressed in pixels coordinates. If for both axes a common focal length is used with a given
|
||||
\f$a\f$ aspect ratio (usually 1), then \f$f_y=f_x*a\f$ and in the upper formula we will have a single focal
|
||||
length \f$f\f$. The matrix containing these four parameters is referred to as the *camera matrix*. While
|
||||
the distortion coefficients are the same regardless of the camera resolutions used, these should be
|
||||
scaled along with the current resolution from the calibrated resolution.
|
||||
|
||||
The process of determining these two matrices is the calibration. Calculation of these parameters is
|
||||
done through basic geometrical equations. The equations used depend on the chosen calibrating
|
||||
objects. Currently OpenCV supports three types of objects for calibration:
|
||||
|
||||
- Classical black-white chessboard
|
||||
- ChArUco board pattern
|
||||
- Symmetrical circle pattern
|
||||
- Asymmetrical circle pattern
|
||||
|
||||
In practice, you need to capture multiple images of these patterns using your camera and let OpenCV find them.
|
||||
Each found pattern results in a new equation. To solve the equation you need at least a
|
||||
predetermined number of pattern snapshots to form a well-posed equation system. This number is
|
||||
higher for the chessboard pattern and lower for circle-based patterns.For example, in theory the
|
||||
chessboard pattern requires at least two snapshots. However, in practice we have a good amount of
|
||||
noise present in our input images, so for good results you will probably need at least 10 good
|
||||
snapshots of the input pattern in different positions.
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
The sample application will:
|
||||
|
||||
- Determine the distortion matrix
|
||||
- Determine the camera matrix
|
||||
- Take input from Camera, Video and Image file list
|
||||
- Read configuration from XML/YAML file
|
||||
- Save the results into XML/YAML file
|
||||
- Calculate re-projection error
|
||||
|
||||
Source code
|
||||
-----------
|
||||
|
||||
You may also find the source code in the `samples/cpp/tutorial_code/calib3d/camera_calibration/`
|
||||
folder of the OpenCV source library or [download it from here
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp).
|
||||
For the usage of the program, run it with `-h` argument. The program has an
|
||||
essential argument: the name of its configuration file. If none is given then it will try to open the
|
||||
one named "default.xml". [Here's a sample configuration file
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/calib3d/camera_calibration/in_VID5.xml) in XML format. In the
|
||||
configuration file you may choose to use camera as an input, a video file or an image list. If you
|
||||
opt for the last one, you will need to create a configuration file where you enumerate the images to
|
||||
use. Here's [an example of this ](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/calib3d/camera_calibration/VID5.xml).
|
||||
The important part to remember is that the images need to be specified using the absolute path or
|
||||
the relative one from your application's working directory. You may find all this in the samples
|
||||
directory mentioned above.
|
||||
|
||||
The application starts up with reading the settings from the configuration file. Although, this is
|
||||
an important part of it, it has nothing to do with the subject of this tutorial: *camera
|
||||
calibration*. Therefore, I've chosen not to post the code for that part here. Technical background
|
||||
on how to do this you can find in the @ref tutorial_file_input_output_with_xml_yml tutorial.
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
-# **Read the settings**
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp file_read
|
||||
|
||||
For this I've used simple OpenCV class input operation. After reading the file I've an
|
||||
additional post-processing function that checks validity of the input. Only if all inputs are
|
||||
good then *goodInput* variable will be true.
|
||||
|
||||
-# **Get next input, if it fails or we have enough of them - calibrate**
|
||||
|
||||
After this we have a big
|
||||
loop where we do the following operations: get the next image from the image list, camera or
|
||||
video file. If this fails or we have enough images then we run the calibration process. In case
|
||||
of image we step out of the loop and otherwise the remaining frames will be undistorted (if the
|
||||
option is set) via changing from *DETECTION* mode to the *CALIBRATED* one.
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp get_input
|
||||
For some cameras we may need to flip the input image. Here we do this too.
|
||||
|
||||
-# **Find the pattern in the current input**
|
||||
|
||||
The formation of the equations I mentioned above aims
|
||||
to finding major patterns in the input: in case of the chessboard these are corners of the
|
||||
squares and for the circles, well, the circles themselves. ChArUco board is equivalent to
|
||||
chessboard, but corners are matched by ArUco markers. The position of these will form the
|
||||
result which will be written into the *pointBuf* vector.
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp find_pattern
|
||||
Depending on the type of the input pattern you use either the @ref cv::findChessboardCorners or
|
||||
the @ref cv::findCirclesGrid function or @ref cv::aruco::CharucoDetector::detectBoard method.
|
||||
For all of them you pass the current image and the size of the board and you'll get the positions
|
||||
of the patterns. cv::findChessboardCorners and cv::findCirclesGrid return a boolean variable
|
||||
which states if the pattern was found in the input (we only need to take into account
|
||||
those images where this is true!). `CharucoDetector::detectBoard` may detect partially visible
|
||||
pattern and returns coordinates and ids of visible inner corners.
|
||||
|
||||
@note Board size and amount of matched points is different for chessboard, circles grid and ChArUco.
|
||||
All chessboard related algorithm expects amount of inner corners as board width and height.
|
||||
Board size of circles grid is just amount of circles by both grid dimensions. ChArUco board size
|
||||
is defined in squares, but detection result is list of inner corners and that's why is smaller
|
||||
by 1 in both dimensions.
|
||||
|
||||
In the case of live cameras, we only capture images when an input delay time is passed.
|
||||
This is done to allow the user to move the chessboard around and getting different images.
|
||||
Similar images result in similar equations, and similar equations at the calibration step will
|
||||
form an ill-posed problem, so the calibration will fail. For square images the positions of the
|
||||
corners are only approximate. We may improve this by calling the @ref cv::cornerSubPix function.
|
||||
(`winSize` is used to control the side length of the search window. Its default value is 11.
|
||||
`winSize` may be changed by command line parameter `--winSize=<number>`.)
|
||||
It will produce better calibration result. After this we add a valid inputs result to the
|
||||
*imagePoints* vector to collect all of the equations into a single container. Finally, for
|
||||
visualization feedback purposes we will draw the found points on the input image using @ref
|
||||
cv::findChessboardCorners function.
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp pattern_found
|
||||
|
||||
-# **Show state and result to the user, plus command line control of the application**
|
||||
|
||||
This part shows text output on the image.
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp output_text
|
||||
If we ran calibration and got camera's matrix with the distortion coefficients we may want to
|
||||
correct the image using @ref cv::undistort function:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp output_undistorted
|
||||
Then we show the image and wait for an input key and if this is *u* we toggle the distortion removal,
|
||||
if it is *g* we start again the detection process, and finally for the *ESC* key we quit the application:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp await_input
|
||||
|
||||
-# **Show the distortion removal for the images too**
|
||||
|
||||
When you work with an image list it is not
|
||||
possible to remove the distortion inside the loop. Therefore, you must do this after the loop.
|
||||
Taking advantage of this now I'll expand the @ref cv::undistort function, which is in fact first
|
||||
calls @ref cv::initUndistortRectifyMap to find transformation matrices and then performs
|
||||
transformation using @ref cv::remap function. Because, after successful calibration map
|
||||
calculation needs to be done only once, by using this expanded form you may speed up your
|
||||
application:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp show_results
|
||||
|
||||
The calibration and save
|
||||
------------------------
|
||||
|
||||
Because the calibration needs to be done only once per camera, it makes sense to save it after a
|
||||
successful calibration. This way later on you can just load these values into your program. Due to
|
||||
this we first make the calibration, and if it succeeds we save the result into an OpenCV style XML
|
||||
or YAML file, depending on the extension you give in the configuration file.
|
||||
|
||||
Therefore in the first function we just split up these two processes. Because we want to save many
|
||||
of the calibration variables we'll create these variables here and pass on both of them to the
|
||||
calibration and saving function. Again, I'll not show the saving part as that has little in common
|
||||
with the calibration. Explore the source file in order to find out how and what:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp run_and_save
|
||||
We do the calibration with the help of the @ref cv::calibrateCameraRO function. It has the following
|
||||
parameters:
|
||||
|
||||
- The object points. This is a vector of *Point3f* vector that for each input image describes how
|
||||
should the pattern look. If we have a planar pattern (like a chessboard) then we can simply set
|
||||
all Z coordinates to zero. This is a collection of the points where these important points are
|
||||
present. Because, we use a single pattern for all the input images we can calculate this just
|
||||
once and multiply it for all the other input views. We calculate the corner points with the
|
||||
*calcBoardCornerPositions* function as:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp board_corners
|
||||
And then multiply it as:
|
||||
@code{.cpp}
|
||||
vector<vector<Point3f> > objectPoints(1);
|
||||
calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
|
||||
objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + grid_width;
|
||||
newObjPoints = objectPoints[0];
|
||||
|
||||
objectPoints.resize(imagePoints.size(),objectPoints[0]);
|
||||
@endcode
|
||||
@note If your calibration board is inaccurate, unmeasured, roughly planar targets (Checkerboard
|
||||
patterns on paper using off-the-shelf printers are the most convenient calibration targets and
|
||||
most of them are not accurate enough.), a method from @cite strobl2011iccv can be utilized to
|
||||
dramatically improve the accuracies of the estimated camera intrinsic parameters. This new
|
||||
calibration method will be called if command line parameter `-d=<number>` is provided. In the
|
||||
above code snippet, `grid_width` is actually the value set by `-d=<number>`. It's the measured
|
||||
distance between top-left (0, 0, 0) and top-right (s.squareSize*(s.boardSize.width-1), 0, 0)
|
||||
corners of the pattern grid points. It should be measured precisely with rulers or vernier calipers.
|
||||
After calibration, newObjPoints will be updated with refined 3D coordinates of object points.
|
||||
- The image points. This is a vector of *Point2f* vector which for each input image contains
|
||||
coordinates of the important points (corners for chessboard and centers of the circles for the
|
||||
circle pattern). We have already collected this from @ref cv::findChessboardCorners or @ref
|
||||
cv::findCirclesGrid function. We just need to pass it on.
|
||||
- The size of the image acquired from the camera, video file or the images.
|
||||
- The index of the object point to be fixed. We set it to -1 to request standard calibration method.
|
||||
If the new object-releasing method to be used, set it to the index of the top-right corner point
|
||||
of the calibration board grid. See cv::calibrateCameraRO for detailed explanation.
|
||||
@code{.cpp}
|
||||
int iFixedPoint = -1;
|
||||
if (release_object)
|
||||
iFixedPoint = s.boardSize.width - 1;
|
||||
@endcode
|
||||
- The camera matrix. If we used the fixed aspect ratio option we need to set \f$f_x\f$:
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp fixed_aspect
|
||||
- The distortion coefficient matrix. Initialize with zero.
|
||||
@code{.cpp}
|
||||
distCoeffs = Mat::zeros(8, 1, CV_64F);
|
||||
@endcode
|
||||
- For all the views the function will calculate rotation and translation vectors which transform
|
||||
the object points (given in the model coordinate space) to the image points (given in the world
|
||||
coordinate space). The 7-th and 8-th parameters are the output vector of matrices containing in
|
||||
the i-th position the rotation and translation vector for the i-th object point to the i-th
|
||||
image point.
|
||||
- The updated output vector of calibration pattern points. This parameter is ignored with standard
|
||||
calibration method.
|
||||
- The final argument is the flag. You need to specify here options like fix the aspect ratio for
|
||||
the focal length, assume zero tangential distortion or to fix the principal point. Here we use
|
||||
CALIB_USE_LU to get faster calibration speed.
|
||||
@code{.cpp}
|
||||
rms = calibrateCameraRO(objectPoints, imagePoints, imageSize, iFixedPoint,
|
||||
cameraMatrix, distCoeffs, rvecs, tvecs, newObjPoints,
|
||||
s.flag | CALIB_USE_LU);
|
||||
@endcode
|
||||
- The function returns the average re-projection error. This number gives a good estimation of
|
||||
precision of the found parameters. This should be as close to zero as possible. Given the
|
||||
intrinsic, distortion, rotation and translation matrices we may calculate the error for one view
|
||||
by using the @ref cv::projectPoints to first transform the object point to image point. Then we
|
||||
calculate the absolute norm between what we got with our transformation and the corner/circle
|
||||
finding algorithm. To find the average error we calculate the arithmetical mean of the errors
|
||||
calculated for all the calibration images.
|
||||
@snippet samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp compute_errors
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
Let there be [this input chessboard pattern ](pattern.png) which has a size of 9 X 6. I've used an
|
||||
AXIS IP camera to create a couple of snapshots of the board and saved it into VID5 directory. I've
|
||||
put this inside the `images/CameraCalibration` folder of my working directory and created the
|
||||
following `VID5.XML` file that describes which images to use:
|
||||
@code{.xml}
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<images>
|
||||
images/CameraCalibration/VID5/xx1.jpg
|
||||
images/CameraCalibration/VID5/xx2.jpg
|
||||
images/CameraCalibration/VID5/xx3.jpg
|
||||
images/CameraCalibration/VID5/xx4.jpg
|
||||
images/CameraCalibration/VID5/xx5.jpg
|
||||
images/CameraCalibration/VID5/xx6.jpg
|
||||
images/CameraCalibration/VID5/xx7.jpg
|
||||
images/CameraCalibration/VID5/xx8.jpg
|
||||
</images>
|
||||
</opencv_storage>
|
||||
@endcode
|
||||
Then passed `images/CameraCalibration/VID5/VID5.XML` as an input in the configuration file. Here's a
|
||||
chessboard pattern found during the runtime of the application:
|
||||
|
||||

|
||||
|
||||
After applying the distortion removal we get:
|
||||
|
||||

|
||||
|
||||
The same works for [this asymmetrical circle pattern ](acircles_pattern.png) by setting the input
|
||||
width to 4 and height to 11. This time I've used a live camera feed by specifying its ID ("1") for
|
||||
the input. Here's, how a detected pattern should look:
|
||||
|
||||

|
||||
|
||||
In both cases in the specified output XML/YAML file you'll find the camera and distortion
|
||||
coefficients matrices:
|
||||
@code{.xml}
|
||||
<camera_matrix type_id="opencv-matrix">
|
||||
<rows>3</rows>
|
||||
<cols>3</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
6.5746697944293521e+002 0. 3.1950000000000000e+002 0.
|
||||
6.5746697944293521e+002 2.3950000000000000e+002 0. 0. 1.</data></camera_matrix>
|
||||
<distortion_coefficients type_id="opencv-matrix">
|
||||
<rows>5</rows>
|
||||
<cols>1</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
-4.1802327176423804e-001 5.0715244063187526e-001 0. 0.
|
||||
-5.7843597214487474e-001</data></distortion_coefficients>
|
||||
@endcode
|
||||
Add these values as constants to your program, call the @ref cv::initUndistortRectifyMap and the
|
||||
@ref cv::remap function to remove distortion and enjoy distortion free inputs for cheap and low
|
||||
quality cameras.
|
||||
|
||||
You may observe a runtime instance of this on the [YouTube
|
||||
here](https://www.youtube.com/watch?v=ViPN810E0SU).
|
||||
|
||||
@youtube{ViPN810E0SU}
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,172 @@
|
||||
Create Calibration Pattern {#tutorial_camera_calibration_pattern}
|
||||
==========================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@next_tutorial{tutorial_camera_calibration_square_chess}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Authors | Laurent Berger, Alexander Panov, Alexander Smorkalov |
|
||||
| Compatibility | OpenCV > 4.12 |
|
||||
|
||||
|
||||
The tutorial describes all pattern supported by OpenCV for camera(s) calibration and pose estimation
|
||||
with their strength, pitfalls and practical recommendations.
|
||||
|
||||
What is calibration pattern? why I need it?
|
||||
-------------------------------------------
|
||||
|
||||
The flat printable pattern may be used:
|
||||
|
||||
1. For camera intrinsics (internal parameters) calibration. See @ref tutorial_camera_calibration.
|
||||
2. For stereo or multi-camera system extrinsics (external parameters: rotation and translation
|
||||
of each camera) calibration. See cv::stereoCalibrate for details.
|
||||
3. Camera pose registration relative to well known point in 3d world. See multiview calibration
|
||||
tutorial in OpenCV 5.x.
|
||||
|
||||
Pattern Types
|
||||
-------------
|
||||
|
||||
**Chessboard**. Classic calibration pattern of black and white squares. The all calibration algorithms
|
||||
use internal chessboard corners as features. See cv::findChessboardCorners and cv::cornerSubPix to
|
||||
detect the board and refine corners coordinates with sub-pixel accuracy. The board size is defined
|
||||
as amount of internal corners, but not amount of black or white squares. Also pay attention, that
|
||||
the board with even size is symmetric. If board has even amount of corners by one of direction then
|
||||
its pose is defined up to 180 degrees (2 solutions). It the board is square with size N x N then its
|
||||
pose is defined up to 90 degrees (4 solutions). The last two cases are not suitable for calibration.
|
||||
Example code to generate features coordinates for calibration (object points):
|
||||
```
|
||||
std::vector<cv::Point3f> objectPoints;
|
||||
for (int i = 0; i < boardSize.height; ++i) {
|
||||
for (int j = 0; j < boardSize.width; ++j) {
|
||||
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
|
||||
}
|
||||
}
|
||||
```
|
||||
Printable chessboard pattern: https://github.com/opencv/opencv/blob/4.x/doc/pattern.png
|
||||
(9x6 chessboard, page width: 210 mm, page height: 297 mm (A4))
|
||||
|
||||
**Circles Grid**. The circles grid is symmetric or asymmetric (each even row shifted) grid of black
|
||||
circles on a white background or vice verse. See cv::findCirclesGrid function to detect the board
|
||||
with OpenCV. The detector produces sub-pixel coordinates of the circle centers and does not require
|
||||
additional refinement. The board size is defined as amount of circles in grid by x and y axis.
|
||||
In case of asymmetric grid the shifted rows are taken into account too. The board is suitable for
|
||||
intrinsics calibration. Symmetric grids suffer from the same issue as chessboard pattern with even
|
||||
size. It's pose is defined up to 180 degrees.
|
||||
Example code to generate features coordinates for calibration with symmetric grid (object points):
|
||||
```
|
||||
std::vector<cv::Point3f> objectPoints;
|
||||
for (int i = 0; i < boardSize.height; ++i) {
|
||||
for (int j = 0; j < boardSize.width; ++j) {
|
||||
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
|
||||
}
|
||||
}
|
||||
```
|
||||
Example code to generate features coordinates for calibration with asymmetric grid (object points):
|
||||
```
|
||||
std::vector<cv::Point3f> objectPoints;
|
||||
for (int i = 0; i < boardSize.height; i++) {
|
||||
for (int j = 0; j < boardSize.width; j++) {
|
||||
objectPoints.push_back(Point3f((2 * j + i % 2)*squareSize, i*squareSize, 0));
|
||||
}
|
||||
}
|
||||
```
|
||||
Printable asymmetric circles grid pattern: https://github.com/opencv/opencv/blob/4.x/doc/acircles_pattern.png
|
||||
(11x4 asymmetric circles grid, page width: 210 mm, page height: 297 mm (A4))
|
||||
|
||||
**ChAruco board**. Chessboard unreached with ArUco markers. Each internal corner of the board is
|
||||
described by 2 neighborhood ArUco markers that makes it unique. The board size is defined in number
|
||||
of units, but not internal corners. ChAruco board of size N x M is equivalent to chessboard pattern
|
||||
of size N-1 x M-1. OpenCV provides `cv::aruco::CharucoDetector` class for the board detection.
|
||||
The detector algorithm finds ArUco markers first and them "assembles" the board using knowledge
|
||||
about ArUco pairs. In opposite to the previous pattern partially occluded board may be used as all
|
||||
corners are labeled. The board is rotation invariant, but set of ArUco markers and their order
|
||||
should be known to detector apriori. It cannot detect ChAruco board with predefined size and random
|
||||
set of markers.
|
||||
Example code to generate features coordinates for calibration (object points) for board size in units:
|
||||
```
|
||||
std::vector<cv::Point3f> objectPoints;
|
||||
for (int i = 0; i < boardSize.height-1; ++i) {
|
||||
for (int j = 0; j < boardSize.width-1; ++j) {
|
||||
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
|
||||
}
|
||||
}
|
||||
```
|
||||
Printable ChAruco board pattern: https://github.com/opencv/opencv/blob/4.x/doc/charuco_board_pattern.png
|
||||
(7X5 ChAruco board, square size: 30 mm, marker size: 15 mm, ArUco dict: DICT_5X5_100, page width:
|
||||
210 mm, page height: 297 mm (A4))
|
||||
|
||||
Create Your Own Pattern
|
||||
-----------------------
|
||||
|
||||
In case if ready pattern does not satisfy your requirements, you can generate your own. OpenCV
|
||||
provides generate_pattern.py tool in `apps/pattern-tools` of source repository or your binary
|
||||
distribution. The only requirement is Python 3.
|
||||
|
||||
Examples:
|
||||
|
||||
create a checkerboard pattern in file chessboard.svg with 9 rows, 6 columns and a square size of 20mm:
|
||||
|
||||
python generate_pattern.py -o chessboard.svg --rows 9 --columns 6 --type checkerboard --square_size 20
|
||||
|
||||
create a circle board pattern in file circleboard.svg with 7 rows, 5 columns and a radius of 15 mm:
|
||||
|
||||
python generate_pattern.py -o circleboard.svg --rows 7 --columns 5 --type circles --square_size 15
|
||||
|
||||
create a circle board pattern in file acircleboard.svg with 7 rows, 5 columns and a square size of
|
||||
10mm and less spacing between circle:
|
||||
|
||||
python generate_pattern.py -o acircleboard.svg --rows 7 --columns 5 --type acircles --square_size 10 --radius_rate 2
|
||||
|
||||
create a radon checkerboard for findChessboardCornersSB() with markers in (7 4), (7 5), (8 5) cells:
|
||||
|
||||
python generate_pattern.py -o radon_checkerboard.svg --rows 10 --columns 15 --type radon_checkerboard -s 12.1 -m 7 4 7 5 8 5
|
||||
|
||||
create a ChAruco board pattern in charuco_board.svg with 7 rows, 5 columns, square size 30 mm, aruco
|
||||
marker size 15 mm and using DICT_5X5_100 as dictionary for aruco markers (it contains in DICT_ARUCO.json file):
|
||||
|
||||
python generate_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board --square_size 30 --marker_size 15 -f DICT_5X5_100.json.gz
|
||||
|
||||
If you want to change the measurement units, use the -u option (e.g. mm, inches, px, m)
|
||||
|
||||
If you want to change the page size, use the -w (width) and -h (height) options
|
||||
|
||||
If you want to use your own dictionary for the ChAruco board, specify the name of your dictionary
|
||||
file. For example:
|
||||
|
||||
python generate_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board -f my_dictionary.json
|
||||
|
||||
You can generate your dictionary in the file my_dictionary.json with 30 markers and a marker size of
|
||||
5 bits using the utility provided in `samples/cpp/aruco_dict_utils.cpp`.
|
||||
|
||||
bin/example_cpp_aruco_dict_utils.exe my_dict.json -nMarkers=30 -markerSize=5
|
||||
|
||||
Pattern Size
|
||||
------------
|
||||
|
||||
Pattern is defined by it's physical board size, element (square or circle) physical size and amount
|
||||
of elements. Factors that affect calibration quality:
|
||||
|
||||
- **Amount of features**. Most of OpenCV functions that work with detected patterns use optimization
|
||||
or some random consensus strategies inside. More features on board means more points for optimization
|
||||
and better estimation quality. Calibration process requires several images. It means that in most
|
||||
of cases lower amount of pattern features may be compensated by higher amount frames.
|
||||
|
||||
- **Element size**. The physical size of elements depends on the distance and size in pixels.
|
||||
Each detector defines some minimal size for reliable detection. For circles grid it's circle
|
||||
radius, for chessboard it's square size, for ChAruco board it's ArUco marker element size.
|
||||
General recommendation: larger elements (in frame pixels) reduces detection uncertainty.
|
||||
|
||||
- **Board size**. The board should be fully visible, sharp and reliably detected by OpenCV algorithms.
|
||||
So, the board size should satisfy previous items, if it's used with typical target distance.
|
||||
Usually larger board is better, but smaller boards allow to calibrate corners better.
|
||||
|
||||
Generic Recommendations
|
||||
-----------------------
|
||||
|
||||
1. The final pattern should be as flat as possible. It improves calibration accuracy.
|
||||
2. Glance pattern is worse than matte. Blinks and shadows on glance surface degrades board detection
|
||||
significantly.
|
||||
3. Most of detection algorithms expect white (black) border around the markers. Please do not cut
|
||||
them or cover them.
|
||||
@@ -0,0 +1,71 @@
|
||||
Camera calibration with square chessboard {#tutorial_camera_calibration_square_chess}
|
||||
=========================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_camera_calibration_pattern}
|
||||
@next_tutorial{tutorial_camera_calibration}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Victor Eruhimov |
|
||||
| Compatibility | OpenCV >= 4.0 |
|
||||
|
||||
|
||||
The goal of this tutorial is to learn how to calibrate a camera given a set of chessboard images.
|
||||
|
||||
*Test data*: use images in your data/chess folder.
|
||||
|
||||
- Compile OpenCV with samples by setting BUILD_EXAMPLES to ON in cmake configuration.
|
||||
|
||||
- Go to bin folder and use imagelist_creator to create an XML/YAML list of your images.
|
||||
|
||||
- Then, run calibration sample to get camera parameters. Use square size equal to 3cm.
|
||||
|
||||
Pose estimation
|
||||
---------------
|
||||
|
||||
Now, let us write code that detects a chessboard in an image and finds its distance from the
|
||||
camera. You can apply this method to any object with known 3D geometry; which you detect in an
|
||||
image.
|
||||
|
||||
*Test data*: use chess_test\*.jpg images from your data folder.
|
||||
|
||||
- Create an empty console project. Load a test image :
|
||||
|
||||
Mat img = imread(argv[1], IMREAD_GRAYSCALE);
|
||||
|
||||
- Detect a chessboard in this image using findChessboard function :
|
||||
|
||||
bool found = findChessboardCorners( img, boardSize, ptvec, CALIB_CB_ADAPTIVE_THRESH );
|
||||
|
||||
- Now, write a function that generates a vector\<Point3f\> array of 3d coordinates of a chessboard
|
||||
in any coordinate system. For simplicity, let us choose a system such that one of the chessboard
|
||||
corners is in the origin and the board is in the plane *z = 0*
|
||||
|
||||
- Read camera parameters from XML/YAML file :
|
||||
|
||||
FileStorage fs( filename, FileStorage::READ );
|
||||
Mat intrinsics, distortion;
|
||||
fs["camera_matrix"] >> intrinsics;
|
||||
fs["distortion_coefficients"] >> distortion;
|
||||
|
||||
- Now we are ready to find a chessboard pose by running \`solvePnP\` :
|
||||
|
||||
vector<Point3f> boardPoints;
|
||||
// fill the array
|
||||
...
|
||||
|
||||
solvePnP(Mat(boardPoints), Mat(foundBoardCorners), cameraMatrix,
|
||||
distCoeffs, rvec, tvec, false);
|
||||
|
||||
- Calculate reprojection error like it is done in calibration sample (see
|
||||
opencv/samples/cpp/calibration.cpp, function computeReprojectionErrors).
|
||||
|
||||
Question: how would you calculate distance from the camera origin to any one of the corners?
|
||||
Answer: After obtaining the camera pose using solvePnP, the rotation (rvec) and translation (tvec) vectors define the transformation between the world (chessboard) coordinates and the camera coordinate system. To calculate the distance from the camera’s origin to any chessboard corner, first transform the 3D point from the chessboard coordinate system to the camera coordinate system (if not already done) and then compute its Euclidean distance using the L2 norm, for example:
|
||||
|
||||
// assuming 'point' is the 3D position of a chessboard corner in the camera coordinate system
|
||||
double distance = norm(point);
|
||||
|
||||
This is equivalent to applying the L2 norm on the 3D point’s coordinates (x, y, z).
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 78 KiB |
@@ -0,0 +1,213 @@
|
||||
Interactive camera calibration application {#tutorial_interactive_calibration}
|
||||
==============================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_real_time_pose}
|
||||
@next_tutorial{tutorial_usac}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Vladislav Sovrasov |
|
||||
| Compatibility | OpenCV >= 3.1 |
|
||||
|
||||
|
||||
According to classical calibration technique user must collect all data first and then run @ref cv::calibrateCamera function
|
||||
to obtain camera parameters. If average re-projection error is huge or if estimated parameters seems to be wrong, process of
|
||||
selection or collecting data and starting of @ref cv::calibrateCamera repeats.
|
||||
|
||||
Interactive calibration process assumes that after each new data portion user can see results and errors estimation, also
|
||||
he can delete last data portion and finally, when dataset for calibration is big enough starts process of auto data selection.
|
||||
|
||||
Main application features
|
||||
------
|
||||
|
||||
The sample application will:
|
||||
|
||||
- Determine the distortion matrix and confidence interval for each element
|
||||
- Determine the camera matrix and confidence interval for each element
|
||||
- Take input from camera or video file
|
||||
- Read configuration from XML file
|
||||
- Save the results into XML file
|
||||
- Calculate re-projection error
|
||||
- Reject patterns views on sharp angles to prevent appear of ill-conditioned jacobian blocks
|
||||
- Auto switch calibration flags (fix aspect ratio and elements of distortion matrix if needed)
|
||||
- Auto detect when calibration is done by using several criteria
|
||||
- Auto capture of static patterns (user doesn't need press any keys to capture frame, just don't move pattern for a second)
|
||||
|
||||
Supported patterns:
|
||||
|
||||
- Black-white chessboard
|
||||
- Asymmetrical circle pattern
|
||||
- Dual asymmetrical circle pattern
|
||||
- chAruco (chessboard with Aruco markers)
|
||||
- Symmetrical circle pattern
|
||||
|
||||
Description of parameters
|
||||
------
|
||||
|
||||
Application has two groups of parameters: primary (passed through command line) and advances (passed through XML file).
|
||||
|
||||
### Primary parameters:
|
||||
|
||||
All of this parameters are passed to application through a command line.
|
||||
|
||||
-[parameter]=[default value]: description
|
||||
|
||||
- -v=[filename]: get video from filename, default input -- camera with id=0
|
||||
- -ci=[0]: get video from camera with specified id
|
||||
- -flip=[false]: vertical flip of input frames
|
||||
- -t=[circles]: pattern for calibration (circles, chessboard, dualCircles, chAruco, symcircles)
|
||||
- -sz=[16.3]: distance between two nearest centers of circles or squares on calibration board
|
||||
- -dst=[295] distance between white and black parts of dualCircles pattern
|
||||
- -w=[width]: width of pattern (in corners or circles)
|
||||
- -h=[height]: height of pattern (in corners or circles)
|
||||
- -of=[camParams.xml]: output file name
|
||||
- -ft=[true]: auto tuning of calibration flags
|
||||
- -vis=[grid]: captured boards visualization (grid, window)
|
||||
- -d=[0.8]: delay between captures in seconds
|
||||
- -pf=[defaultConfig.xml]: advanced application parameters file
|
||||
- -force_reopen=[false]: Forcefully reopen camera in case of errors. Can be helpful for ip cameras with unstable connection.
|
||||
- -save_frames=[false]: Save frames that contribute to final calibration
|
||||
- -zoom=[1]: Zoom factor applied to the preview image
|
||||
|
||||
### Advanced parameters:
|
||||
|
||||
By default values of advanced parameters are stored in defaultConfig.xml
|
||||
|
||||
@code{.xml}
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<charuco_dict>0</charuco_dict>
|
||||
<charuco_square_length>200</charuco_square_length>
|
||||
<charuco_marker_size>100</charuco_marker_size>
|
||||
<calibration_step>1</calibration_step>
|
||||
<max_frames_num>30</max_frames_num>
|
||||
<min_frames_num>10</min_frames_num>
|
||||
<solver_eps>1e-7</solver_eps>
|
||||
<solver_max_iters>30</solver_max_iters>
|
||||
<fast_solver>0</fast_solver>
|
||||
<frame_filter_conv_param>0.1</frame_filter_conv_param>
|
||||
<camera_resolution>1280 720</camera_resolution>
|
||||
</opencv_storage>
|
||||
@endcode
|
||||
|
||||
- *charuco_dict*: name of special dictionary, which has been used for generation of chAruco pattern
|
||||
- *charuco_square_length*: size of square on chAruco board (in pixels)
|
||||
- *charuco_marker_size*: size of Aruco markers on chAruco board (in pixels)
|
||||
- *calibration_step*: interval in frames between launches of @ref cv::calibrateCamera
|
||||
- *max_frames_num*: if number of frames for calibration is greater than this value frames filter starts working.
|
||||
After filtration size of calibration dataset is equals to *max_frames_num*
|
||||
- *min_frames_num*: if number of frames is greater than this value turns on auto flags tuning, undistorted view and quality evaluation
|
||||
- *solver_eps*: precision of Levenberg-Marquardt solver in @ref cv::calibrateCamera
|
||||
- *solver_max_iters*: iterations limit of solver
|
||||
- *fast_solver*: if this value is nonzero and Lapack is found QR decomposition is used instead of SVD in solver.
|
||||
QR faster than SVD, but potentially less precise
|
||||
- *frame_filter_conv_param*: parameter which used in linear convolution of bicriterial frames filter
|
||||
- *camera_resolution*: resolution of camera which is used for calibration
|
||||
|
||||
**Note:** *charuco_dict*, *charuco_square_length* and *charuco_marker_size* are used for chAruco pattern generation
|
||||
(see Aruco module description for details: [Aruco tutorials](https://github.com/opencv/opencv_contrib/tree/4.x/modules/aruco/tutorials))
|
||||
|
||||
Default chAruco pattern:
|
||||
|
||||

|
||||
|
||||
Dual circles pattern
|
||||
------
|
||||
|
||||
To make this pattern you need standard OpenCV circles pattern and binary inverted one.
|
||||
Place two patterns on one plane in order when all horizontal lines of circles in one pattern are
|
||||
continuations of similar lines in another.
|
||||
Measure distance between patterns as shown at picture below pass it as **dst** command line parameter. Also measure distance between centers of nearest circles and pass
|
||||
this value as **sz** command line parameter.
|
||||
|
||||

|
||||
|
||||
This pattern is very sensitive to quality of production and measurements.
|
||||
|
||||
|
||||
Data filtration
|
||||
------
|
||||
When size of calibration dataset is greater than *max_frames_num* starts working
|
||||
data filter. It tries to remove "bad" frames from dataset. Filter removes the frame
|
||||
on which \f$loss\_function\f$ takes maximum.
|
||||
|
||||
\f[loss\_function(i)=\alpha RMS(i)+(1-\alpha)reducedGridQuality(i)\f]
|
||||
|
||||
**RMS** is an average re-projection error calculated for frame *i*, **reducedGridQuality**
|
||||
is scene coverage quality evaluation without frame *i*. \f$\alpha\f$ is equals to
|
||||
**frame_filter_conv_param**.
|
||||
|
||||
|
||||
Calibration process
|
||||
------
|
||||
|
||||
To start calibration just run application. Place pattern ahead the camera and fixate pattern in some pose.
|
||||
After that wait for capturing (will be shown message like "Frame #i captured").
|
||||
Current focal distance and re-projection error will be shown at the main screen. Move pattern to the next position and repeat procedure. Try to cover image plane
|
||||
uniformly and don't show pattern on sharp angles to the image plane.
|
||||
|
||||

|
||||
|
||||
If calibration seems to be successful (confidence intervals and average re-projection
|
||||
error are small, frame coverage quality and number of pattern views are big enough)
|
||||
application will show a message like on screen below.
|
||||
|
||||
|
||||

|
||||
|
||||
Hot keys:
|
||||
|
||||
- Esc -- exit application
|
||||
- s -- save current data to XML file
|
||||
- r -- delete last frame
|
||||
- d -- delete all frames
|
||||
- u -- enable/disable applying of undistortion
|
||||
- v -- switch visualization mode
|
||||
|
||||
Results
|
||||
------
|
||||
|
||||
As result you will get camera parameters and confidence intervals for them.
|
||||
|
||||
Example of output XML file:
|
||||
|
||||
@code{.xml}
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<calibrationDate>"Thu 07 Apr 2016 04:23:03 PM MSK"</calibrationDate>
|
||||
<framesCount>21</framesCount>
|
||||
<cameraResolution>
|
||||
1280 720</cameraResolution>
|
||||
<camera_matrix type_id="opencv-matrix">
|
||||
<rows>3</rows>
|
||||
<cols>3</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
1.2519588293098975e+03 0. 6.6684948780852471e+02 0.
|
||||
1.2519588293098975e+03 3.6298123112613683e+02 0. 0. 1.</data></camera_matrix>
|
||||
<camera_matrix_std_dev type_id="opencv-matrix">
|
||||
<rows>4</rows>
|
||||
<cols>1</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
0. 1.2887048808572649e+01 2.8536856683866230e+00
|
||||
2.8341737483430314e+00</data></camera_matrix_std_dev>
|
||||
<distortion_coefficients type_id="opencv-matrix">
|
||||
<rows>1</rows>
|
||||
<cols>5</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
1.3569117181595716e-01 -8.2513063822554633e-01 0. 0.
|
||||
1.6412101575010554e+00</data></distortion_coefficients>
|
||||
<distortion_coefficients_std_dev type_id="opencv-matrix">
|
||||
<rows>5</rows>
|
||||
<cols>1</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
1.5570675523402111e-02 8.7229075437543435e-02 0. 0.
|
||||
1.8382427901856876e-01</data></distortion_coefficients_std_dev>
|
||||
<avg_reprojection_error>4.2691743074130178e-01</avg_reprojection_error>
|
||||
</opencv_storage>
|
||||
@endcode
|
||||
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 106 KiB |
@@ -0,0 +1,805 @@
|
||||
Real Time pose estimation of a textured object {#tutorial_real_time_pose}
|
||||
==============================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_camera_calibration}
|
||||
@next_tutorial{tutorial_interactive_calibration}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Edgar Riba |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
|
||||
Nowadays, augmented reality is one of the top research topic in computer vision and robotics fields.
|
||||
The most elemental problem in augmented reality is the estimation of the camera pose respect of an
|
||||
object in the case of computer vision area to perform subsequent 3D rendering or, in robotics, to obtain an object pose for grasping and manipulation. However, this is not a trivial
|
||||
problem to solve due to the fact that the most common issue in image processing is the computational
|
||||
cost of applying a lot of algorithms or mathematical operations for solving a problem which is basic
|
||||
and immediately for humans.
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
This tutorial explains how to build a real-time application to estimate the camera pose in
|
||||
order to track a textured object with six degrees of freedom given a 2D image and its 3D textured
|
||||
model.
|
||||
|
||||
The application will have the following parts:
|
||||
|
||||
- Read 3D textured object model and object mesh.
|
||||
- Take input from Camera or Video.
|
||||
- Extract ORB features and descriptors from the scene.
|
||||
- Match scene descriptors with model descriptors using Flann matcher.
|
||||
- Pose estimation using PnP + Ransac.
|
||||
- Linear Kalman Filter for bad poses rejection.
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
In computer vision estimate the camera pose from *n* 3D-to-2D point correspondences is a fundamental
|
||||
and well understood problem. The most general version of the problem requires estimating the six
|
||||
degrees of freedom of the pose and five calibration parameters: focal length, principal point,
|
||||
aspect ratio and skew. It could be established with a minimum of 6 correspondences, using the well
|
||||
known Direct Linear Transform (DLT) algorithm. There are, though, several simplifications to the
|
||||
problem which turn into an extensive list of different algorithms that improve the accuracy of the
|
||||
DLT.
|
||||
|
||||
The most common simplification is to assume known calibration parameters which is the so-called
|
||||
Perspective-*n*-Point problem:
|
||||
|
||||

|
||||
|
||||
**Problem Formulation:** Given a set of correspondences between 3D points \f$p_i\f$ expressed in a world
|
||||
reference frame, and their 2D projections \f$u_i\f$ onto the image, we seek to retrieve the pose (\f$R\f$
|
||||
and \f$t\f$) of the camera w.r.t. the world and the focal length \f$f\f$.
|
||||
|
||||
OpenCV provides four different approaches to solve the Perspective-*n*-Point problem which return
|
||||
\f$R\f$ and \f$t\f$. Then, using the following formula it's possible to project 3D points into the image
|
||||
plane:
|
||||
|
||||
\f[s\ \left [ \begin{matrix} u \\ v \\ 1 \end{matrix} \right ] = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ] \left [ \begin{matrix} r_{11} & r_{12} & r_{13} & t_1 \\ r_{21} & r_{22} & r_{23} & t_2 \\ r_{31} & r_{32} & r_{33} & t_3 \end{matrix} \right ] \left [ \begin{matrix} X \\ Y \\ Z\\ 1 \end{matrix} \right ]\f]
|
||||
|
||||
The complete documentation of how to manage with this equations is in @ref calib3d .
|
||||
|
||||
Source code
|
||||
-----------
|
||||
|
||||
You can find the source code of this tutorial in the
|
||||
`samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/` folder of the OpenCV source library.
|
||||
|
||||
The tutorial consists of two main programs:
|
||||
|
||||
-# **Model registration**
|
||||
|
||||
This application is intended for users who do not have a 3D textured model of the object to be detected.
|
||||
You can use this program to create your own textured 3D model. This program only works for planar
|
||||
objects, then if you want to model an object with complex shape you should use a sophisticated
|
||||
software to create it.
|
||||
|
||||
The application needs an input image of the object to be registered and its 3D mesh. We have also
|
||||
to provide the intrinsic parameters of the camera with which the input image was taken. All the
|
||||
files need to be specified using the absolute path or the relative one from your application’s
|
||||
working directory. If no files are specified the program will try to open the provided default
|
||||
parameters.
|
||||
|
||||
The application starts up extracting the ORB features and descriptors from the input image and
|
||||
then uses the mesh along with the [Möller–Trumbore intersection
|
||||
algorithm](http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm/)
|
||||
to compute the 3D coordinates of the found features. Finally, the 3D points and the descriptors
|
||||
are stored in different lists in a file with YAML format which each row is a different point. The
|
||||
technical background on how to store the files can be found in the @ref tutorial_file_input_output_with_xml_yml
|
||||
tutorial.
|
||||
|
||||

|
||||
|
||||
-# **Model detection**
|
||||
|
||||
The aim of this application is to estimate in real time the object pose given its 3D textured model.
|
||||
|
||||
The application starts up loading the 3D textured model in YAML file format with the same
|
||||
structure explained in the model registration program. From the scene, the ORB features and
|
||||
descriptors are detected and extracted. Then, is used @ref cv::FlannBasedMatcher with
|
||||
@ref cv::flann::GenericIndex to do the matching between the scene descriptors and the model descriptors.
|
||||
Using the found matches along with @ref cv::solvePnPRansac function the `R` and `t` of
|
||||
the camera are computed. Finally, a KalmanFilter is applied in order to reject bad poses.
|
||||
|
||||
In the case that you compiled OpenCV with the samples, you can find it in `opencv/build/bin/cpp-tutorial-pnp_detection`.
|
||||
Then you can run the application and change some parameters:
|
||||
@code{.cpp}
|
||||
This program shows how to detect an object given its 3D textured model. You can choose to use a recorded video or the webcam.
|
||||
Usage:
|
||||
./cpp-tutorial-pnp_detection -help
|
||||
Keys:
|
||||
'esc' - to quit.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
Usage: cpp-tutorial-pnp_detection [params]
|
||||
|
||||
-c, --confidence (value:0.95)
|
||||
RANSAC confidence
|
||||
-e, --error (value:2.0)
|
||||
RANSAC reprojection error
|
||||
-f, --fast (value:true)
|
||||
use of robust fast match
|
||||
-h, --help (value:true)
|
||||
print this message
|
||||
--in, --inliers (value:30)
|
||||
minimum inliers for Kalman update
|
||||
--it, --iterations (value:500)
|
||||
RANSAC maximum iterations count
|
||||
-k, --keypoints (value:2000)
|
||||
number of keypoints to detect
|
||||
--mesh
|
||||
path to ply mesh
|
||||
--method, --pnp (value:0)
|
||||
PnP method: (0) ITERATIVE - (1) EPNP - (2) P3P - (3) DLS
|
||||
--model
|
||||
path to yml model
|
||||
-r, --ratio (value:0.7)
|
||||
threshold for ratio test
|
||||
-v, --video
|
||||
path to recorded video
|
||||
@endcode
|
||||
For example, you can run the application changing the pnp method:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --method=2
|
||||
@endcode
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
Here is explained in detail the code for the real time application:
|
||||
|
||||
-# **Read 3D textured object model and object mesh.**
|
||||
|
||||
In order to load the textured model I implemented the *class* **Model** which has the function
|
||||
*load()* that opens a YAML file and take the stored 3D points with its corresponding descriptors.
|
||||
You can find an example of a 3D textured model in
|
||||
`samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/cookies_ORB.yml`.
|
||||
@code{.cpp}
|
||||
/* Load a YAML file using OpenCV */
|
||||
void Model::load(const std::string path)
|
||||
{
|
||||
cv::Mat points3d_mat;
|
||||
|
||||
cv::FileStorage storage(path, cv::FileStorage::READ);
|
||||
storage["points_3d"] >> points3d_mat;
|
||||
storage["descriptors"] >> descriptors_;
|
||||
|
||||
points3d_mat.copyTo(list_points3d_in_);
|
||||
|
||||
storage.release();
|
||||
|
||||
}
|
||||
@endcode
|
||||
In the main program the model is loaded as follows:
|
||||
@code{.cpp}
|
||||
Model model; // instantiate Model object
|
||||
model.load(yml_read_path); // load a 3D textured object model
|
||||
@endcode
|
||||
In order to read the model mesh I implemented a *class* **Mesh** which has a function *load()*
|
||||
that opens a \f$*\f$.ply file and store the 3D points of the object and also the composed triangles.
|
||||
You can find an example of a model mesh in
|
||||
`samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.ply`.
|
||||
@code{.cpp}
|
||||
/* Load a CSV with *.ply format */
|
||||
void Mesh::load(const std::string path)
|
||||
{
|
||||
|
||||
// Create the reader
|
||||
CsvReader csvReader(path);
|
||||
|
||||
// Clear previous data
|
||||
list_vertex_.clear();
|
||||
list_triangles_.clear();
|
||||
|
||||
// Read from .ply file
|
||||
csvReader.readPLY(list_vertex_, list_triangles_);
|
||||
|
||||
// Update mesh attributes
|
||||
num_vertexs_ = list_vertex_.size();
|
||||
num_triangles_ = list_triangles_.size();
|
||||
|
||||
}
|
||||
@endcode
|
||||
In the main program the mesh is loaded as follows:
|
||||
@code{.cpp}
|
||||
Mesh mesh; // instantiate Mesh object
|
||||
mesh.load(ply_read_path); // load an object mesh
|
||||
@endcode
|
||||
You can also load different model and mesh:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --mesh=/absolute_path_to_your_mesh.ply --model=/absolute_path_to_your_model.yml
|
||||
@endcode
|
||||
|
||||
-# **Take input from Camera or Video**
|
||||
|
||||
To detect is necessary capture video. It's done loading a recorded video by passing the absolute
|
||||
path where it is located in your machine. In order to test the application you can find a recorded
|
||||
video in `samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.mp4`.
|
||||
@code{.cpp}
|
||||
cv::VideoCapture cap; // instantiate VideoCapture
|
||||
cap.open(video_read_path); // open a recorded video
|
||||
|
||||
if(!cap.isOpened()) // check if we succeeded
|
||||
{
|
||||
std::cout << "Could not open the camera device" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
@endcode
|
||||
Then the algorithm is computed frame per frame:
|
||||
@code{.cpp}
|
||||
cv::Mat frame, frame_vis;
|
||||
|
||||
while(cap.read(frame) && cv::waitKey(30) != 27) // capture frame until ESC is pressed
|
||||
{
|
||||
|
||||
frame_vis = frame.clone(); // refresh visualisation frame
|
||||
|
||||
// MAIN ALGORITHM
|
||||
|
||||
}
|
||||
@endcode
|
||||
You can also load different recorded video:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --video=/absolute_path_to_your_video.mp4
|
||||
@endcode
|
||||
|
||||
-# **Extract ORB features and descriptors from the scene**
|
||||
|
||||
The next step is to detect the scene features and extract it descriptors. For this task I
|
||||
implemented a *class* **RobustMatcher** which has a function for keypoints detection and features
|
||||
extraction. You can find it in
|
||||
`samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/src/RobustMatcher.cpp`. In your
|
||||
*RobustMatch* object you can use any of the 2D features detectors of OpenCV. In this case I used
|
||||
@ref cv::ORB features because is based on @ref cv::FAST to detect the keypoints and cv::xfeatures2d::BriefDescriptorExtractor
|
||||
to extract the descriptors which means that is fast and robust to rotations. You can find more
|
||||
detailed information about *ORB* in the documentation.
|
||||
|
||||
The following code is how to instantiate and set the features detector and the descriptors
|
||||
extractor:
|
||||
@code{.cpp}
|
||||
RobustMatcher rmatcher; // instantiate RobustMatcher
|
||||
|
||||
cv::FeatureDetector * detector = new cv::OrbFeatureDetector(numKeyPoints); // instantiate ORB feature detector
|
||||
cv::DescriptorExtractor * extractor = new cv::OrbDescriptorExtractor(); // instantiate ORB descriptor extractor
|
||||
|
||||
rmatcher.setFeatureDetector(detector); // set feature detector
|
||||
rmatcher.setDescriptorExtractor(extractor); // set descriptor extractor
|
||||
@endcode
|
||||
The features and descriptors will be computed by the *RobustMatcher* inside the matching function.
|
||||
|
||||
-# **Match scene descriptors with model descriptors using Flann matcher**
|
||||
|
||||
It is the first step in our detection algorithm. The main idea is to match the scene descriptors
|
||||
with our model descriptors in order to know the 3D coordinates of the found features into the
|
||||
current scene.
|
||||
|
||||
Firstly, we have to set which matcher we want to use. In this case is used
|
||||
@ref cv::FlannBasedMatcher matcher which in terms of computational cost is faster than the
|
||||
@ref cv::BFMatcher matcher as we increase the trained collection of features. Then, for
|
||||
FlannBased matcher the index created is *Multi-Probe LSH: Efficient Indexing for High-Dimensional
|
||||
Similarity Search* due to *ORB* descriptors are binary.
|
||||
|
||||
You can tune the *LSH* and search parameters to improve the matching efficiency:
|
||||
@code{.cpp}
|
||||
cv::Ptr<cv::flann::IndexParams> indexParams = cv::makePtr<cv::flann::LshIndexParams>(6, 12, 1); // instantiate LSH index parameters
|
||||
cv::Ptr<cv::flann::SearchParams> searchParams = cv::makePtr<cv::flann::SearchParams>(50); // instantiate flann search parameters
|
||||
|
||||
cv::DescriptorMatcher * matcher = new cv::FlannBasedMatcher(indexParams, searchParams); // instantiate FlannBased matcher
|
||||
rmatcher.setDescriptorMatcher(matcher); // set matcher
|
||||
@endcode
|
||||
Secondly, we have to call the matcher by using *robustMatch()* or *fastRobustMatch()* function.
|
||||
The difference of using this two functions is its computational cost. The first method is slower
|
||||
but more robust at filtering good matches because uses two ratio test and a symmetry test. In
|
||||
contrast, the second method is faster but less robust because only applies a single ratio test to
|
||||
the matches.
|
||||
|
||||
The following code is to get the model 3D points and its descriptors and then call the matcher in
|
||||
the main program:
|
||||
@code{.cpp}
|
||||
// Get the MODEL INFO
|
||||
|
||||
std::vector<cv::Point3f> list_points3d_model = model.get_points3d(); // list with model 3D coordinates
|
||||
cv::Mat descriptors_model = model.get_descriptors(); // list with descriptors of each 3D coordinate
|
||||
@endcode
|
||||
@code{.cpp}
|
||||
// -- Step 1: Robust matching between model descriptors and scene descriptors
|
||||
|
||||
std::vector<cv::DMatch> good_matches; // to obtain the model 3D points in the scene
|
||||
std::vector<cv::KeyPoint> keypoints_scene; // to obtain the 2D points of the scene
|
||||
|
||||
if(fast_match)
|
||||
{
|
||||
rmatcher.fastRobustMatch(frame, good_matches, keypoints_scene, descriptors_model);
|
||||
}
|
||||
else
|
||||
{
|
||||
rmatcher.robustMatch(frame, good_matches, keypoints_scene, descriptors_model);
|
||||
}
|
||||
@endcode
|
||||
The following code corresponds to the *robustMatch()* function which belongs to the
|
||||
*RobustMatcher* class. This function uses the given image to detect the keypoints and extract the
|
||||
descriptors, match using *two Nearest Neighbour* the extracted descriptors with the given model
|
||||
descriptors and vice versa. Then, a ratio test is applied to the two direction matches in order to
|
||||
remove these matches which its distance ratio between the first and second best match is larger
|
||||
than a given threshold. Finally, a symmetry test is applied in order to remove non symmetrical
|
||||
matches.
|
||||
@code{.cpp}
|
||||
void RobustMatcher::robustMatch( const cv::Mat& frame, std::vector<cv::DMatch>& good_matches,
|
||||
std::vector<cv::KeyPoint>& keypoints_frame,
|
||||
const std::vector<cv::KeyPoint>& keypoints_model, const cv::Mat& descriptors_model )
|
||||
{
|
||||
|
||||
// 1a. Detection of the ORB features
|
||||
this->computeKeyPoints(frame, keypoints_frame);
|
||||
|
||||
// 1b. Extraction of the ORB descriptors
|
||||
cv::Mat descriptors_frame;
|
||||
this->computeDescriptors(frame, keypoints_frame, descriptors_frame);
|
||||
|
||||
// 2. Match the two image descriptors
|
||||
std::vector<std::vector<cv::DMatch> > matches12, matches21;
|
||||
|
||||
// 2a. From image 1 to image 2
|
||||
matcher_->knnMatch(descriptors_frame, descriptors_model, matches12, 2); // return 2 nearest neighbours
|
||||
|
||||
// 2b. From image 2 to image 1
|
||||
matcher_->knnMatch(descriptors_model, descriptors_frame, matches21, 2); // return 2 nearest neighbours
|
||||
|
||||
// 3. Remove matches for which NN ratio is > than threshold
|
||||
// clean image 1 -> image 2 matches
|
||||
int removed1 = ratioTest(matches12);
|
||||
// clean image 2 -> image 1 matches
|
||||
int removed2 = ratioTest(matches21);
|
||||
|
||||
// 4. Remove non-symmetrical matches
|
||||
symmetryTest(matches12, matches21, good_matches);
|
||||
|
||||
}
|
||||
@endcode
|
||||
After the matches filtering we have to subtract the 2D and 3D correspondences from the found scene
|
||||
keypoints and our 3D model using the obtained *DMatches* vector. For more information about
|
||||
@ref cv::DMatch check the documentation.
|
||||
@code{.cpp}
|
||||
// -- Step 2: Find out the 2D/3D correspondences
|
||||
|
||||
std::vector<cv::Point3f> list_points3d_model_match; // container for the model 3D coordinates found in the scene
|
||||
std::vector<cv::Point2f> list_points2d_scene_match; // container for the model 2D coordinates found in the scene
|
||||
|
||||
for(unsigned int match_index = 0; match_index < good_matches.size(); ++match_index)
|
||||
{
|
||||
cv::Point3f point3d_model = list_points3d_model[ good_matches[match_index].trainIdx ]; // 3D point from model
|
||||
cv::Point2f point2d_scene = keypoints_scene[ good_matches[match_index].queryIdx ].pt; // 2D point from the scene
|
||||
list_points3d_model_match.push_back(point3d_model); // add 3D point
|
||||
list_points2d_scene_match.push_back(point2d_scene); // add 2D point
|
||||
}
|
||||
@endcode
|
||||
You can also change the ratio test threshold, the number of keypoints to detect as well as use or
|
||||
not the robust matcher:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --ratio=0.8 --keypoints=1000 --fast=false
|
||||
@endcode
|
||||
|
||||
-# **Pose estimation using PnP + Ransac**
|
||||
|
||||
Once with the 2D and 3D correspondences we have to apply a PnP algorithm in order to estimate the
|
||||
camera pose. The reason why we have to use @ref cv::solvePnPRansac instead of @ref cv::solvePnP is
|
||||
due to the fact that after the matching not all the found correspondences are correct and, as like
|
||||
as not, there are false correspondences or also called *outliers*. The [Random Sample
|
||||
Consensus](http://en.wikipedia.org/wiki/RANSAC) or *Ransac* is a non-deterministic iterative
|
||||
method which estimate parameters of a mathematical model from observed data producing an
|
||||
approximate result as the number of iterations increase. After applying *Ransac* all the *outliers*
|
||||
will be eliminated to then estimate the camera pose with a certain probability to obtain a good
|
||||
solution.
|
||||
|
||||
For the camera pose estimation I have implemented a *class* **PnPProblem**. This *class* has 4
|
||||
attributes: a given calibration matrix, the rotation matrix, the translation matrix and the
|
||||
rotation-translation matrix. The intrinsic calibration parameters of the camera which you are
|
||||
using to estimate the pose are necessary. In order to obtain the parameters you can check
|
||||
@ref tutorial_camera_calibration_square_chess and @ref tutorial_camera_calibration tutorials.
|
||||
|
||||
The following code is how to declare the *PnPProblem class* in the main program:
|
||||
@code{.cpp}
|
||||
// Intrinsic camera parameters: UVC WEBCAM
|
||||
|
||||
double f = 55; // focal length in mm
|
||||
double sx = 22.3, sy = 14.9; // sensor size
|
||||
double width = 640, height = 480; // image size
|
||||
|
||||
double params_WEBCAM[] = { width*f/sx, // fx
|
||||
height*f/sy, // fy
|
||||
width/2, // cx
|
||||
height/2}; // cy
|
||||
|
||||
PnPProblem pnp_detection(params_WEBCAM); // instantiate PnPProblem class
|
||||
@endcode
|
||||
The following code is how the *PnPProblem class* initialises its attributes:
|
||||
@code{.cpp}
|
||||
// Custom constructor given the intrinsic camera parameters
|
||||
|
||||
PnPProblem::PnPProblem(const double params[])
|
||||
{
|
||||
_A_matrix = cv::Mat::zeros(3, 3, CV_64FC1); // intrinsic camera parameters
|
||||
_A_matrix.at<double>(0, 0) = params[0]; // [ fx 0 cx ]
|
||||
_A_matrix.at<double>(1, 1) = params[1]; // [ 0 fy cy ]
|
||||
_A_matrix.at<double>(0, 2) = params[2]; // [ 0 0 1 ]
|
||||
_A_matrix.at<double>(1, 2) = params[3];
|
||||
_A_matrix.at<double>(2, 2) = 1;
|
||||
_R_matrix = cv::Mat::zeros(3, 3, CV_64FC1); // rotation matrix
|
||||
_t_matrix = cv::Mat::zeros(3, 1, CV_64FC1); // translation matrix
|
||||
_P_matrix = cv::Mat::zeros(3, 4, CV_64FC1); // rotation-translation matrix
|
||||
|
||||
}
|
||||
@endcode
|
||||
OpenCV provides four PnP methods: ITERATIVE, EPNP, P3P and DLS. Depending on the application type,
|
||||
the estimation method will be different. In the case that we want to make a real time application,
|
||||
the more suitable methods are EPNP and P3P since they are faster than ITERATIVE and DLS at
|
||||
finding an optimal solution. However, EPNP and P3P are not especially robust in front of planar
|
||||
surfaces and sometimes the pose estimation seems to have a mirror effect. Therefore, in this
|
||||
tutorial an ITERATIVE method is used due to the object to be detected has planar surfaces.
|
||||
|
||||
The OpenCV RANSAC implementation wants you to provide three parameters: 1) the maximum number of
|
||||
iterations until the algorithm stops, 2) the maximum allowed distance between the observed and
|
||||
computed point projections to consider it an inlier and 3) the confidence to obtain a good result.
|
||||
You can tune these parameters in order to improve your algorithm performance. Increasing the
|
||||
number of iterations will have a more accurate solution, but will take more time to find a
|
||||
solution. Increasing the reprojection error will reduce the computation time, but your solution
|
||||
will be unaccurate. Decreasing the confidence your algorithm will be faster, but the obtained
|
||||
solution will be unaccurate.
|
||||
|
||||
The following parameters work for this application:
|
||||
@code{.cpp}
|
||||
// RANSAC parameters
|
||||
|
||||
int iterationsCount = 500; // number of Ransac iterations.
|
||||
float reprojectionError = 2.0; // maximum allowed distance to consider it an inlier.
|
||||
float confidence = 0.95; // RANSAC successful confidence.
|
||||
@endcode
|
||||
The following code corresponds to the *estimatePoseRANSAC()* function which belongs to the
|
||||
*PnPProblem class*. This function estimates the rotation and translation matrix given a set of
|
||||
2D/3D correspondences, the desired PnP method to use, the output inliers container and the Ransac
|
||||
parameters:
|
||||
@code{.cpp}
|
||||
// Estimate the pose given a list of 2D/3D correspondences with RANSAC and the method to use
|
||||
|
||||
void PnPProblem::estimatePoseRANSAC( const std::vector<cv::Point3f> &list_points3d, // list with model 3D coordinates
|
||||
const std::vector<cv::Point2f> &list_points2d, // list with scene 2D coordinates
|
||||
int flags, cv::Mat &inliers, int iterationsCount, // PnP method; inliers container
|
||||
float reprojectionError, float confidence ) // RANSAC parameters
|
||||
{
|
||||
cv::Mat distCoeffs = cv::Mat::zeros(4, 1, CV_64FC1); // vector of distortion coefficients
|
||||
cv::Mat rvec = cv::Mat::zeros(3, 1, CV_64FC1); // output rotation vector
|
||||
cv::Mat tvec = cv::Mat::zeros(3, 1, CV_64FC1); // output translation vector
|
||||
|
||||
bool useExtrinsicGuess = false; // if true the function uses the provided rvec and tvec values as
|
||||
// initial approximations of the rotation and translation vectors
|
||||
|
||||
cv::solvePnPRansac( list_points3d, list_points2d, _A_matrix, distCoeffs, rvec, tvec,
|
||||
useExtrinsicGuess, iterationsCount, reprojectionError, confidence,
|
||||
inliers, flags );
|
||||
|
||||
Rodrigues(rvec,_R_matrix); // converts Rotation Vector to Matrix
|
||||
_t_matrix = tvec; // set translation matrix
|
||||
|
||||
this->set_P_matrix(_R_matrix, _t_matrix); // set rotation-translation matrix
|
||||
|
||||
}
|
||||
@endcode
|
||||
In the following code are the 3rd and 4th steps of the main algorithm. The first, calling the
|
||||
above function and the second taking the output inliers vector from RANSAC to get the 2D scene
|
||||
points for drawing purpose. As seen in the code we must be sure to apply RANSAC if we have
|
||||
matches, in the other case, the function @ref cv::solvePnPRansac crashes due to any OpenCV *bug*.
|
||||
@code{.cpp}
|
||||
if(good_matches.size() > 0) // None matches, then RANSAC crashes
|
||||
{
|
||||
|
||||
// -- Step 3: Estimate the pose using RANSAC approach
|
||||
pnp_detection.estimatePoseRANSAC( list_points3d_model_match, list_points2d_scene_match,
|
||||
pnpMethod, inliers_idx, iterationsCount, reprojectionError, confidence );
|
||||
|
||||
|
||||
// -- Step 4: Catch the inliers keypoints to draw
|
||||
for(int inliers_index = 0; inliers_index < inliers_idx.rows; ++inliers_index)
|
||||
{
|
||||
int n = inliers_idx.at<int>(inliers_index); // i-inlier
|
||||
cv::Point2f point2d = list_points2d_scene_match[n]; // i-inlier point 2D
|
||||
list_points2d_inliers.push_back(point2d); // add i-inlier to list
|
||||
}
|
||||
@endcode
|
||||
Finally, once the camera pose has been estimated we can use the \f$R\f$ and \f$t\f$ in order to compute
|
||||
the 2D projection onto the image of a given 3D point expressed in a world reference frame using
|
||||
the showed formula on *Theory*.
|
||||
|
||||
The following code corresponds to the *backproject3DPoint()* function which belongs to the
|
||||
*PnPProblem class*. The function backproject a given 3D point expressed in a world reference frame
|
||||
onto a 2D image:
|
||||
@code{.cpp}
|
||||
// Backproject a 3D point to 2D using the estimated pose parameters
|
||||
|
||||
cv::Point2f PnPProblem::backproject3DPoint(const cv::Point3f &point3d)
|
||||
{
|
||||
// 3D point vector [x y z 1]'
|
||||
cv::Mat point3d_vec = cv::Mat(4, 1, CV_64FC1);
|
||||
point3d_vec.at<double>(0) = point3d.x;
|
||||
point3d_vec.at<double>(1) = point3d.y;
|
||||
point3d_vec.at<double>(2) = point3d.z;
|
||||
point3d_vec.at<double>(3) = 1;
|
||||
|
||||
// 2D point vector [u v 1]'
|
||||
cv::Mat point2d_vec = cv::Mat(4, 1, CV_64FC1);
|
||||
point2d_vec = _A_matrix * _P_matrix * point3d_vec;
|
||||
|
||||
// Normalization of [u v]'
|
||||
cv::Point2f point2d;
|
||||
point2d.x = point2d_vec.at<double>(0) / point2d_vec.at<double>(2);
|
||||
point2d.y = point2d_vec.at<double>(1) / point2d_vec.at<double>(2);
|
||||
|
||||
return point2d;
|
||||
}
|
||||
@endcode
|
||||
The above function is used to compute all the 3D points of the object *Mesh* to show the pose of
|
||||
the object.
|
||||
|
||||
You can also change RANSAC parameters and PnP method:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --error=0.25 --confidence=0.90 --iterations=250 --method=3
|
||||
@endcode
|
||||
|
||||
-# **Linear Kalman Filter for bad poses rejection**
|
||||
|
||||
Is it common in computer vision or robotics fields that after applying detection or tracking
|
||||
techniques, bad results are obtained due to some sensor errors. In order to avoid these bad
|
||||
detections in this tutorial is explained how to implement a Linear Kalman Filter. The Kalman
|
||||
Filter will be applied after detected a given number of inliers.
|
||||
|
||||
You can find more information about what [Kalman
|
||||
Filter](http://en.wikipedia.org/wiki/Kalman_filter) is. In this tutorial it's used the OpenCV
|
||||
implementation of the @ref cv::KalmanFilter based on
|
||||
[Linear Kalman Filter for position and orientation tracking](http://campar.in.tum.de/Chair/KalmanFilter)
|
||||
to set the dynamics and measurement models.
|
||||
|
||||
Firstly, we have to define our state vector which will have 18 states: the positional data (x,y,z)
|
||||
with its first and second derivatives (velocity and acceleration), then rotation is added in form
|
||||
of three euler angles (roll, pitch, jaw) together with their first and second derivatives (angular
|
||||
velocity and acceleration)
|
||||
|
||||
\f[X = (x,y,z,\dot x,\dot y,\dot z,\ddot x,\ddot y,\ddot z,\psi,\theta,\phi,\dot \psi,\dot \theta,\dot \phi,\ddot \psi,\ddot \theta,\ddot \phi)^T\f]
|
||||
|
||||
Secondly, we have to define the number of measurements which will be 6: from \f$R\f$ and \f$t\f$ we can
|
||||
extract \f$(x,y,z)\f$ and \f$(\psi,\theta,\phi)\f$. In addition, we have to define the number of control
|
||||
actions to apply to the system which in this case will be *zero*. Finally, we have to define the
|
||||
differential time between measurements which in this case is \f$1/T\f$, where *T* is the frame rate of
|
||||
the video.
|
||||
@code{.cpp}
|
||||
cv::KalmanFilter KF; // instantiate Kalman Filter
|
||||
|
||||
int nStates = 18; // the number of states
|
||||
int nMeasurements = 6; // the number of measured states
|
||||
int nInputs = 0; // the number of action control
|
||||
|
||||
double dt = 0.125; // time between measurements (1/FPS)
|
||||
|
||||
initKalmanFilter(KF, nStates, nMeasurements, nInputs, dt); // init function
|
||||
@endcode
|
||||
The following code corresponds to the *Kalman Filter* initialisation. Firstly, is set the process
|
||||
noise, the measurement noise and the error covariance matrix. Secondly, are set the transition
|
||||
matrix which is the dynamic model and finally the measurement matrix, which is the measurement
|
||||
model.
|
||||
|
||||
You can tune the process and measurement noise to improve the *Kalman Filter* performance. As the
|
||||
measurement noise is reduced the faster will converge doing the algorithm sensitive in front of
|
||||
bad measurements.
|
||||
@code{.cpp}
|
||||
void initKalmanFilter(cv::KalmanFilter &KF, int nStates, int nMeasurements, int nInputs, double dt)
|
||||
{
|
||||
|
||||
KF.init(nStates, nMeasurements, nInputs, CV_64F); // init Kalman Filter
|
||||
|
||||
cv::setIdentity(KF.processNoiseCov, cv::Scalar::all(1e-5)); // set process noise
|
||||
cv::setIdentity(KF.measurementNoiseCov, cv::Scalar::all(1e-4)); // set measurement noise
|
||||
cv::setIdentity(KF.errorCovPost, cv::Scalar::all(1)); // error covariance
|
||||
|
||||
|
||||
/* DYNAMIC MODEL */
|
||||
|
||||
// [1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 1 0 0 dt 0 0 dt2 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 1 0 0 dt 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0 dt2]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 dt]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
|
||||
|
||||
// position
|
||||
KF.transitionMatrix.at<double>(0,3) = dt;
|
||||
KF.transitionMatrix.at<double>(1,4) = dt;
|
||||
KF.transitionMatrix.at<double>(2,5) = dt;
|
||||
KF.transitionMatrix.at<double>(3,6) = dt;
|
||||
KF.transitionMatrix.at<double>(4,7) = dt;
|
||||
KF.transitionMatrix.at<double>(5,8) = dt;
|
||||
KF.transitionMatrix.at<double>(0,6) = 0.5*std::pow(dt,2);
|
||||
KF.transitionMatrix.at<double>(1,7) = 0.5*std::pow(dt,2);
|
||||
KF.transitionMatrix.at<double>(2,8) = 0.5*std::pow(dt,2);
|
||||
|
||||
// orientation
|
||||
KF.transitionMatrix.at<double>(9,12) = dt;
|
||||
KF.transitionMatrix.at<double>(10,13) = dt;
|
||||
KF.transitionMatrix.at<double>(11,14) = dt;
|
||||
KF.transitionMatrix.at<double>(12,15) = dt;
|
||||
KF.transitionMatrix.at<double>(13,16) = dt;
|
||||
KF.transitionMatrix.at<double>(14,17) = dt;
|
||||
KF.transitionMatrix.at<double>(9,15) = 0.5*std::pow(dt,2);
|
||||
KF.transitionMatrix.at<double>(10,16) = 0.5*std::pow(dt,2);
|
||||
KF.transitionMatrix.at<double>(11,17) = 0.5*std::pow(dt,2);
|
||||
|
||||
|
||||
/* MEASUREMENT MODEL */
|
||||
|
||||
// [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
|
||||
// [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
|
||||
|
||||
KF.measurementMatrix.at<double>(0,0) = 1; // x
|
||||
KF.measurementMatrix.at<double>(1,1) = 1; // y
|
||||
KF.measurementMatrix.at<double>(2,2) = 1; // z
|
||||
KF.measurementMatrix.at<double>(3,9) = 1; // roll
|
||||
KF.measurementMatrix.at<double>(4,10) = 1; // pitch
|
||||
KF.measurementMatrix.at<double>(5,11) = 1; // yaw
|
||||
|
||||
}
|
||||
@endcode
|
||||
In the following code is the 5th step of the main algorithm. When the obtained number of inliers
|
||||
after *Ransac* is over the threshold, the measurements matrix is filled and then the *Kalman
|
||||
Filter* is updated:
|
||||
@code{.cpp}
|
||||
// -- Step 5: Kalman Filter
|
||||
|
||||
// GOOD MEASUREMENT
|
||||
if( inliers_idx.rows >= minInliersKalman )
|
||||
{
|
||||
|
||||
// Get the measured translation
|
||||
cv::Mat translation_measured(3, 1, CV_64F);
|
||||
translation_measured = pnp_detection.get_t_matrix();
|
||||
|
||||
// Get the measured rotation
|
||||
cv::Mat rotation_measured(3, 3, CV_64F);
|
||||
rotation_measured = pnp_detection.get_R_matrix();
|
||||
|
||||
// fill the measurements vector
|
||||
fillMeasurements(measurements, translation_measured, rotation_measured);
|
||||
|
||||
}
|
||||
|
||||
// Instantiate estimated translation and rotation
|
||||
cv::Mat translation_estimated(3, 1, CV_64F);
|
||||
cv::Mat rotation_estimated(3, 3, CV_64F);
|
||||
|
||||
// update the Kalman filter with good measurements
|
||||
updateKalmanFilter( KF, measurements,
|
||||
translation_estimated, rotation_estimated);
|
||||
@endcode
|
||||
The following code corresponds to the *fillMeasurements()* function which converts the measured
|
||||
[Rotation Matrix to Eulers
|
||||
angles](http://euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm)
|
||||
and fill the measurements matrix along with the measured translation vector:
|
||||
@code{.cpp}
|
||||
void fillMeasurements( cv::Mat &measurements,
|
||||
const cv::Mat &translation_measured, const cv::Mat &rotation_measured)
|
||||
{
|
||||
// Convert rotation matrix to euler angles
|
||||
cv::Mat measured_eulers(3, 1, CV_64F);
|
||||
measured_eulers = rot2euler(rotation_measured);
|
||||
|
||||
// Set measurement to predict
|
||||
measurements.at<double>(0) = translation_measured.at<double>(0); // x
|
||||
measurements.at<double>(1) = translation_measured.at<double>(1); // y
|
||||
measurements.at<double>(2) = translation_measured.at<double>(2); // z
|
||||
measurements.at<double>(3) = measured_eulers.at<double>(0); // roll
|
||||
measurements.at<double>(4) = measured_eulers.at<double>(1); // pitch
|
||||
measurements.at<double>(5) = measured_eulers.at<double>(2); // yaw
|
||||
}
|
||||
@endcode
|
||||
The following code corresponds to the *updateKalmanFilter()* function which update the Kalman
|
||||
Filter and set the estimated Rotation Matrix and translation vector. The estimated Rotation Matrix
|
||||
comes from the estimated [Euler angles to Rotation
|
||||
Matrix](http://euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm).
|
||||
@code{.cpp}
|
||||
void updateKalmanFilter( cv::KalmanFilter &KF, cv::Mat &measurement,
|
||||
cv::Mat &translation_estimated, cv::Mat &rotation_estimated )
|
||||
{
|
||||
|
||||
// First predict, to update the internal statePre variable
|
||||
cv::Mat prediction = KF.predict();
|
||||
|
||||
// The "correct" phase that is going to use the predicted value and our measurement
|
||||
cv::Mat estimated = KF.correct(measurement);
|
||||
|
||||
// Estimated translation
|
||||
translation_estimated.at<double>(0) = estimated.at<double>(0);
|
||||
translation_estimated.at<double>(1) = estimated.at<double>(1);
|
||||
translation_estimated.at<double>(2) = estimated.at<double>(2);
|
||||
|
||||
// Estimated euler angles
|
||||
cv::Mat eulers_estimated(3, 1, CV_64F);
|
||||
eulers_estimated.at<double>(0) = estimated.at<double>(9);
|
||||
eulers_estimated.at<double>(1) = estimated.at<double>(10);
|
||||
eulers_estimated.at<double>(2) = estimated.at<double>(11);
|
||||
|
||||
// Convert estimated quaternion to rotation matrix
|
||||
rotation_estimated = euler2rot(eulers_estimated);
|
||||
|
||||
}
|
||||
@endcode
|
||||
The 6th step is set the estimated rotation-translation matrix:
|
||||
@code{.cpp}
|
||||
// -- Step 6: Set estimated projection matrix
|
||||
pnp_detection_est.set_P_matrix(rotation_estimated, translation_estimated);
|
||||
@endcode
|
||||
The last and optional step is draw the found pose. To do it I implemented a function to draw all
|
||||
the mesh 3D points and an extra reference axis:
|
||||
@code{.cpp}
|
||||
// -- Step X: Draw pose
|
||||
|
||||
drawObjectMesh(frame_vis, &mesh, &pnp_detection, green); // draw current pose
|
||||
drawObjectMesh(frame_vis, &mesh, &pnp_detection_est, yellow); // draw estimated pose
|
||||
|
||||
double l = 5;
|
||||
std::vector<cv::Point2f> pose_points2d;
|
||||
pose_points2d.push_back(pnp_detection_est.backproject3DPoint(cv::Point3f(0,0,0))); // axis center
|
||||
pose_points2d.push_back(pnp_detection_est.backproject3DPoint(cv::Point3f(l,0,0))); // axis x
|
||||
pose_points2d.push_back(pnp_detection_est.backproject3DPoint(cv::Point3f(0,l,0))); // axis y
|
||||
pose_points2d.push_back(pnp_detection_est.backproject3DPoint(cv::Point3f(0,0,l))); // axis z
|
||||
draw3DCoordinateAxes(frame_vis, pose_points2d); // draw axes
|
||||
@endcode
|
||||
You can also modify the minimum inliers to update Kalman Filter:
|
||||
@code{.cpp}
|
||||
./cpp-tutorial-pnp_detection --inliers=20
|
||||
@endcode
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
The following videos are the results of pose estimation in real time using the explained detection
|
||||
algorithm using the following parameters:
|
||||
@code{.cpp}
|
||||
// Robust Matcher parameters
|
||||
|
||||
int numKeyPoints = 2000; // number of detected keypoints
|
||||
float ratio = 0.70f; // ratio test
|
||||
bool fast_match = true; // fastRobustMatch() or robustMatch()
|
||||
|
||||
|
||||
// RANSAC parameters
|
||||
|
||||
int iterationsCount = 500; // number of Ransac iterations.
|
||||
int reprojectionError = 2.0; // maximum allowed distance to consider it an inlier.
|
||||
float confidence = 0.95; // ransac successful confidence.
|
||||
|
||||
|
||||
// Kalman Filter parameters
|
||||
|
||||
int minInliersKalman = 30; // Kalman threshold updating
|
||||
@endcode
|
||||
You can watch the real time pose estimation on the [YouTube
|
||||
here](http://www.youtube.com/user/opencvdev/videos).
|
||||
|
||||
@youtube{XNATklaJlSQ}
|
||||
@youtube{YLS9bWek78k}
|
||||
@@ -0,0 +1,9 @@
|
||||
Camera calibration and 3D reconstruction (calib3d module) {#tutorial_table_of_content_calib3d}
|
||||
==========================================================
|
||||
|
||||
- @subpage tutorial_camera_calibration_pattern
|
||||
- @subpage tutorial_camera_calibration_square_chess
|
||||
- @subpage tutorial_camera_calibration
|
||||
- @subpage tutorial_real_time_pose
|
||||
- @subpage tutorial_interactive_calibration
|
||||
- @subpage tutorial_usac
|
||||
@@ -0,0 +1,269 @@
|
||||
USAC: Improvement of Random Sample Consensus in OpenCV {#tutorial_usac}
|
||||
==============================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_interactive_calibration}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Maksym Ivashechkin |
|
||||
| Compatibility | OpenCV >= 4.0 |
|
||||
|
||||
This work was integrated as part of the Google Summer of Code (August 2020).
|
||||
|
||||
Contribution
|
||||
------
|
||||
|
||||
The integrated part to OpenCV `calib3d` module is RANSAC-based universal
|
||||
framework USAC (`namespace usac`) written in C++. The framework includes
|
||||
different state-of-the-arts methods for sampling, verification or local
|
||||
optimization. The main advantage of the framework is its independence to
|
||||
any estimation problem and modular structure. Therefore, new solvers or
|
||||
methods can be added/removed easily. So far it includes the following
|
||||
components:
|
||||
|
||||
1. Sampling method:
|
||||
|
||||
1. Uniform – standard RANSAC sampling proposed in @cite FischlerRANSAC which draw
|
||||
minimal subset independently uniformly at random. *The default
|
||||
option in proposed framework*.
|
||||
|
||||
2. PROSAC – method @cite ChumPROSAC that assumes input data points sorted by
|
||||
quality so sampling can start from the most promising points.
|
||||
Correspondences for this method can be sorted e.g., by ratio of
|
||||
descriptor distances of the best to second match obtained from
|
||||
SIFT detector. *This is method is recommended to use because it
|
||||
can find good model and terminate much earlier*.
|
||||
|
||||
3. NAPSAC – sampling method @cite MyattNAPSAC which takes initial point
|
||||
uniformly at random and the rest of points for minimal sample in
|
||||
the neighborhood of initial point. This is method can be
|
||||
potentially useful when models are localized. For example, for
|
||||
plane fitting. However, in practise struggles from degenerate
|
||||
issues and defining optimal neighborhood size.
|
||||
|
||||
4. Progressive-NAPSAC – sampler @cite barath2019progressive which is similar to NAPSAC,
|
||||
although it starts from local and gradually converges to
|
||||
global sampling. This method can be quite useful if local models
|
||||
are expected but distribution of data can be arbitrary. The
|
||||
implemented version assumes data points to be sorted by quality
|
||||
as in PROSAC.
|
||||
|
||||
2. Score Method. USAC as well as standard RANSAC finds model which
|
||||
minimizes total loss. Loss can be represented by following
|
||||
functions:
|
||||
|
||||
1. RANSAC – binary 0 / 1 loss. 1 for outlier, 0 for inlier. *Good
|
||||
option if the goal is to find as many inliers as possible.*
|
||||
|
||||
2. MSAC – truncated squared error distance of point to model. *The
|
||||
default option in framework*. The model might not have as many
|
||||
inliers as using RANSAC score, however will be more accurate.
|
||||
|
||||
3. MAGSAC – threshold-free method @cite BarathMAGSAC to compute score. Using,
|
||||
although, maximum sigma (standard deviation of noise) level to
|
||||
marginalize residual of point over sigma. Score of the point
|
||||
represents likelihood of point being inlier. *Recommended option
|
||||
when image noise is unknown since method does not require
|
||||
threshold*. However, it is still recommended to provide at least
|
||||
approximated threshold, because termination itself is based on
|
||||
number of points which error is less than threshold. By giving 0
|
||||
threshold the method will output model after maximum number of
|
||||
iterations reached.
|
||||
|
||||
4. LMeds – the least median of squared error distances. In the
|
||||
framework finding median is efficiently implement with $O(n)$
|
||||
complexity using quick-sort algorithm. Note, LMeds does not have
|
||||
to work properly when inlier ratio is less than 50%, in other
|
||||
cases this method is robust and does not require threshold.
|
||||
|
||||
3. Error metric which describes error distance of point to
|
||||
estimated model.
|
||||
|
||||
1. Re-projection distance – used for affine, homography and
|
||||
projection matrices. For homography also symmetric re-projection
|
||||
distance can be used.
|
||||
|
||||
2. Sampson distance – used for Fundamental matrix.
|
||||
|
||||
3. Symmetric Geometric distance – used for Essential matrix.
|
||||
|
||||
4. Degeneracy:
|
||||
|
||||
1. DEGENSAC – method @cite ChumDominant which for Fundamental matrix estimation
|
||||
efficiently verifies and recovers model which has at least 5
|
||||
points in minimal sample lying on the dominant plane.
|
||||
|
||||
2. Collinearity test – for affine and homography matrix estimation
|
||||
checks if no 3 points lying on the line. For homography matrix
|
||||
since points are planar is applied test which checks if points
|
||||
in minimal sample lie on the same side w.r.t. to any line
|
||||
crossing any two points in sample (does not assume reflection).
|
||||
|
||||
3. Oriented epipolar constraint – method @cite ChumEpipolar for epipolar
|
||||
geometry which verifies model (fundamental and essential matrix)
|
||||
to have points visible in the front of the camera.
|
||||
|
||||
5. SPRT verification – method @cite Matas2005RandomizedRW which verifies model by its
|
||||
evaluation on randomly shuffled points using statistical properties
|
||||
given by probability of inlier, relative time for estimation,
|
||||
average number of output models etc. Significantly speeding up
|
||||
framework, because bad model can be rejected very quickly without
|
||||
explicitly computing error for every point.
|
||||
|
||||
6. Local Optimization:
|
||||
|
||||
1. Locally Optimized RANSAC – method @cite ChumLORANSAC that iteratively
|
||||
improves so-far-the-best model by non-minimal estimation. *The
|
||||
default option in framework. This procedure is the fastest and
|
||||
not worse than others local optimization methods.*
|
||||
|
||||
2. Graph-Cut RANSAC – method @cite BarathGCRANSAC that refine so-far-the-best
|
||||
model, however, it exploits spatial coherence of the
|
||||
data points. *This procedure is quite precise however
|
||||
computationally slower.*
|
||||
|
||||
3. Sigma Consensus – method @cite BarathMAGSAC which improves model by applying
|
||||
non-minimal weighted estimation, where weights are computed with
|
||||
the same logic as in MAGSAC score. This method is better to use
|
||||
together with MAGSAC score.
|
||||
|
||||
7. Termination:
|
||||
|
||||
1. Standard – standard equation for independent and
|
||||
uniform sampling.
|
||||
|
||||
2. PROSAC – termination for PROSAC.
|
||||
|
||||
3. SPRT – termination for SPRT.
|
||||
|
||||
8. Solver. In the framework there are minimal and non-minimal solvers.
|
||||
In minimal solver standard methods for estimation is applied. In
|
||||
non-minimal solver usually the covariance matrix is built and the
|
||||
model is found as the eigen vector corresponding to the highest
|
||||
eigen value.
|
||||
|
||||
1. Affine2D matrix
|
||||
|
||||
2. Homography matrix – for minimal solver is used RHO
|
||||
(Gaussian elimination) algorithm from OpenCV.
|
||||
|
||||
3. Fundamental matrix – for 7-points algorithm two null vectors are
|
||||
found using Gaussian elimination (eliminating to upper
|
||||
triangular matrix and back-substitution) instead of SVD and then
|
||||
solving 3-degrees polynomial. For 8-points solver Gaussian
|
||||
elimination is used too.
|
||||
|
||||
4. Essential matrix – 4 null vectors are found using
|
||||
Gaussian elimination. Then the solver based on Gröbner basis
|
||||
described in @cite SteweniusRecent is used. Essential matrix can be computed
|
||||
only if <span style="font-variant:small-caps;">LAPACK</span> or
|
||||
<span style="font-variant:small-caps;">Eigen</span> are
|
||||
installed as it requires eigen decomposition with complex
|
||||
eigen values.
|
||||
|
||||
5. Perspective-n-Point – the minimal solver is classical 3 points
|
||||
with up to 4 solutions. For RANSAC the low number of sample size
|
||||
plays significant role as it requires less iterations,
|
||||
furthermore in average P3P solver has around 1.39
|
||||
estimated models. Also, in new version of `solvePnPRansac(...)`
|
||||
with `UsacParams` there is an option to pass empty intrinsic
|
||||
matrix `InputOutputArray cameraMatrix`. If matrix is empty then
|
||||
using Direct Linear Transformation algorithm (PnP with 6 points)
|
||||
framework outputs not only rotation and translation vector but
|
||||
also calibration matrix.
|
||||
|
||||
Also, the framework can be run in parallel. The parallelization is done
|
||||
in the way that multiple RANSACs are created and they share two atomic
|
||||
variables `bool success` and `int num_hypothesis_tested` which
|
||||
determines when all RANSACs must terminate. If one of RANSAC terminated
|
||||
successfully then all other RANSAC will terminate as well. In the end
|
||||
the best model is synchronized from all threads. If PROSAC sampler is
|
||||
used then threads must share the same sampler since sampling is done
|
||||
sequentially. However, using default options of framework parallel
|
||||
RANSAC is not deterministic since it depends on how often each thread is
|
||||
running. The easiest way to make it deterministic is using PROSAC
|
||||
sampler without SPRT and Local Optimization and not for Fundamental
|
||||
matrix, because they internally use random generators.
|
||||
|
||||
For NAPSAC, Progressive NAPSAC or Graph-Cut methods is required to build
|
||||
a neighborhood graph. In framework there are 3 options to do it:
|
||||
|
||||
1. NEIGH_FLANN_KNN – estimate neighborhood graph using OpenCV FLANN
|
||||
K nearest-neighbors. The default value for KNN is 7. KNN method may
|
||||
work good for sampling but not good for GC-RANSAC.
|
||||
|
||||
2. `NEIGH_FLANN_RADIUS` – similarly as in previous case finds neighbor
|
||||
points which distance is less than 20 pixels.
|
||||
|
||||
3. `NEIGH_GRID` – for finding points’ neighborhood tiles points in
|
||||
cells using hash-table. The method is described in @cite barath2019progressive. Less
|
||||
accurate than `NEIGH_FLANN_RADIUS`, although significantly faster.
|
||||
|
||||
Note, `NEIGH_FLANN_RADIUS` and `NEIGH_GRID` are not able to PnP
|
||||
solver, since there are 3D object points.
|
||||
|
||||
New flags:
|
||||
------
|
||||
1. `USAC_DEFAULT` – has standard LO-RANSAC.
|
||||
|
||||
2. `USAC_PARALLEL` – has LO-RANSAC and RANSACs run in parallel.
|
||||
|
||||
3. `USAC_ACCURATE` – has GC-RANSAC.
|
||||
|
||||
4. `USAC_FAST` – has LO-RANSAC with smaller number iterations in local
|
||||
optimization step. Uses RANSAC score to maximize number of inliers
|
||||
and terminate earlier.
|
||||
|
||||
5. `USAC_PROSAC` – has PROSAC sampling. Note, points must be sorted.
|
||||
|
||||
6. `USAC_FM_8PTS` – has LO-RANSAC. Only valid for Fundamental matrix
|
||||
with 8-points solver.
|
||||
|
||||
7. `USAC_MAGSAC` – has MAGSAC++.
|
||||
|
||||
Every flag uses SPRT verification. And in the end the final
|
||||
so-far-the-best model is polished by non minimal estimation of all found
|
||||
inliers.
|
||||
|
||||
A few other important parameters:
|
||||
------
|
||||
|
||||
1. `randomGeneratorState` – since every USAC solver is deterministic in
|
||||
OpenCV (i.e., for the same points and parameters returns the
|
||||
same result) by providing new state it will output new model.
|
||||
|
||||
2. `loIterations` – number of iterations for Local Optimization method.
|
||||
*The default value is 10*. By increasing `loIterations` the output
|
||||
model could be more accurate, however, the computational time may
|
||||
also increase.
|
||||
|
||||
3. `loSampleSize` – maximum sample number for Local Optimization. *The
|
||||
default value is 14*. Note, that by increasing `loSampleSize` the
|
||||
accuracy of model can increase as well as the computational time.
|
||||
However, it is recommended to keep value less than 100, because
|
||||
estimation on low number of points is faster and more robust.
|
||||
|
||||
Samples:
|
||||
------
|
||||
|
||||
There are three new sample files in opencv/samples directory.
|
||||
|
||||
1. `epipolar_lines.cpp` – input arguments of `main` function are two
|
||||
paths to images. Then correspondences are found using
|
||||
SIFT detector. Fundamental matrix is found using RANSAC from
|
||||
tentative correspondences and epipolar lines are plotted.
|
||||
|
||||
2. `essential_mat_reconstr.cpp` – input arguments are path to data file
|
||||
containing image names and single intrinsic matrix and directory
|
||||
where these images located. Correspondences are found using SIFT.
|
||||
The essential matrix is estimated using RANSAC and decomposed to
|
||||
rotation and translation. Then by building two relative poses with
|
||||
projection matrices image points are triangulated to object points.
|
||||
By running RANSAC with 3D plane fitting object points as well as
|
||||
correspondences are clustered into planes.
|
||||
|
||||
3. `essential_mat_reconstr.py` – the same functionality as in .cpp
|
||||
file, however instead of clustering points to plane the 3D map of
|
||||
object points is plotted.
|
||||
@@ -0,0 +1,123 @@
|
||||
Adding (blending) two images using OpenCV {#tutorial_adding_images}
|
||||
=========================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_mat_operations}
|
||||
@next_tutorial{tutorial_basic_linear_transform}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Ana Huamán |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
We will learn how to blend two images!
|
||||
Goal
|
||||
----
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
- what is *linear blending* and why it is useful;
|
||||
- how to add two images using **addWeighted()**
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
@note
|
||||
The explanation below belongs to the book [Computer Vision: Algorithms and
|
||||
Applications](https://szeliski.org/Book/) by Richard Szeliski
|
||||
|
||||
From our previous tutorial, we already know a bit of *Pixel operators*. An interesting dyadic
|
||||
(two-input) operator is the *linear blend operator*:
|
||||
|
||||
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
|
||||
|
||||
By varying \f$\alpha\f$ from \f$0 \rightarrow 1\f$, this operator can be used to perform a temporal
|
||||
*cross-dissolve* between two images or videos, as seen in slide shows and film productions (cool,
|
||||
eh?)
|
||||
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
Download the source code from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/cpp/tutorial_code/core/AddingImages/AddingImages.cpp).
|
||||
@include cpp/tutorial_code/core/AddingImages/AddingImages.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
Download the source code from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/java/tutorial_code/core/AddingImages/AddingImages.java).
|
||||
@include java/tutorial_code/core/AddingImages/AddingImages.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
Download the source code from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/python/tutorial_code/core/AddingImages/adding_images.py).
|
||||
@include python/tutorial_code/core/AddingImages/adding_images.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
Since we are going to perform:
|
||||
|
||||
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
|
||||
|
||||
We need two source images (\f$f_{0}(x)\f$ and \f$f_{1}(x)\f$). So, we load them in the usual way:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py load
|
||||
@end_toggle
|
||||
|
||||
We used the following images: [LinuxLogo.jpg](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/LinuxLogo.jpg) and [WindowsLogo.jpg](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/WindowsLogo.jpg)
|
||||
|
||||
@warning Since we are *adding* *src1* and *src2*, they both have to be of the same size
|
||||
(width and height) and type.
|
||||
|
||||
Now we need to generate the `g(x)` image. For this, the function **addWeighted()** comes quite handy:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp blend_images
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java blend_images
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py blend_images
|
||||
Numpy version of above line (but cv function is around 2x faster):
|
||||
\code{.py}
|
||||
dst = np.uint8(alpha*(img1)+beta*(img2))
|
||||
\endcode
|
||||
@end_toggle
|
||||
|
||||
since **addWeighted()** produces:
|
||||
\f[dst = \alpha \cdot src1 + \beta \cdot src2 + \gamma\f]
|
||||
In this case, `gamma` is the argument \f$0.0\f$ in the code above.
|
||||
|
||||
Create windows, show the images and wait for the user to end the program.
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py display
|
||||
@end_toggle
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,325 @@
|
||||
Changing the contrast and brightness of an image! {#tutorial_basic_linear_transform}
|
||||
=================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_adding_images}
|
||||
@next_tutorial{tutorial_discrete_fourier_transform}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Ana Huamán |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
- Access pixel values
|
||||
- Initialize a matrix with zeros
|
||||
- Learn what @ref cv::saturate_cast does and why it is useful
|
||||
- Get some cool info about pixel transformations
|
||||
- Improve the brightness of an image on a practical example
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
@note
|
||||
The explanation below belongs to the book [Computer Vision: Algorithms and
|
||||
Applications](https://szeliski.org/Book/) by Richard Szeliski
|
||||
|
||||
### Image Processing
|
||||
|
||||
- A general image processing operator is a function that takes one or more input images and
|
||||
produces an output image.
|
||||
- Image transforms can be seen as:
|
||||
- Point operators (pixel transforms)
|
||||
- Neighborhood (area-based) operators
|
||||
|
||||
### Pixel Transforms
|
||||
|
||||
- In this kind of image processing transform, each output pixel's value depends on only the
|
||||
corresponding input pixel value (plus, potentially, some globally collected information or
|
||||
parameters).
|
||||
- Examples of such operators include *brightness and contrast adjustments* as well as color
|
||||
correction and transformations.
|
||||
|
||||
### Brightness and contrast adjustments
|
||||
|
||||
- Two commonly used point processes are *multiplication* and *addition* with a constant:
|
||||
|
||||
\f[g(x) = \alpha f(x) + \beta\f]
|
||||
|
||||
- The parameters \f$\alpha > 0\f$ and \f$\beta\f$ are often called the *gain* and *bias* parameters;
|
||||
sometimes these parameters are said to control *contrast* and *brightness* respectively.
|
||||
- You can think of \f$f(x)\f$ as the source image pixels and \f$g(x)\f$ as the output image pixels. Then,
|
||||
more conveniently we can write the expression as:
|
||||
|
||||
\f[g(i,j) = \alpha \cdot f(i,j) + \beta\f]
|
||||
|
||||
where \f$i\f$ and \f$j\f$ indicates that the pixel is located in the *i-th* row and *j-th* column.
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@add_toggle_cpp
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp)
|
||||
|
||||
- The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ :
|
||||
@include samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java)
|
||||
|
||||
- The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ :
|
||||
@include samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py)
|
||||
|
||||
- The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ :
|
||||
@include samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
- We load an image using @ref cv::imread and save it in a Mat object:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp basic-linear-transform-load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java basic-linear-transform-load
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py basic-linear-transform-load
|
||||
@end_toggle
|
||||
|
||||
- Now, since we will make some transformations to this image, we need a new Mat object to store
|
||||
it. Also, we want this to have the following features:
|
||||
|
||||
- Initial pixel values equal to zero
|
||||
- Same size and type as the original image
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp basic-linear-transform-output
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java basic-linear-transform-output
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py basic-linear-transform-output
|
||||
@end_toggle
|
||||
|
||||
We observe that @ref cv::Mat::zeros returns a Matlab-style zero initializer based on
|
||||
*image.size()* and *image.type()*
|
||||
|
||||
- We ask now the values of \f$\alpha\f$ and \f$\beta\f$ to be entered by the user:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp basic-linear-transform-parameters
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java basic-linear-transform-parameters
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py basic-linear-transform-parameters
|
||||
@end_toggle
|
||||
|
||||
- Now, to perform the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ we will access to each
|
||||
pixel in image. Since we are operating with BGR images, we will have three values per pixel (B,
|
||||
G and R), so we will also access them separately. Here is the piece of code:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp basic-linear-transform-operation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java basic-linear-transform-operation
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py basic-linear-transform-operation
|
||||
@end_toggle
|
||||
|
||||
Notice the following (**C++ code only**):
|
||||
- To access each pixel in the images we are using this syntax: *image.at\<Vec3b\>(y,x)[c]*
|
||||
where *y* is the row, *x* is the column and *c* is B, G or R (0, 1 or 2).
|
||||
- Since the operation \f$\alpha \cdot p(i,j) + \beta\f$ can give values out of range or not
|
||||
integers (if \f$\alpha\f$ is float), we use cv::saturate_cast to make sure the
|
||||
values are valid.
|
||||
|
||||
- Finally, we create windows and show the images, the usual way.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp basic-linear-transform-display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/BasicLinearTransformsDemo.java basic-linear-transform-display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/BasicLinearTransforms.py basic-linear-transform-display
|
||||
@end_toggle
|
||||
|
||||
@note
|
||||
Instead of using the **for** loops to access each pixel, we could have simply used this command:
|
||||
|
||||
@add_toggle_cpp
|
||||
@code{.cpp}
|
||||
image.convertTo(new_image, -1, alpha, beta);
|
||||
@endcode
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@code{.java}
|
||||
image.convertTo(newImage, -1, alpha, beta);
|
||||
@endcode
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@code{.py}
|
||||
new_image = cv.convertScaleAbs(image, alpha=alpha, beta=beta)
|
||||
@endcode
|
||||
@end_toggle
|
||||
|
||||
where @ref cv::Mat::convertTo would effectively perform *new_image = a*image + beta\*. However, we
|
||||
wanted to show you how to access each pixel. In any case, both methods give the same result but
|
||||
convertTo is more optimized and works a lot faster.
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
- Running our code and using \f$\alpha = 2.2\f$ and \f$\beta = 50\f$
|
||||
@code{.bash}
|
||||
$ ./BasicLinearTransforms lena.jpg
|
||||
Basic Linear Transforms
|
||||
-------------------------
|
||||
* Enter the alpha value [1.0-3.0]: 2.2
|
||||
* Enter the beta value [0-100]: 50
|
||||
@endcode
|
||||
|
||||
- We get this:
|
||||
|
||||

|
||||
|
||||
Practical example
|
||||
----
|
||||
|
||||
In this paragraph, we will put into practice what we have learned to correct an underexposed image by adjusting the brightness
|
||||
and the contrast of the image. We will also see another technique to correct the brightness of an image called
|
||||
gamma correction.
|
||||
|
||||
### Brightness and contrast adjustments
|
||||
|
||||
Increasing (/ decreasing) the \f$\beta\f$ value will add (/ subtract) a constant value to every pixel. Pixel values outside of the [0 ; 255]
|
||||
range will be saturated (i.e. a pixel value higher (/ lesser) than 255 (/ 0) will be clamped to 255 (/ 0)).
|
||||
|
||||

|
||||
|
||||
The histogram represents for each color level the number of pixels with that color level. A dark image will have many pixels with
|
||||
low color value and thus the histogram will present a peak in its left part. When adding a constant bias, the histogram is shifted to the
|
||||
right as we have added a constant bias to all the pixels.
|
||||
|
||||
The \f$\alpha\f$ parameter will modify how the levels spread. If \f$ \alpha < 1 \f$, the color levels will be compressed and the result
|
||||
will be an image with less contrast.
|
||||
|
||||

|
||||
|
||||
Note that these histograms have been obtained using the Brightness-Contrast tool in the Gimp software. The brightness tool should be
|
||||
identical to the \f$\beta\f$ bias parameters but the contrast tool seems to differ to the \f$\alpha\f$ gain where the output range
|
||||
seems to be centered with Gimp (as you can notice in the previous histogram).
|
||||
|
||||
It can occur that playing with the \f$\beta\f$ bias will improve the brightness but in the same time the image will appear with a
|
||||
slight veil as the contrast will be reduced. The \f$\alpha\f$ gain can be used to diminue this effect but due to the saturation,
|
||||
we will lose some details in the original bright regions.
|
||||
|
||||
### Gamma correction
|
||||
|
||||
[Gamma correction](https://en.wikipedia.org/wiki/Gamma_correction) can be used to correct the brightness of an image by using a non
|
||||
linear transformation between the input values and the mapped output values:
|
||||
|
||||
\f[O = \left( \frac{I}{255} \right)^{\gamma} \times 255\f]
|
||||
|
||||
As this relation is non linear, the effect will not be the same for all the pixels and will depend to their original value.
|
||||
|
||||

|
||||
|
||||
When \f$ \gamma < 1 \f$, the original dark regions will be brighter and the histogram will be shifted to the right whereas it will
|
||||
be the opposite with \f$ \gamma > 1 \f$.
|
||||
|
||||
### Correct an underexposed image
|
||||
|
||||
The following image has been corrected with: \f$ \alpha = 1.3 \f$ and \f$ \beta = 40 \f$.
|
||||
|
||||
![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) { width=90% }
|
||||
|
||||
The overall brightness has been improved but you can notice that the clouds are now greatly saturated due to the numerical saturation
|
||||
of the implementation used ([highlight clipping](https://en.wikipedia.org/wiki/Clipping_(photography)) in photography).
|
||||
|
||||
The following image has been corrected with: \f$ \gamma = 0.4 \f$.
|
||||
|
||||
![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) { width=90% }
|
||||
|
||||
The gamma correction should tend to add less saturation effect as the mapping is non linear and there is no numerical saturation possible as in the previous method.
|
||||
|
||||

|
||||
|
||||
The previous figure compares the histograms for the three images (the y-ranges are not the same between the three histograms).
|
||||
You can notice that most of the pixel values are in the lower part of the histogram for the original image. After \f$ \alpha \f$,
|
||||
\f$ \beta \f$ correction, we can observe a big peak at 255 due to the saturation as well as a shift in the right.
|
||||
After gamma correction, the histogram is shifted to the right but the pixels in the dark regions are more shifted
|
||||
(see the gamma curves [figure](Basic_Linear_Transform_Tutorial_gamma.png)) than those in the bright regions.
|
||||
|
||||
In this tutorial, you have seen two simple methods to adjust the contrast and the brightness of an image. **They are basic techniques
|
||||
and are not intended to be used as a replacement of a raster graphics editor!**
|
||||
|
||||
### Code
|
||||
|
||||
@add_toggle_cpp
|
||||
Code for the tutorial is [here](https://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp).
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
Code for the tutorial is [here](https://github.com/opencv/opencv/blob/4.x/samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/ChangingContrastBrightnessImageDemo.java).
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
Code for the tutorial is [here](https://github.com/opencv/opencv/blob/4.x/samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.py).
|
||||
@end_toggle
|
||||
|
||||
Code for the gamma correction:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp changing-contrast-brightness-gamma-correction
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/ImgProc/changing_contrast_brightness_image/ChangingContrastBrightnessImageDemo.java changing-contrast-brightness-gamma-correction
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/imgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.py changing-contrast-brightness-gamma-correction
|
||||
@end_toggle
|
||||
|
||||
A look-up table is used to improve the performance of the computation as only 256 values needs to be calculated once.
|
||||
|
||||
### Additional resources
|
||||
|
||||
- [Gamma correction in graphics rendering](https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction)
|
||||
- [Gamma correction and images displayed on CRT monitors](http://www.graphics.cornell.edu/~westin/gamma/gamma.html)
|
||||
- [Digital exposure techniques](http://www.cambridgeincolour.com/tutorials/digital-exposure-techniques.htm)
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 222 KiB |
@@ -0,0 +1,245 @@
|
||||
Discrete Fourier Transform {#tutorial_discrete_fourier_transform}
|
||||
==========================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_basic_linear_transform}
|
||||
@next_tutorial{tutorial_file_input_output_with_xml_yml}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
We'll seek answers for the following questions:
|
||||
|
||||
- What is a Fourier transform and why use it?
|
||||
- How to do it in OpenCV?
|
||||
- Usage of functions such as: **copyMakeBorder()** , **merge()** , **dft()** ,
|
||||
**getOptimalDFTSize()** , **log()** and **normalize()** .
|
||||
|
||||
Source code
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
You can [download this from here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or
|
||||
find it in the
|
||||
`samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
You can [download this from here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java) or
|
||||
find it in the
|
||||
`samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
You can [download this from here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py) or
|
||||
find it in the
|
||||
`samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
Here's a sample usage of **dft()** :
|
||||
|
||||
@add_toggle_cpp
|
||||
@include cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@include java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@include python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
The Fourier Transform will decompose an image into its sinus and cosines components. In other words,
|
||||
it will transform an image from its spatial domain to its frequency domain. The idea is that any
|
||||
function may be approximated exactly with the sum of infinite sinus and cosines functions. The
|
||||
Fourier Transform is a way how to do this. Mathematically a two dimensional images Fourier transform
|
||||
is:
|
||||
|
||||
\f[F(k,l) = \displaystyle\sum\limits_{i=0}^{N-1}\sum\limits_{j=0}^{N-1} f(i,j)e^{-i2\pi(\frac{ki}{N}+\frac{lj}{N})}\f]\f[e^{ix} = \cos{x} + i\sin {x}\f]
|
||||
|
||||
Here f is the image value in its spatial domain and F in its frequency domain. The result of the
|
||||
transformation is complex numbers. Displaying this is possible either via a *real* image and a
|
||||
*complex* image or via a *magnitude* and a *phase* image. However, throughout the image processing
|
||||
algorithms only the *magnitude* image is interesting as this contains all the information we need
|
||||
about the images geometric structure. Nevertheless, if you intend to make some modifications of the
|
||||
image in these forms and then you need to retransform it you'll need to preserve both of these.
|
||||
|
||||
In this sample I'll show how to calculate and show the *magnitude* image of a Fourier Transform. In
|
||||
case of digital images are discrete. This means they may take up a value from a given domain value.
|
||||
For example in a basic gray scale image values usually are between zero and 255. Therefore the
|
||||
Fourier Transform too needs to be of a discrete type resulting in a Discrete Fourier Transform
|
||||
(*DFT*). You'll want to use this whenever you need to determine the structure of an image from a
|
||||
geometrical point of view. Here are the steps to follow (in case of a gray scale input image *I*):
|
||||
|
||||
### Expand the image to an optimal size
|
||||
|
||||
The performance of a DFT is dependent of the image
|
||||
size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and
|
||||
five. Therefore, to achieve maximal performance it is generally a good idea to pad border values
|
||||
to the image to get a size with such traits. The **getOptimalDFTSize()** returns this
|
||||
optimal size and we can use the **copyMakeBorder()** function to expand the borders of an
|
||||
image (the appended pixels are initialized with zero):
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp expand
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java expand
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py expand
|
||||
@end_toggle
|
||||
|
||||
### Make place for both the complex and the real values
|
||||
|
||||
The result of a Fourier Transform is
|
||||
complex. This implies that for each image value the result is two image values (one per
|
||||
component). Moreover, the frequency domains range is much larger than its spatial counterpart.
|
||||
Therefore, we store these usually at least in a *float* format. Therefore we'll convert our
|
||||
input image to this type and expand it with another channel to hold the complex values:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp complex_and_real
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java complex_and_real
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py complex_and_real
|
||||
@end_toggle
|
||||
|
||||
### Make the Discrete Fourier Transform
|
||||
It's possible an in-place calculation (same input as
|
||||
output):
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp dft
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java dft
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py dft
|
||||
@end_toggle
|
||||
|
||||
### Transform the real and complex values to magnitude
|
||||
A complex number has a real (*Re*) and a
|
||||
complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a
|
||||
DFT is:
|
||||
|
||||
\f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f]
|
||||
|
||||
Translated to OpenCV code:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp magnitude
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java magnitude
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py magnitude
|
||||
@end_toggle
|
||||
|
||||
### Switch to a logarithmic scale
|
||||
It turns out that the dynamic range of the Fourier
|
||||
coefficients is too large to be displayed on the screen. We have some small and some high
|
||||
changing values that we can't observe like this. Therefore the high values will all turn out as
|
||||
white points, while the small ones as black. To use the gray scale values to for visualization
|
||||
we can transform our linear scale to a logarithmic one:
|
||||
|
||||
\f[M_1 = \log{(1 + M)}\f]
|
||||
|
||||
Translated to OpenCV code:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp log
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java log
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py log
|
||||
@end_toggle
|
||||
|
||||
### Crop and rearrange
|
||||
Remember, that at the first step, we expanded the image? Well, it's time
|
||||
to throw away the newly introduced values. For visualization purposes we may also rearrange the
|
||||
quadrants of the result, so that the origin (zero, zero) corresponds with the image center.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
### Normalize
|
||||
This is done again for visualization purposes. We now have the magnitudes,
|
||||
however this are still out of our image display range of zero to one. We normalize our values to
|
||||
this range using the @ref cv::normalize() function.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp normalize
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java normalize
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py normalize
|
||||
@end_toggle
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
An application idea would be to determine the geometrical orientation present in the image. For
|
||||
example, let us find out if a text is horizontal or not? Looking at some text you'll notice that the
|
||||
text lines sort of form also horizontal lines and the letters form sort of vertical lines. These two
|
||||
main components of a text snippet may be also seen in case of the Fourier transform. Let us use
|
||||
[this horizontal ](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/imageTextN.png) and [this rotated](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/imageTextR.png)
|
||||
image about a text.
|
||||
|
||||
In case of the horizontal text:
|
||||
|
||||

|
||||
|
||||
In case of a rotated text:
|
||||
|
||||

|
||||
|
||||
You can see that the most influential components of the frequency domain (brightest dots on the
|
||||
magnitude image) follow the geometric rotation of objects on the image. From this we may calculate
|
||||
the offset and perform an image rotation to correct eventual miss alignments.
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,297 @@
|
||||
File Input and Output using XML / YAML / JSON files {#tutorial_file_input_output_with_xml_yml}
|
||||
==============================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_discrete_fourier_transform}
|
||||
@next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_}
|
||||
@next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_new}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
You'll find answers to the following questions:
|
||||
|
||||
- How do you print and read text entries to a file in OpenCV using YAML, XML, or JSON files?
|
||||
- How can you perform the same operations for OpenCV data structures?
|
||||
- How can this be done for your custom data structures?
|
||||
- How do you use OpenCV data structures, such as @ref cv::FileStorage , @ref cv::FileNode or @ref
|
||||
cv::FileNodeIterator .
|
||||
|
||||
Source code
|
||||
-----------
|
||||
@add_toggle_cpp
|
||||
You can [download this from here
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp) or find it in the
|
||||
`samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp` of the OpenCV source code
|
||||
library.
|
||||
|
||||
Here's a sample code of how to achieve all the stuff enumerated at the goal list.
|
||||
|
||||
@include cpp/tutorial_code/core/file_input_output/file_input_output.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
You can [download this from here
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/core/file_input_output/file_input_output.py) or find it in the
|
||||
`samples/python/tutorial_code/core/file_input_output/file_input_output.py` of the OpenCV source code
|
||||
library.
|
||||
|
||||
Here's a sample code of how to achieve all the stuff enumerated at the goal list.
|
||||
|
||||
@include python/tutorial_code/core/file_input_output/file_input_output.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
Here we talk only about XML, YAML and JSON file inputs. Your output (and its respective input) file may
|
||||
have only one of these extensions and the structure coming from this. They are two kinds of data
|
||||
structures you may serialize: *mappings* (like the STL map and the Python dictionary) and *element sequence* (like the STL
|
||||
vector). The difference between these is that in a map every element has a unique name through what
|
||||
you may access it. For sequences you need to go through them to query a specific item.
|
||||
|
||||
-# **XML/YAML/JSON File Open and Close.** Before you write any content to such file you need to open it
|
||||
and at the end to close it. The XML/YAML/JSON data structure in OpenCV is @ref cv::FileStorage . To
|
||||
specify that this structure to which file binds on your hard drive you can use either its
|
||||
constructor or the *open()* function of this:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp open
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py open
|
||||
@end_toggle
|
||||
Either one of this you use the second argument is a constant specifying the type of operations
|
||||
you'll be able to on them: WRITE, READ or APPEND. The extension specified in the file name also
|
||||
determinates the output format that will be used. The output may be even compressed if you
|
||||
specify an extension such as *.xml.gz*.
|
||||
|
||||
The file automatically closes when the @ref cv::FileStorage objects is destroyed. However, you
|
||||
may explicitly call for this by using the *release* function:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp close
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py close
|
||||
@end_toggle
|
||||
-# **Input and Output of text and numbers.** In C++, the data structure uses the \<\< output
|
||||
operator in the STL library. In Python, @ref cv::FileStorage.write() is used instead. For
|
||||
outputting any type of data structure we need first to specify its name. We do this by just
|
||||
simply pushing the name of this to the stream in C++. In Python, the first parameter for the
|
||||
write function is the name. For basic types you may follow this with the print of the value :
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp writeNum
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py writeNum
|
||||
@end_toggle
|
||||
Reading in is a simple addressing (via the [] operator) and casting operation or a read via
|
||||
the \>\> operator. In Python, we address with getNode() and use real() :
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp readNum
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp readNum
|
||||
@end_toggle
|
||||
-# **Input/Output of OpenCV Data structures.** Well these behave exactly just as the basic C++
|
||||
and Python types:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp iomati
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp iomatw
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp iomat
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py iomati
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py iomatw
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py iomat
|
||||
@end_toggle
|
||||
-# **Input/Output of vectors (arrays) and associative maps.** As I mentioned beforehand, we can
|
||||
output maps and sequences (array, vector) too. Again we first print the name of the variable and
|
||||
then we have to specify if our output is either a sequence or map.
|
||||
|
||||
For sequence before the first element print the "[" character and after the last one the "]"
|
||||
character. With Python, call `FileStorage.startWriteStruct(structure_name, struct_type)`,
|
||||
where `struct_type` is `cv2.FileNode_MAP` or `cv2.FileNode_SEQ` to start writing the structure.
|
||||
Call `FileStorage.endWriteStruct()` to finish the structure:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp writeStr
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py writeStr
|
||||
@end_toggle
|
||||
For maps the drill is the same however now we use the "{" and "}" delimiter characters:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp writeMap
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py writeMap
|
||||
@end_toggle
|
||||
To read from these we use the @ref cv::FileNode and the @ref cv::FileNodeIterator data
|
||||
structures. The [] operator of the @ref cv::FileStorage class (or the getNode() function in Python) returns a @ref cv::FileNode data
|
||||
type. If the node is sequential we can use the @ref cv::FileNodeIterator to iterate through the
|
||||
items. In Python, the at() function can be used to address elements of the sequence and the
|
||||
size() function returns the length of the sequence:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp readStr
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py readStr
|
||||
@end_toggle
|
||||
For maps you can use the [] operator (at() function in Python) again to access the given item (or the \>\> operator too):
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp readMap
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py readMap
|
||||
@end_toggle
|
||||
-# **Read and write your own data structures.** Suppose you have a data structure such as:
|
||||
@add_toggle_cpp
|
||||
@code{.cpp}
|
||||
class MyData
|
||||
{
|
||||
public:
|
||||
MyData() : A(0), X(0), id() {}
|
||||
public: // Data Members
|
||||
int A;
|
||||
double X;
|
||||
string id;
|
||||
};
|
||||
@endcode
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@code{.py}
|
||||
class MyData:
|
||||
def __init__(self):
|
||||
self.A = self.X = 0
|
||||
self.name = ''
|
||||
@endcode
|
||||
@end_toggle
|
||||
In C++, it's possible to serialize this through the OpenCV I/O XML/YAML interface (just as
|
||||
in case of the OpenCV data structures) by adding a read and a write function inside and outside of your
|
||||
class. In Python, you can get close to this by implementing a read and write function inside
|
||||
the class. For the inside part:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp inside
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py inside
|
||||
@end_toggle
|
||||
@add_toggle_cpp
|
||||
In C++, you need to add the following functions definitions outside the class:
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp outside
|
||||
@end_toggle
|
||||
Here you can observe that in the read section we defined what happens if the user tries to read
|
||||
a non-existing node. In this case we just return the default initialization value, however a
|
||||
more verbose solution would be to return for instance a minus one value for an object ID.
|
||||
|
||||
Once you added these four functions use the \>\> operator for write and the \<\< operator for
|
||||
read (or the defined input/output functions for Python):
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp customIOi
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp customIOw
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp customIO
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py customIOi
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py customIOw
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py customIO
|
||||
@end_toggle
|
||||
Or to try out reading a non-existing read:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/file_input_output/file_input_output.cpp nonexist
|
||||
@end_toggle
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/file_input_output/file_input_output.py nonexist
|
||||
@end_toggle
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
Well mostly we just print out the defined numbers. On the screen of your console you could see:
|
||||
@code{.bash}
|
||||
Write Done.
|
||||
|
||||
Reading:
|
||||
100image1.jpg
|
||||
Awesomeness
|
||||
baboon.jpg
|
||||
Two 2; One 1
|
||||
|
||||
|
||||
R = [1, 0, 0;
|
||||
0, 1, 0;
|
||||
0, 0, 1]
|
||||
T = [0; 0; 0]
|
||||
|
||||
MyData =
|
||||
{ id = mydata1234, X = 3.14159, A = 97}
|
||||
|
||||
Attempt to read NonExisting (should initialize the data structure with its default).
|
||||
NonExisting =
|
||||
{ id = , X = 0, A = 0}
|
||||
|
||||
Tip: Open up output.xml with a text editor to see the serialized data.
|
||||
@endcode
|
||||
Nevertheless, it's much more interesting what you may see in the output xml file:
|
||||
@code{.xml}
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<iterationNr>100</iterationNr>
|
||||
<strings>
|
||||
image1.jpg Awesomeness baboon.jpg</strings>
|
||||
<Mapping>
|
||||
<One>1</One>
|
||||
<Two>2</Two></Mapping>
|
||||
<R type_id="opencv-matrix">
|
||||
<rows>3</rows>
|
||||
<cols>3</cols>
|
||||
<dt>u</dt>
|
||||
<data>
|
||||
1 0 0 0 1 0 0 0 1</data></R>
|
||||
<T type_id="opencv-matrix">
|
||||
<rows>3</rows>
|
||||
<cols>1</cols>
|
||||
<dt>d</dt>
|
||||
<data>
|
||||
0. 0. 0.</data></T>
|
||||
<MyData>
|
||||
<A>97</A>
|
||||
<X>3.1415926535897931e+000</X>
|
||||
<id>mydata1234</id></MyData>
|
||||
</opencv_storage>
|
||||
@endcode
|
||||
Or the YAML file:
|
||||
@code{.yaml}
|
||||
%YAML:1.0
|
||||
iterationNr: 100
|
||||
strings:
|
||||
- "image1.jpg"
|
||||
- Awesomeness
|
||||
- "baboon.jpg"
|
||||
Mapping:
|
||||
One: 1
|
||||
Two: 2
|
||||
R: !!opencv-matrix
|
||||
rows: 3
|
||||
cols: 3
|
||||
dt: u
|
||||
data: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]
|
||||
T: !!opencv-matrix
|
||||
rows: 3
|
||||
cols: 1
|
||||
dt: d
|
||||
data: [ 0., 0., 0. ]
|
||||
MyData:
|
||||
A: 97
|
||||
X: 3.1415926535897931e+000
|
||||
id: mydata1234
|
||||
@endcode
|
||||
You may observe a runtime instance of this on the [YouTube
|
||||
here](https://www.youtube.com/watch?v=A4yqVnByMMM) .
|
||||
|
||||
@youtube{A4yqVnByMMM}
|
||||
@@ -0,0 +1,227 @@
|
||||
How to scan images, lookup tables and time measurement with OpenCV {#tutorial_how_to_scan_images}
|
||||
==================================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_mat_the_basic_image_container}
|
||||
@next_tutorial{tutorial_mat_mask_operations}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
We'll seek answers for the following questions:
|
||||
|
||||
- How to go through each and every pixel of an image?
|
||||
- How are OpenCV matrix values stored?
|
||||
- How to measure the performance of our algorithm?
|
||||
- What are lookup tables and why use them?
|
||||
|
||||
Our test case
|
||||
-------------
|
||||
|
||||
Let us consider a simple color reduction method. By using the unsigned char C and C++ type for
|
||||
matrix item storing, a channel of pixel may have up to 256 different values. For a three channel
|
||||
image this can allow the formation of way too many colors (16 million to be exact). Working with so
|
||||
many color shades may give a heavy blow to our algorithm performance. However, sometimes it is
|
||||
enough to work with a lot less of them to get the same final result.
|
||||
|
||||
In this cases it's common that we make a *color space reduction*. This means that we divide the
|
||||
color space current value with a new input value to end up with fewer colors. For instance every
|
||||
value between zero and nine takes the new value zero, every value between ten and nineteen the value
|
||||
ten and so on.
|
||||
|
||||
When you divide an *uchar* (unsigned char - aka values between zero and 255) value with an *int*
|
||||
value the result will be also *char*. These values may only be char values. Therefore, any fraction
|
||||
will be rounded down. Taking advantage of this fact the upper operation in the *uchar* domain may be
|
||||
expressed as:
|
||||
|
||||
\f[I_{new} = (\frac{I_{old}}{10}) * 10\f]
|
||||
|
||||
A simple color space reduction algorithm would consist of just passing through every pixel of an
|
||||
image matrix and applying this formula. It's worth noting that we do a divide and a multiplication
|
||||
operation. These operations are bloody expensive for a system. If possible it's worth avoiding them
|
||||
by using cheaper operations such as a few subtractions, addition or in best case a simple
|
||||
assignment. Furthermore, note that we only have a limited number of input values for the upper
|
||||
operation. In case of the *uchar* system this is 256 to be exact.
|
||||
|
||||
Therefore, for larger images it would be wise to calculate all possible values beforehand and during
|
||||
the assignment just make the assignment, by using a lookup table. Lookup tables are simple arrays
|
||||
(having one or more dimensions) that for a given input value variation holds the final output value.
|
||||
Its strength is that we do not need to make the calculation, we just need to read the result.
|
||||
|
||||
Our test case program (and the code sample below) will do the following: read in an image passed
|
||||
as a command line argument (it may be either color or grayscale) and apply the reduction
|
||||
with the given command line argument integer value. In OpenCV, at the moment there are
|
||||
three major ways of going through an image pixel by pixel. To make things a little more interesting
|
||||
we'll make the scanning of the image using each of these methods, and print out how long it took.
|
||||
|
||||
You can download the full source code [here
|
||||
](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/core/how_to_scan_images/how_to_scan_images.cpp) or look it up in
|
||||
the samples directory of OpenCV at the cpp tutorial code for the core section. Its basic usage is:
|
||||
@code{.bash}
|
||||
how_to_scan_images imageName.jpg intValueToReduce [G]
|
||||
@endcode
|
||||
The final argument is optional. If given the image will be loaded in grayscale format, otherwise
|
||||
the BGR color space is used. The first thing is to calculate the lookup table.
|
||||
|
||||
@snippet how_to_scan_images.cpp dividewith
|
||||
|
||||
Here we first use the C++ *stringstream* class to convert the third command line argument from text
|
||||
to an integer format. Then we use a simple look and the upper formula to calculate the lookup table.
|
||||
No OpenCV specific stuff here.
|
||||
|
||||
Another issue is how do we measure time? Well OpenCV offers two simple functions to achieve this
|
||||
cv::getTickCount() and cv::getTickFrequency() . The first returns the number of ticks of
|
||||
your systems CPU from a certain event (like since you booted your system). The second returns how
|
||||
many times your CPU emits a tick during a second. So, measuring amount of time elapsed between
|
||||
two operations is as easy as:
|
||||
@code{.cpp}
|
||||
double t = (double)getTickCount();
|
||||
// do something ...
|
||||
t = ((double)getTickCount() - t)/getTickFrequency();
|
||||
cout << "Times passed in seconds: " << t << endl;
|
||||
@endcode
|
||||
|
||||
@anchor tutorial_how_to_scan_images_storing
|
||||
How is the image matrix stored in memory?
|
||||
-----------------------------------------
|
||||
|
||||
As you could already read in my @ref tutorial_mat_the_basic_image_container tutorial the size of the matrix
|
||||
depends on the color system used. More accurately, it depends on the number of channels used. In
|
||||
case of a grayscale image we have something like:
|
||||
|
||||

|
||||
|
||||
For multichannel images the columns contain as many sub columns as the number of channels. For
|
||||
example in case of an BGR color system:
|
||||
|
||||

|
||||
|
||||
Note that the order of the channels is inverse: BGR instead of RGB. Because in many cases the memory
|
||||
is large enough to store the rows in a successive fashion the rows may follow one after another,
|
||||
creating a single long row. Because everything is in a single place following one after another this
|
||||
may help to speed up the scanning process. We can use the cv::Mat::isContinuous() function to *ask*
|
||||
the matrix if this is the case. Continue on to the next section to find an example.
|
||||
|
||||
The efficient way
|
||||
-----------------
|
||||
|
||||
When it comes to performance you cannot beat the classic C style operator[] (pointer) access.
|
||||
Therefore, the most efficient method we can recommend for making the assignment is:
|
||||
|
||||
@snippet how_to_scan_images.cpp scan-c
|
||||
|
||||
Here we basically just acquire a pointer to the start of each row and go through it until it ends.
|
||||
In the special case that the matrix is stored in a continuous manner we only need to request the
|
||||
pointer a single time and go all the way to the end. We need to look out for color images: we have
|
||||
three channels so we need to pass through three times more items in each row.
|
||||
|
||||
There's another way of this. The *data* data member of a *Mat* object returns the pointer to the
|
||||
first row, first column. If this pointer is null you have no valid input in that object. Checking
|
||||
this is the simplest method to check if your image loading was a success. In case the storage is
|
||||
continuous we can use this to go through the whole data pointer. In case of a grayscale image this
|
||||
would look like:
|
||||
@code{.cpp}
|
||||
uchar* p = I.data;
|
||||
|
||||
for( unsigned int i = 0; i < ncol*nrows; ++i)
|
||||
*p++ = table[*p];
|
||||
@endcode
|
||||
You would get the same result. However, this code is a lot harder to read later on. It gets even
|
||||
harder if you have some more advanced technique there. Moreover, in practice I've observed you'll
|
||||
get the same performance result (as most of the modern compilers will probably make this small
|
||||
optimization trick automatically for you).
|
||||
|
||||
The iterator (safe) method
|
||||
--------------------------
|
||||
|
||||
In case of the efficient way making sure that you pass through the right amount of *uchar* fields
|
||||
and to skip the gaps that may occur between the rows was your responsibility. The iterator method is
|
||||
considered a safer way as it takes over these tasks from the user. All you need to do is to ask the
|
||||
begin and the end of the image matrix and then just increase the begin iterator until you reach the
|
||||
end. To acquire the value *pointed* by the iterator use the \* operator (add it before it).
|
||||
|
||||
@snippet how_to_scan_images.cpp scan-iterator
|
||||
|
||||
In case of color images we have three uchar items per column. This may be considered a short vector
|
||||
of uchar items, that has been baptized in OpenCV with the *Vec3b* name. To access the n-th sub
|
||||
column we use simple operator[] access. It's important to remember that OpenCV iterators go through
|
||||
the columns and automatically skip to the next row. Therefore in case of color images if you use a
|
||||
simple *uchar* iterator you'll be able to access only the blue channel values.
|
||||
|
||||
On-the-fly address calculation with reference returning
|
||||
-------------------------------------------------------
|
||||
|
||||
The final method isn't recommended for scanning. It was made to acquire or modify somehow random
|
||||
elements in the image. Its basic usage is to specify the row and column number of the item you want
|
||||
to access. During our earlier scanning methods you could already notice that it is important through
|
||||
what type we are looking at the image. It's no different here as you need to manually specify what
|
||||
type to use at the automatic lookup. You can observe this in case of the grayscale images for the
|
||||
following source code (the usage of the + cv::Mat::at() function):
|
||||
|
||||
@snippet how_to_scan_images.cpp scan-random
|
||||
|
||||
The function takes your input type and coordinates and calculates the address of the
|
||||
queried item. Then returns a reference to that. This may be a constant when you *get* the value and
|
||||
non-constant when you *set* the value. As a safety step in **debug mode only**\* there is a check
|
||||
performed that your input coordinates are valid and do exist. If this isn't the case you'll get a
|
||||
nice output message of this on the standard error output stream. Compared to the efficient way in
|
||||
release mode the only difference in using this is that for every element of the image you'll get a
|
||||
new row pointer for what we use the C operator[] to acquire the column element.
|
||||
|
||||
If you need to do multiple lookups using this method for an image it may be troublesome and time
|
||||
consuming to enter the type and the at keyword for each of the accesses. To solve this problem
|
||||
OpenCV has a cv::Mat_ data type. It's the same as Mat with the extra need that at definition
|
||||
you need to specify the data type through what to look at the data matrix, however in return you can
|
||||
use the operator() for fast access of items. To make things even better this is easily convertible
|
||||
from and to the usual cv::Mat data type. A sample usage of this you can see in case of the
|
||||
color images of the function above. Nevertheless, it's important to note that the same operation
|
||||
(with the same runtime speed) could have been done with the cv::Mat::at function. It's just a less
|
||||
to write for the lazy programmer trick.
|
||||
|
||||
The Core Function
|
||||
-----------------
|
||||
|
||||
This is a bonus method of achieving lookup table modification in an image. In image
|
||||
processing it's quite common that you want to modify all of a given image values to some other value.
|
||||
OpenCV provides a function for modifying image values, without the need to write the scanning logic
|
||||
of the image. We use the cv::LUT() function of the core module. First we build a Mat type of the
|
||||
lookup table:
|
||||
|
||||
@snippet how_to_scan_images.cpp table-init
|
||||
|
||||
Finally call the function (I is our input image and J the output one):
|
||||
|
||||
@snippet how_to_scan_images.cpp table-use
|
||||
|
||||
Performance Difference
|
||||
----------------------
|
||||
|
||||
For the best result compile the program and run it yourself. To make the differences more
|
||||
clear, I've used a quite large (2560 X 1600) image. The performance presented here are for
|
||||
color images. For a more accurate value I've averaged the value I got from the call of the function
|
||||
for hundred times.
|
||||
|
||||
Method | Time
|
||||
--------------- | ----------------------
|
||||
Efficient Way | 79.4717 milliseconds
|
||||
Iterator | 83.7201 milliseconds
|
||||
On-The-Fly RA | 93.7878 milliseconds
|
||||
LUT function | 32.5759 milliseconds
|
||||
|
||||
We can conclude a couple of things. If possible, use the already made functions of OpenCV (instead
|
||||
of reinventing these). The fastest method turns out to be the LUT function. This is because the OpenCV
|
||||
library is multi-thread enabled via Intel Threaded Building Blocks. However, if you need to write a
|
||||
simple image scan prefer the pointer method. The iterator is a safer bet, however quite slower.
|
||||
Using the on-the-fly reference access method for full image scan is the most costly in debug mode.
|
||||
In the release mode it may beat the iterator approach or not, however it surely sacrifices for this
|
||||
the safety trait of iterators.
|
||||
|
||||
Finally, you may watch a sample run of the program on the [video posted](https://www.youtube.com/watch?v=fB3AN5fjgwc) on our YouTube channel.
|
||||
|
||||
@youtube{fB3AN5fjgwc}
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,201 @@
|
||||
How to use the OpenCV parallel_for_ function to parallelize your code (Mandelbrot set example) {#tutorial_how_to_use_OpenCV_parallel_for_}
|
||||
==================================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_file_input_output_with_xml_yml}
|
||||
@next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_new}
|
||||
@next_tutorial{tutorial_univ_intrin}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
|
||||
@note See this [tuturial](@ref tutorial_how_to_use_OpenCV_parallel_for_new) for a `parallel_for_` usage applied to image convolution.
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
The goal of this tutorial is to show you how to use the OpenCV `parallel_for_` framework to easily
|
||||
parallelize your code. To illustrate the concept, we will write a program to draw a Mandelbrot set
|
||||
exploiting almost all the CPU load available.
|
||||
The full tutorial code is [here](https://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp).
|
||||
If you want more information about multithreading, you will have to refer to a reference book or course as this tutorial is intended
|
||||
to remain simple.
|
||||
|
||||
Precondition
|
||||
------------
|
||||
|
||||
The first precondition is to have OpenCV built with a parallel framework.
|
||||
In OpenCV 4, the following parallel frameworks are available in that order:
|
||||
|
||||
1. Intel Threading Building Blocks (3rdparty library, should be explicitly enabled)
|
||||
2. OpenMP (integrated to compiler, should be explicitly enabled)
|
||||
3. APPLE GCD (system wide, used automatically (APPLE only))
|
||||
4. Windows RT concurrency (system wide, used automatically (Windows RT only))
|
||||
5. Windows concurrency (part of runtime, used automatically (Windows only - MSVC++ >= 10))
|
||||
6. Pthreads (if available)
|
||||
|
||||
As you can see, several parallel frameworks can be used in the OpenCV library. Some parallel libraries
|
||||
are third party libraries and have to be explicitly built and enabled in CMake (e.g. TBB), others are
|
||||
automatically available with the platform (e.g. APPLE GCD) but chances are that you should be enable to
|
||||
have access to a parallel framework either directly or by enabling the option in CMake and rebuild the library.
|
||||
|
||||
The second (weak) precondition is more related to the task you want to achieve as not all computations
|
||||
are suitable / can be adapted to be run in a parallel way. To remain simple, tasks that can be split
|
||||
into multiple elementary operations with no memory dependency (no possible race condition) are easily
|
||||
parallelizable. Computer vision processing are often easily parallelizable as most of the time the processing of
|
||||
one pixel does not depend to the state of other pixels.
|
||||
|
||||
Simple example: drawing a Mandelbrot set
|
||||
----
|
||||
|
||||
We will use the example of drawing a Mandelbrot set to show how from a regular sequential code you can easily adapt
|
||||
the code to parallelize the computation.
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
The Mandelbrot set definition has been named in tribute to the mathematician Benoit Mandelbrot by the mathematician
|
||||
Adrien Douady. It has been famous outside of the mathematics field as the image representation is an example of a
|
||||
class of fractals, a mathematical set that exhibits a repeating pattern displayed at every scale (even more, a
|
||||
Mandelbrot set is self-similar as the whole shape can be repeatedly seen at different scale). For a more in-depth
|
||||
introduction, you can look at the corresponding [Wikipedia article](https://en.wikipedia.org/wiki/Mandelbrot_set).
|
||||
Here, we will just introduce the formula to draw the Mandelbrot set (from the mentioned Wikipedia article).
|
||||
|
||||
> The Mandelbrot set is the set of values of \f$ c \f$ in the complex plane for which the orbit of 0 under iteration
|
||||
> of the quadratic map
|
||||
> \f[\begin{cases} z_0 = 0 \\ z_{n+1} = z_n^2 + c \end{cases}\f]
|
||||
> remains bounded.
|
||||
> That is, a complex number \f$ c \f$ is part of the Mandelbrot set if, when starting with \f$ z_0 = 0 \f$ and applying
|
||||
> the iteration repeatedly, the absolute value of \f$ z_n \f$ remains bounded however large \f$ n \f$ gets.
|
||||
> This can also be represented as
|
||||
> \f[\limsup_{n\to\infty}|z_{n+1}|\leqslant2\f]
|
||||
|
||||
Pseudocode
|
||||
----------
|
||||
|
||||
A simple algorithm to generate a representation of the Mandelbrot set is called the
|
||||
["escape time algorithm"](https://en.wikipedia.org/wiki/Mandelbrot_set#Escape_time_algorithm).
|
||||
For each pixel in the rendered image, we test using the recurrence relation if the complex number is bounded or not
|
||||
under a maximum number of iterations. Pixels that do not belong to the Mandelbrot set will escape quickly whereas
|
||||
we assume that the pixel is in the set after a fixed maximum number of iterations. A high value of iterations will
|
||||
produce a more detailed image but the computation time will increase accordingly. We use the number of iterations
|
||||
needed to "escape" to depict the pixel value in the image.
|
||||
|
||||
```
|
||||
For each pixel (Px, Py) on the screen, do:
|
||||
{
|
||||
x0 = scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2, 1))
|
||||
y0 = scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1))
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
iteration = 0
|
||||
max_iteration = 1000
|
||||
while (x*x + y*y < 2*2 AND iteration < max_iteration) {
|
||||
xtemp = x*x - y*y + x0
|
||||
y = 2*x*y + y0
|
||||
x = xtemp
|
||||
iteration = iteration + 1
|
||||
}
|
||||
color = palette[iteration]
|
||||
plot(Px, Py, color)
|
||||
}
|
||||
```
|
||||
|
||||
To relate between the pseudocode and the theory, we have:
|
||||
* \f$ z = x + iy \f$
|
||||
* \f$ z^2 = x^2 + i2xy - y^2 \f$
|
||||
* \f$ c = x_0 + iy_0 \f$
|
||||
|
||||

|
||||
|
||||
On this figure, we recall that the real part of a complex number is on the x-axis and the imaginary part on the y-axis.
|
||||
You can see that the whole shape can be repeatedly visible if we zoom at particular locations.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
Escape time algorithm implementation
|
||||
------------------------------------
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-escape-time-algorithm
|
||||
|
||||
Here, we used the [`std::complex`](https://en.cppreference.com/cpp/numeric/complex) template class to represent a
|
||||
complex number. This function performs the test to check if the pixel is in set or not and returns the "escaped" iteration.
|
||||
|
||||
Sequential Mandelbrot implementation
|
||||
------------------------------------
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-sequential
|
||||
|
||||
In this implementation, we sequentially iterate over the pixels in the rendered image to perform the test to check if the
|
||||
pixel is likely to belong to the Mandelbrot set or not.
|
||||
|
||||
Another thing to do is to transform the pixel coordinate into the Mandelbrot set space with:
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-transformation
|
||||
|
||||
Finally, to assign the grayscale value to the pixels, we use the following rule:
|
||||
* a pixel is black if it reaches the maximum number of iterations (pixel is assumed to be in the Mandelbrot set),
|
||||
* otherwise we assign a grayscale value depending on the escaped iteration and scaled to fit the grayscale range.
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-grayscale-value
|
||||
|
||||
Using a linear scale transformation is not enough to perceive the grayscale variation. To overcome this, we will boost
|
||||
the perception by using a square root scale transformation (borrowed from Jeremy D. Frens in his
|
||||
[blog post](https://web.archive.org/web/20250419124416/http://www.programming-during-recess.net/2016/06/26/color-schemes-for-mandelbrot-sets/)):
|
||||
\f$ f \left( x \right) = \sqrt{\frac{x}{\text{maxIter}}} \times 255 \f$
|
||||
|
||||

|
||||
|
||||
The green curve corresponds to a simple linear scale transformation, the blue one to a square root scale transformation
|
||||
and you can observe how the lowest values will be boosted when looking at the slope at these positions.
|
||||
|
||||
Parallel Mandelbrot implementation
|
||||
----------------------------------
|
||||
|
||||
When looking at the sequential implementation, we can notice that each pixel is computed independently. To optimize the
|
||||
computation, we can perform multiple pixel calculations in parallel, by exploiting the multi-core architecture of modern
|
||||
processor. To achieve this easily, we will use the OpenCV @ref cv::parallel_for_ framework.
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel
|
||||
|
||||
The first thing is to declare a custom class that inherits from @ref cv::ParallelLoopBody and to override the
|
||||
`virtual void operator ()(const cv::Range& range) const`.
|
||||
|
||||
The range in the `operator ()` represents the subset of pixels that will be treated by an individual thread.
|
||||
This splitting is done automatically to distribute equally the computation load. We have to convert the pixel index coordinate
|
||||
to a 2D `[row, col]` coordinate. Also note that we have to keep a reference on the mat image to be able to modify in-place
|
||||
the image.
|
||||
|
||||
The parallel execution is called with:
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel-call
|
||||
|
||||
Here, the range represents the total number of operations to be executed, so the total number of pixels in the image.
|
||||
To set the number of threads, you can use: @ref cv::setNumThreads. You can also specify the number of splitting using the
|
||||
nstripes parameter in @ref cv::parallel_for_. For instance, if your processor has 4 threads, setting `cv::setNumThreads(2)`
|
||||
or setting `nstripes=2` should be the same as by default it will use all the processor threads available but will split the
|
||||
workload only on two threads.
|
||||
|
||||
@note
|
||||
C++ 11 standard allows simplifying the parallel implementation by get rid of the `ParallelMandelbrot` class and replacing it with lambda expression:
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel-call-cxx11
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
You can find the full tutorial code [here](https://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp).
|
||||
The performance of the parallel implementation depends of the type of CPU you have. For instance, on 4 cores / 8 threads
|
||||
CPU, you can expect a speed-up of around 6.9X. There are many factors to explain why we do not achieve a speed-up of almost 8X.
|
||||
Main reasons should be mostly due to:
|
||||
* the overhead to create and manage the threads,
|
||||
* background processes running in parallel,
|
||||
* the difference between 4 hardware cores with 2 logical threads for each core and 8 hardware cores.
|
||||
|
||||
The resulting image produced by the tutorial code (you can modify the code to use more iterations and assign a pixel color
|
||||
depending on the escaped iteration and using a color palette to get more aesthetic images):
|
||||

|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,167 @@
|
||||
How to use the OpenCV parallel_for_ function to parallelize your code (convolution example) {#tutorial_how_to_use_OpenCV_parallel_for_new}
|
||||
==================================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_file_input_output_with_xml_yml}
|
||||
@prev_tutorial{tutorial_how_to_use_OpenCV_parallel_for_}
|
||||
@next_tutorial{tutorial_univ_intrin}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
The goal of this tutorial is to demonstrate the use of the OpenCV `parallel_for_` framework to easily parallelize your code. To illustrate the concept, we will write a program to perform convolution operation over an image.
|
||||
The full tutorial code is [here](https://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_new.cpp).
|
||||
|
||||
Precondition
|
||||
----
|
||||
|
||||
### Parallel Frameworks
|
||||
The first precondition is to have OpenCV built with a parallel framework.
|
||||
In OpenCV 4.5, the following parallel frameworks are available in that order:
|
||||
|
||||
* Intel Threading Building Blocks (3rdparty library, should be explicitly enabled)
|
||||
* OpenMP (integrated to compiler, should be explicitly enabled)
|
||||
* APPLE GCD (system wide, used automatically (APPLE only))
|
||||
* Windows RT concurrency (system wide, used automatically (Windows RT only))
|
||||
* Windows concurrency (part of runtime, used automatically (Windows only - MSVC++ >= 10))
|
||||
* Pthreads
|
||||
|
||||
As you can see, several parallel frameworks can be used in the OpenCV library. Some parallel libraries are third party libraries and have to be explicitly enabled in CMake before building, while others are automatically available with the platform (e.g. APPLE GCD).
|
||||
|
||||
### Race Conditions
|
||||
Race conditions occur when more than one thread try to write *or* read and write to a particular memory location simultaneously.
|
||||
Based on that, we can broadly classify algorithms into two categories:-
|
||||
1. Algorithms in which only a single thread writes data to a particular memory location.
|
||||
* In *convolution*, for example, even though multiple threads may read from a pixel at a particular time, only a single thread *writes* to a particular pixel.
|
||||
|
||||
2. Algorithms in which multiple threads may write to a single memory location.
|
||||
* Finding contours, features, etc. Such algorithms may require each thread to add data to a global variable simultaneously. For example, when detecting features, each thread will add features of their respective parts of the image to a common vector, thus creating a race condition.
|
||||
|
||||
Convolution
|
||||
-----------
|
||||
|
||||
We will use the example of performing a convolution to demonstrate the use of `parallel_for_` to parallelize the computation. This is an example of an algorithm which does not lead to a race condition.
|
||||
|
||||
Theory
|
||||
------
|
||||
Convolution is a simple mathematical operation widely used in image processing. Here, we slide a smaller matrix, called the *kernel*, over an image and a sum of the product of pixel values and corresponding values in the kernel gives us the value of the particular pixel in the output (called the anchor point of the kernel). Based on the values in the kernel, we get different results.
|
||||
In the example below, we use a 3x3 kernel (anchored at its center) and convolve over a 5x5 matrix to produce a 3x3 matrix. The size of the output can be altered by padding the input with suitable values.
|
||||

|
||||
|
||||
For more information about different kernels and what they do, look [here](https://en.wikipedia.org/wiki/Kernel_(image_processing))
|
||||
|
||||
For the purpose of this tutorial, we will implement the simplest form of the function which takes a grayscale image (1 channel) and an odd length square kernel and produces an output image.
|
||||
The operation will not be performed in-place.
|
||||
@note We can store a few of the relevant pixels temporarily to make sure we use the original values during the convolution and then do it in-place. However, the purpose of this tutorial is to introduce parallel_for_ function and an inplace implementation may be too complicated.
|
||||
|
||||
Pseudocode
|
||||
-----------
|
||||
|
||||
InputImage src, OutputImage dst, kernel(size n)
|
||||
makeborder(src, n/2)
|
||||
for each pixel (i, j) strictly inside borders, do:
|
||||
{
|
||||
value := 0
|
||||
for k := -n/2 to n/2, do:
|
||||
for l := -n/2 to n/2, do:
|
||||
value += kernel[n/2 + k][n/2 + l]*src[i + k][j + l]
|
||||
|
||||
dst[i][j] := value
|
||||
}
|
||||
|
||||
For an *n-sized kernel*, we will add a border of size *n/2* to handle edge cases.
|
||||
We then run two loops to move along the kernel and add the products to sum
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
### Sequential implementation
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-sequential
|
||||
|
||||
We first make an output matrix(dst) with the same size as src and add borders to the src image(to handle edge cases).
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-make-borders
|
||||
|
||||
We then sequentially iterate over the pixels in the src image and compute the value over the kernel and the neighbouring pixel values.
|
||||
We then fill value to the corresponding pixel in the dst image.
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-kernel-loop
|
||||
|
||||
### Parallel implementation
|
||||
|
||||
When looking at the sequential implementation, we can notice that each pixel depends on multiple neighbouring pixels but only one pixel is edited at a time. Thus, to optimize the computation, we can split the image into stripes and parallelly perform convolution on each, by exploiting the multi-core architecture of modern processor. The OpenCV @ref cv::parallel_for_ framework automatically decides how to split the computation efficiently and does most of the work for us.
|
||||
|
||||
@note Although values of a pixel in a particular stripe may depend on pixel values outside the stripe, these are only read only operations and hence will not cause undefined behaviour.
|
||||
|
||||
|
||||
We first declare a custom class that inherits from @ref cv::ParallelLoopBody and override the `virtual void operator ()(const cv::Range& range) const`.
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-parallel
|
||||
|
||||
The range in the `operator ()` represents the subset of values that will be treated by an individual thread. Based on the requirement, there may be different ways of splitting the range which in turn changes the computation.
|
||||
|
||||
For example, we can either
|
||||
1. Split the entire traversal of the image and obtain the [row, col] coordinate in the following way (as shown in the above code):
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp overload-full
|
||||
|
||||
We would then call the parallel_for_ function in the following way:
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-parallel-function
|
||||
<br>
|
||||
|
||||
2. Split the rows and compute for each row:
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp overload-row-split
|
||||
|
||||
In this case, we call the parallel_for_ function with a different range:
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-parallel-function-row
|
||||
|
||||
@note In our case, both implementations perform similarly. Some cases may allow better memory access patterns or other performance benefits.
|
||||
|
||||
To set the number of threads, you can use: @ref cv::setNumThreads. You can also specify the number of splitting using the nstripes parameter in @ref cv::parallel_for_. For instance, if your processor has 4 threads, setting `cv::setNumThreads(2)` or setting `nstripes=2` should be the same as by default it will use all the processor threads available but will split the workload only on two threads.
|
||||
|
||||
@note C++ 11 standard allows simplifying the parallel implementation by getting rid of the `parallelConvolution` class and replacing it with lambda expression:
|
||||
|
||||
@snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-parallel-cxx11
|
||||
|
||||
Results
|
||||
-----------
|
||||
|
||||
The resulting time taken for execution of the two implementations on a
|
||||
* *512x512 input* with a *5x5 kernel*:
|
||||
|
||||
This program shows how to use the OpenCV parallel_for_ function and
|
||||
compares the performance of the sequential and parallel implementations for a
|
||||
convolution operation
|
||||
Usage:
|
||||
./a.out [image_path -- default lena.jpg]
|
||||
|
||||
Sequential Implementation: 0.0953564s
|
||||
Parallel Implementation: 0.0246762s
|
||||
Parallel Implementation(Row Split): 0.0248722s
|
||||
|
||||
<br>
|
||||
|
||||
* *512x512 input with a 3x3 kernel*
|
||||
|
||||
This program shows how to use the OpenCV parallel_for_ function and
|
||||
compares the performance of the sequential and parallel implementations for a
|
||||
convolution operation
|
||||
Usage:
|
||||
./a.out [image_path -- default lena.jpg]
|
||||
|
||||
Sequential Implementation: 0.0301325s
|
||||
Parallel Implementation: 0.0117053s
|
||||
Parallel Implementation(Row Split): 0.0117894s
|
||||
|
||||
The performance of the parallel implementation depends on the type of CPU you have. For instance, on 4 cores - 8 threads CPU, runtime may be 6x to 7x faster than a sequential implementation. There are many factors to explain why we do not achieve a speed-up of 8x:
|
||||
* the overhead to create and manage the threads,
|
||||
* background processes running in parallel,
|
||||
* the difference between 4 hardware cores with 2 logical threads for each core and 8 hardware cores.
|
||||
|
||||
In the tutorial, we used a horizontal gradient filter(as shown in the animation above), which produces an image highlighting the vertical edges.
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,201 @@
|
||||
Mask operations on matrices {#tutorial_mat_mask_operations}
|
||||
===========================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_how_to_scan_images}
|
||||
@next_tutorial{tutorial_mat_operations}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Mask operations on matrices are quite simple. The idea is that we recalculate each pixel's value in
|
||||
an image according to a mask matrix (also known as kernel). This mask holds values that will adjust
|
||||
how much influence neighboring pixels (and the current pixel) have on the new pixel value. From a
|
||||
mathematical point of view we make a weighted average, with our specified values.
|
||||
|
||||
Our test case
|
||||
-------------
|
||||
|
||||
Let's consider the issue of an image contrast enhancement method. Basically we want to apply for
|
||||
every pixel of the image the following formula:
|
||||
|
||||
\f[I(i,j) = 5*I(i,j) - [ I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]\f]\f[\iff I(i,j)*M, \text{where }
|
||||
M = \bordermatrix{ _i\backslash ^j & -1 & 0 & +1 \cr
|
||||
-1 & 0 & -1 & 0 \cr
|
||||
0 & -1 & 5 & -1 \cr
|
||||
+1 & 0 & -1 & 0 \cr
|
||||
}\f]
|
||||
|
||||
The first notation is by using a formula, while the second is a compacted version of the first by
|
||||
using a mask. You use the mask by putting the center of the mask matrix (in the upper case noted by
|
||||
the zero-zero index) on the pixel you want to calculate and sum up the pixel values multiplied with
|
||||
the overlapped matrix values. It's the same thing, however in case of large matrices the latter
|
||||
notation is a lot easier to look over.
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@add_toggle_cpp
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp`.
|
||||
@include samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java`.
|
||||
@include samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py`.
|
||||
@include samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py
|
||||
@end_toggle
|
||||
|
||||
The Basic Method
|
||||
----------------
|
||||
|
||||
Now let us see how we can make this happen by using the basic pixel access method or by using the
|
||||
**filter2D()** function.
|
||||
|
||||
Here's a function that will do this:
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp basic_method
|
||||
|
||||
At first we make sure that the input images data is in unsigned char format. For this we use the
|
||||
@ref CV_Assert function (macro) that throws an error when the expression inside it is false.
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp 8_bit
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java basic_method
|
||||
|
||||
At first we make sure that the input images data in unsigned 8 bit format.
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java 8_bit
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py basic_method
|
||||
|
||||
At first we make sure that the input images data in unsigned 8 bit format.
|
||||
@code{.py}
|
||||
my_image = cv.cvtColor(my_image, cv.CV_8U)
|
||||
@endcode
|
||||
|
||||
@end_toggle
|
||||
|
||||
We create an output image with the same size and the same type as our input. As you can see in the
|
||||
@ref tutorial_how_to_scan_images_storing "storing" section, depending on the number of channels we may have one or more
|
||||
subcolumns.
|
||||
|
||||
@add_toggle_cpp
|
||||
We will iterate through them via pointers so the total number of elements depends on
|
||||
this number.
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp create_channels
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java create_channels
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@code{.py}
|
||||
height, width, n_channels = my_image.shape
|
||||
result = np.zeros(my_image.shape, my_image.dtype)
|
||||
@endcode
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_cpp
|
||||
We'll use the plain C [] operator to access pixels. Because we need to access multiple rows at the
|
||||
same time we'll acquire the pointers for each of them (a previous, a current and a next line). We
|
||||
need another pointer to where we're going to save the calculation. Then simply access the right
|
||||
items with the [] operator. For moving the output pointer ahead we simply increase this (with one
|
||||
byte) after each operation:
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp basic_method_loop
|
||||
|
||||
On the borders of the image the upper notation results inexistent pixel locations (like minus one -
|
||||
minus one). In these points our formula is undefined. A simple solution is to not apply the kernel
|
||||
in these points and, for example, set the pixels on the borders to zeros:
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp borders
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
We need to access multiple rows and columns which can be done by adding or subtracting 1 to the current center (i,j).
|
||||
Then we apply the sum and put the new value in the Result matrix.
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java basic_method_loop
|
||||
|
||||
On the borders of the image the upper notation results in inexistent pixel locations (like (-1,-1)).
|
||||
In these points our formula is undefined. A simple solution is to not apply the kernel
|
||||
in these points and, for example, set the pixels on the borders to zeros:
|
||||
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java borders
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
We need to access multiple rows and columns which can be done by adding or subtracting 1 to the current center (i,j).
|
||||
Then we apply the sum and put the new value in the Result matrix.
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py basic_method_loop
|
||||
@end_toggle
|
||||
|
||||
The filter2D function
|
||||
---------------------
|
||||
|
||||
Applying such filters are so common in image processing that in OpenCV there is a function that
|
||||
will take care of applying the mask (also called a kernel in some places). For this you first need
|
||||
to define an object that holds the mask:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp kern
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java kern
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py kern
|
||||
@end_toggle
|
||||
|
||||
Then call the **filter2D()** function specifying the input, the output image and the kernel to
|
||||
use:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp filter2D
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java filter2D
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py filter2D
|
||||
@end_toggle
|
||||
|
||||
The function even has a fifth optional argument to specify the center of the kernel, a sixth
|
||||
for adding an optional value to the filtered pixels before storing them in K and a seventh one
|
||||
for determining what to do in the regions where the operation is undefined (borders).
|
||||
|
||||
This function is shorter, less verbose and, because there are some optimizations, it is usually faster
|
||||
than the *hand-coded method*. For example in my test while the second one took only 13
|
||||
milliseconds the first took around 31 milliseconds. Quite some difference.
|
||||
|
||||
For example:
|
||||
|
||||

|
||||
|
||||
@add_toggle_cpp
|
||||
Check out an instance of running the program on our [YouTube
|
||||
channel](http://www.youtube.com/watch?v=7PF1tAU9se4) .
|
||||
@youtube{7PF1tAU9se4}
|
||||
@end_toggle
|
||||
@@ -0,0 +1,270 @@
|
||||
Operations with images {#tutorial_mat_operations}
|
||||
======================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_mat_mask_operations}
|
||||
@next_tutorial{tutorial_adding_images}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Input/Output
|
||||
------------
|
||||
|
||||
### Images
|
||||
|
||||
Load an image from a file:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Load an image from a file
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Load an image from a file
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Load an image from a file
|
||||
@end_toggle
|
||||
|
||||
If you read a jpg file, a 3 channel image is created by default. If you need a grayscale image, use:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Load an image from a file in grayscale
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Load an image from a file in grayscale
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Load an image from a file in grayscale
|
||||
@end_toggle
|
||||
|
||||
@note Format of the file is determined by its content (first few bytes). To save an image to a file:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Save image
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Save image
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Save image
|
||||
@end_toggle
|
||||
|
||||
@note Format of the file is determined by its extension.
|
||||
|
||||
@note Use cv::imdecode and cv::imencode to read and write an image from/to memory rather than a file.
|
||||
|
||||
Basic operations with images
|
||||
----------------------------
|
||||
|
||||
### Accessing pixel intensity values
|
||||
|
||||
In order to get pixel intensity value, you have to know the type of an image and the number of
|
||||
channels. Here is an example for a single channel grey scale image (type 8UC1) and pixel coordinates
|
||||
x and y:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Pixel access 1
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Pixel access 1
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Pixel access 1
|
||||
@end_toggle
|
||||
|
||||
C++ version only:
|
||||
intensity.val[0] contains a value from 0 to 255. Note the ordering of x and y. Since in OpenCV
|
||||
images are represented by the same structure as matrices, we use the same convention for both
|
||||
cases - the 0-based row index (or y-coordinate) goes first and the 0-based column index (or
|
||||
x-coordinate) follows it. Alternatively, you can use the following notation (**C++ only**):
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Pixel access 2
|
||||
|
||||
Now let us consider a 3 channel image with BGR color ordering (the default format returned by
|
||||
imread):
|
||||
|
||||
**C++ code**
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Pixel access 3
|
||||
|
||||
**Python Python**
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Pixel access 3
|
||||
|
||||
You can use the same method for floating-point images (for example, you can get such an image by
|
||||
running Sobel on a 3 channel image) (**C++ only**):
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Pixel access 4
|
||||
|
||||
The same method can be used to change pixel intensities:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Pixel access 5
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Pixel access 5
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Pixel access 5
|
||||
@end_toggle
|
||||
|
||||
There are functions in OpenCV, especially from calib3d module, such as cv::projectPoints, that take an
|
||||
array of 2D or 3D points in the form of Mat. Matrix should contain exactly one column, each row
|
||||
corresponds to a point, matrix type should be 32FC2 or 32FC3 correspondingly. Such a matrix can be
|
||||
easily constructed from `std::vector` (**C++ only**):
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Mat from points vector
|
||||
|
||||
One can access a point in this matrix using the same method `Mat::at` (**C++ only**):
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Point access
|
||||
|
||||
### Memory management and reference counting
|
||||
|
||||
Mat is a structure that keeps matrix/image characteristics (rows and columns number, data type etc)
|
||||
and a pointer to data. So nothing prevents us from having several instances of Mat corresponding to
|
||||
the same data. A Mat keeps a reference count that tells if data has to be deallocated when a
|
||||
particular instance of Mat is destroyed. Here is an example of creating two matrices without copying
|
||||
data (**C++ only**):
|
||||
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Reference counting 1
|
||||
|
||||
As a result, we get a 32FC1 matrix with 3 columns instead of 32FC3 matrix with 1 column. `pointsMat`
|
||||
uses data from points and will not deallocate the memory when destroyed. In this particular
|
||||
instance, however, developer has to make sure that lifetime of `points` is longer than of `pointsMat`
|
||||
If we need to copy the data, this is done using, for example, cv::Mat::copyTo or cv::Mat::clone:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Reference counting 2
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Reference counting 2
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Reference counting 2
|
||||
@end_toggle
|
||||
|
||||
An empty output Mat can be supplied to each function.
|
||||
Each implementation calls Mat::create for a destination matrix.
|
||||
This method allocates data for a matrix if it is empty.
|
||||
If it is not empty and has the correct size and type, the method does nothing.
|
||||
If however, size or type are different from the input arguments, the data is deallocated (and lost) and a new data is allocated.
|
||||
For example:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Reference counting 3
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Reference counting 3
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Reference counting 3
|
||||
@end_toggle
|
||||
|
||||
### Primitive operations
|
||||
|
||||
There is a number of convenient operators defined on a matrix. For example, here is how we can make
|
||||
a black image from an existing greyscale image `img`
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Set image to black
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Set image to black
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Set image to black
|
||||
@end_toggle
|
||||
|
||||
Selecting a region of interest:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Select ROI
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Select ROI
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Select ROI
|
||||
@end_toggle
|
||||
|
||||
Conversion from color to greyscale:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp BGR to Gray
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java BGR to Gray
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py BGR to Gray
|
||||
@end_toggle
|
||||
|
||||
Change image type from 8UC1 to 32FC1:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp Convert to CV_32F
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java Convert to CV_32F
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py Convert to CV_32F
|
||||
@end_toggle
|
||||
|
||||
### Visualizing images
|
||||
|
||||
It is very useful to see intermediate results of your algorithm during development process. OpenCV
|
||||
provides a convenient way of visualizing images. A 8U image can be shown using:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp imshow 1
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java imshow 1
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py imshow 1
|
||||
@end_toggle
|
||||
|
||||
A call to waitKey() starts a message passing cycle that waits for a key stroke in the "image"
|
||||
window. A 32F image needs to be converted to 8U type. For example:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_operations/mat_operations.cpp imshow 2
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_operations/MatOperations.java imshow 2
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_operations/mat_operations.py imshow 2
|
||||
@end_toggle
|
||||
|
||||
@note Here cv::namedWindow is not necessary since it is immediately followed by cv::imshow.
|
||||
Nevertheless, it can be used to change the window properties or when using cv::createTrackbar
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,276 @@
|
||||
Mat - The Basic Image Container {#tutorial_mat_the_basic_image_container}
|
||||
===============================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@next_tutorial{tutorial_how_to_scan_images}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
We have multiple ways to acquire digital images from the real world: digital cameras, scanners,
|
||||
computed tomography, and magnetic resonance imaging to name a few. In every case what we (humans)
|
||||
see are images. However, when transforming this to our digital devices what we record are numerical
|
||||
values for each of the points of the image.
|
||||
|
||||

|
||||
|
||||
For example in the above image you can see that the mirror of the car is nothing more than a matrix
|
||||
containing all the intensity values of the pixel points. How we get and store the pixels values may
|
||||
vary according to our needs, but in the end all images inside a computer world may be reduced to
|
||||
numerical matrices and other information describing the matrix itself. *OpenCV* is a computer vision
|
||||
library whose main focus is to process and manipulate this information. Therefore, the first thing
|
||||
you need to be familiar with is how OpenCV stores and handles images.
|
||||
|
||||
Mat
|
||||
---
|
||||
|
||||
OpenCV has been around since 2001. In those days the library was built around a *C* interface and to
|
||||
store the image in the memory they used a C structure called *IplImage*. This is the one you'll see
|
||||
in most of the older tutorials and educational materials. The problem with this is that it brings to
|
||||
the table all the minuses of the C language. The biggest issue is the manual memory management. It
|
||||
builds on the assumption that the user is responsible for taking care of memory allocation and
|
||||
deallocation. While this is not a problem with smaller programs, once your code base grows it will
|
||||
be more of a struggle to handle all this rather than focusing on solving your development goal.
|
||||
|
||||
Luckily C++ came around and introduced the concept of classes making easier for the user through
|
||||
automatic memory management (more or less). The good news is that C++ is fully compatible with C so
|
||||
no compatibility issues can arise from making the change. Therefore, OpenCV 2.0 introduced a new C++
|
||||
interface which offered a new way of doing things which means you do not need to fiddle with memory
|
||||
management, making your code concise (less to write, to achieve more). The main downside of the C++
|
||||
interface is that many embedded development systems at the moment support only C. Therefore, unless
|
||||
you are targeting embedded platforms, there's no point to using the *old* methods (unless you're a
|
||||
masochist programmer and you're asking for trouble).
|
||||
|
||||
The first thing you need to know about *Mat* is that you no longer need to manually allocate its
|
||||
memory and release it as soon as you do not need it. While doing this is still a possibility, most
|
||||
of the OpenCV functions will allocate its output data automatically. As a nice bonus if you pass on
|
||||
an already existing *Mat* object, which has already allocated the required space for the matrix,
|
||||
this will be reused. In other words we use at all times only as much memory as we need to perform
|
||||
the task.
|
||||
|
||||
*Mat* is basically a class with two data parts: the matrix header (containing information such as
|
||||
the size of the matrix, the method used for storing, at which address is the matrix stored, and so
|
||||
on) and a pointer to the matrix containing the pixel values (taking any dimensionality depending on
|
||||
the method chosen for storing) . The matrix header size is constant, however the size of the matrix
|
||||
itself may vary from image to image and usually is larger by orders of magnitude.
|
||||
|
||||
OpenCV is an image processing library. It contains a large collection of image processing functions.
|
||||
To solve a computational challenge, most of the time you will end up using multiple functions of the
|
||||
library. Because of this, passing images to functions is a common practice. We should not forget
|
||||
that we are talking about image processing algorithms, which tend to be quite computational heavy.
|
||||
The last thing we want to do is further decrease the speed of your program by making unnecessary
|
||||
copies of potentially *large* images.
|
||||
|
||||
To tackle this issue OpenCV uses a reference counting system. The idea is that each *Mat* object has
|
||||
its own header, however a matrix may be shared between two *Mat* objects by having their matrix
|
||||
pointers point to the same address. Moreover, the copy operators **will only copy the headers** and
|
||||
the pointer to the large matrix, not the data itself.
|
||||
|
||||
@code{.cpp}
|
||||
Mat A, C; // creates just the header parts
|
||||
A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
|
||||
|
||||
Mat B(A); // Use the copy constructor
|
||||
|
||||
C = A; // Assignment operator
|
||||
@endcode
|
||||
|
||||
All the above objects, in the end, point to the same single data matrix and making a modification
|
||||
using any of them will affect all the other ones as well. In practice the different objects just
|
||||
provide different access methods to the same underlying data. Nevertheless, their header parts are
|
||||
different. The real interesting part is that you can create headers which refer to only a subsection
|
||||
of the full data. For example, to create a region of interest (*ROI*) in an image you just create
|
||||
a new header with the new boundaries:
|
||||
@code{.cpp}
|
||||
Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
|
||||
Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries
|
||||
@endcode
|
||||
Now you may ask -- if the matrix itself may belong to multiple *Mat* objects, who takes responsibility
|
||||
for cleaning it up when it's no longer needed? The short answer is: the last object that used it.
|
||||
This is handled by using a reference counting mechanism. Whenever somebody copies a header of a
|
||||
*Mat* object, a counter is increased for the matrix. Whenever a header is cleaned, this counter
|
||||
is decreased. When the counter reaches zero the matrix is freed. Sometimes you will want to copy
|
||||
the matrix itself too, so OpenCV provides @ref cv::Mat::clone() and @ref cv::Mat::copyTo() functions.
|
||||
@code{.cpp}
|
||||
Mat F = A.clone();
|
||||
Mat G;
|
||||
A.copyTo(G);
|
||||
@endcode
|
||||
Now modifying *F* or *G* will not affect the matrix pointed to by the *A*'s header. What you need to
|
||||
remember from all this is that:
|
||||
|
||||
- Output image allocation for OpenCV functions is automatic (unless specified otherwise).
|
||||
- You do not need to think about memory management with OpenCV's C++ interface.
|
||||
- The assignment operator and the copy constructor only copy the header.
|
||||
- The underlying matrix of an image may be copied using the @ref cv::Mat::clone() and @ref cv::Mat::copyTo()
|
||||
functions.
|
||||
|
||||
Storing methods
|
||||
-----------------
|
||||
|
||||
This is about how you store the pixel values. You can select the color space and the data type used.
|
||||
The color space refers to how we combine color components in order to code a given color. The
|
||||
simplest one is the grayscale where the colors at our disposal are black and white. The combination
|
||||
of these allows us to create many shades of gray.
|
||||
|
||||
For *colorful* ways we have a lot more methods to choose from. Each of them breaks it down to three
|
||||
or four basic components and we can use the combination of these to create the others. The most
|
||||
popular one is RGB, mainly because this is also how our eye builds up colors. Its base colors are
|
||||
red, green and blue. To code the transparency of a color sometimes a fourth element, alpha (A), is
|
||||
added.
|
||||
|
||||
There are, however, many other color systems, each with their own advantages:
|
||||
|
||||
- RGB is the most common as our eyes use something similar, however keep in mind that OpenCV standard display
|
||||
system composes colors using the BGR color space (red and blue channels are swapped places).
|
||||
- The HSV and HLS decompose colors into their hue, saturation and value/luminance components,
|
||||
which is a more natural way for us to describe colors. You might, for example, dismiss the last
|
||||
component, making your algorithm less sensible to the light conditions of the input image.
|
||||
- YCrCb is used by the popular JPEG image format.
|
||||
- CIE L\*a\*b\* is a perceptually uniform color space, which comes in handy if you need to measure
|
||||
the *distance* of a given color to another color.
|
||||
|
||||
Each of the building components has its own valid domains. This leads to the data type used. How
|
||||
we store a component defines the control we have over its domain. The smallest data type possible is
|
||||
*char*, which means one byte or 8 bits. This may be unsigned (so can store values from 0 to 255) or
|
||||
signed (values from -127 to +127). Although this width, in the case of three components (like RGB), already gives 16
|
||||
million possible colors to represent, we may acquire an even finer control by
|
||||
using the float (4 byte = 32 bit) or double (8 byte = 64 bit) data types for each component.
|
||||
Nevertheless, remember that increasing the size of a component also increases the size of the whole
|
||||
picture in memory.
|
||||
|
||||
Creating a Mat object explicitly
|
||||
----------------------------------
|
||||
|
||||
In the @ref tutorial_display_image tutorial you have already learned how to write a matrix to an image
|
||||
file by using the @ref cv::imwrite() function. However, for debugging purposes it's much more
|
||||
convenient to see the actual values. You can do this using the \<\< operator of *Mat*. Be aware that
|
||||
this only works for two dimensional matrices.
|
||||
|
||||
Although *Mat* works really well as an image container, it is also a general matrix class.
|
||||
Therefore, it is possible to create and manipulate multidimensional matrices. You can create a Mat
|
||||
object in multiple ways:
|
||||
|
||||
- @ref cv::Mat::Mat Constructor
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp constructor
|
||||
|
||||

|
||||
|
||||
For two dimensional and multichannel images we first define their size: row and column count wise.
|
||||
|
||||
Then we need to specify the data type to use for storing the elements and the number of channels
|
||||
per matrix point. To do this we have multiple definitions constructed according to the following
|
||||
convention:
|
||||
@code
|
||||
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
|
||||
@endcode
|
||||
For instance, *CV_8UC3* means we use unsigned char types that are 8 bit long and each pixel has
|
||||
three of these to form the three channels. There are types predefined for up to four channels. The
|
||||
@ref cv::Scalar is four element short vector. Specify it and you can initialize all matrix
|
||||
points with a custom value. If you need more you can create the type with the upper macro, setting
|
||||
the channel number in parenthesis as you can see below.
|
||||
|
||||
- Use C/C++ arrays and initialize via constructor
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp init
|
||||
|
||||
The upper example shows how to create a matrix with more than two dimensions. Specify its
|
||||
dimension, then pass a pointer containing the size for each dimension and the rest remains the
|
||||
same.
|
||||
|
||||
- @ref cv::Mat::create function:
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp create
|
||||
|
||||

|
||||
|
||||
You cannot initialize the matrix values with this construction. It will only reallocate its matrix
|
||||
data memory if the new size will not fit into the old one.
|
||||
|
||||
- MATLAB style initializer: @ref cv::Mat::zeros , @ref cv::Mat::ones , @ref cv::Mat::eye . Specify size and
|
||||
data type to use:
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp matlab
|
||||
|
||||

|
||||
|
||||
- For small matrices you may use initializer lists:
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp list
|
||||
|
||||

|
||||
|
||||
- Create a new header for an existing *Mat* object and @ref cv::Mat::clone or @ref cv::Mat::copyTo it.
|
||||
|
||||
@snippet mat_the_basic_image_container.cpp clone
|
||||
|
||||

|
||||
|
||||
@note
|
||||
You can fill out a matrix with random values using the @ref cv::randu() function. You need to
|
||||
give a lower and upper limit for the random values:
|
||||
@snippet mat_the_basic_image_container.cpp random
|
||||
|
||||
|
||||
Output formatting
|
||||
-----------------
|
||||
|
||||
In the above examples you could see the default formatting option. OpenCV, however, allows you to
|
||||
format your matrix output:
|
||||
|
||||
- Default
|
||||
@snippet mat_the_basic_image_container.cpp out-default
|
||||

|
||||
|
||||
- Python
|
||||
@snippet mat_the_basic_image_container.cpp out-python
|
||||

|
||||
|
||||
- Comma separated values (CSV)
|
||||
@snippet mat_the_basic_image_container.cpp out-csv
|
||||

|
||||
|
||||
- Numpy
|
||||
@snippet mat_the_basic_image_container.cpp out-numpy
|
||||

|
||||
|
||||
- C
|
||||
@snippet mat_the_basic_image_container.cpp out-c
|
||||

|
||||
|
||||
Output of other common items
|
||||
----------------------------
|
||||
|
||||
OpenCV offers support for output of other common OpenCV data structures too via the \<\< operator:
|
||||
|
||||
- 2D Point
|
||||
@snippet mat_the_basic_image_container.cpp out-point2
|
||||

|
||||
|
||||
- 3D Point
|
||||
@snippet mat_the_basic_image_container.cpp out-point3
|
||||

|
||||
|
||||
- std::vector via cv::Mat
|
||||
@snippet mat_the_basic_image_container.cpp out-vector
|
||||

|
||||
|
||||
- std::vector of points
|
||||
@snippet mat_the_basic_image_container.cpp out-vector-points
|
||||

|
||||
|
||||
Most of the samples here have been included in a small console application. You can download it from
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/core/mat_the_basic_image_container/mat_the_basic_image_container.cpp)
|
||||
or in the core section of the cpp samples.
|
||||
|
||||
You can also find a quick video demonstration of this on
|
||||
[YouTube](https://www.youtube.com/watch?v=1tibU7vGWpk).
|
||||
|
||||
@youtube{1tibU7vGWpk}
|
||||
@@ -0,0 +1,19 @@
|
||||
The Core Functionality (core module) {#tutorial_table_of_content_core}
|
||||
=====================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
##### Basic
|
||||
- @subpage tutorial_mat_the_basic_image_container
|
||||
- @subpage tutorial_how_to_scan_images
|
||||
- @subpage tutorial_mat_mask_operations
|
||||
- @subpage tutorial_mat_operations
|
||||
- @subpage tutorial_adding_images
|
||||
- @subpage tutorial_basic_linear_transform
|
||||
|
||||
##### Advanced
|
||||
- @subpage tutorial_discrete_fourier_transform
|
||||
- @subpage tutorial_file_input_output_with_xml_yml
|
||||
- @subpage tutorial_how_to_use_OpenCV_parallel_for_
|
||||
- @subpage tutorial_how_to_use_OpenCV_parallel_for_new
|
||||
- @subpage tutorial_univ_intrin
|
||||
@@ -0,0 +1,333 @@
|
||||
Vectorizing your code using Universal Intrinsics {#tutorial_univ_intrin}
|
||||
==================================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_how_to_use_OpenCV_parallel_for_new}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Compatibility | OpenCV >= 4.11 |
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
The goal of this tutorial is to provide a guide to using the @ref core_hal_intrin feature to vectorize your C++ code for a faster runtime.
|
||||
We'll briefly look into _SIMD intrinsics_ and how to work with wide _registers_, followed by a tutorial on the basic operations using wide registers.
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
In this section, we will briefly look into a few concepts to better help understand the functionality.
|
||||
|
||||
### Intrinsics
|
||||
Intrinsics are functions which are separately handled by the compiler. These functions are often optimized to perform in the most efficient ways possible and hence run faster than normal implementations. However, since these functions depend on the compiler, it makes it difficult to write portable applications.
|
||||
|
||||
### SIMD
|
||||
SIMD stands for **Single Instruction, Multiple Data**. SIMD Intrinsics allow the processor to vectorize calculations. The data is stored in what are known as *registers*. A *register* may be *128-bits*, *256-bits* or *512-bits* wide. Each *register* stores **multiple values** of the **same data type**. The size of the register and the size of each value determines the number of values stored in total.
|
||||
|
||||
Depending on what *Instruction Sets* your CPU supports, you may be able to use the different registers. To learn more, look [here](https://en.wikipedia.org/wiki/Instruction_set_architecture)
|
||||
|
||||
### VLA
|
||||
VLA stands for **Vector Length Agnostic** .
|
||||
A mechanism where the register width is determined by the hardware at runtime rather than being fixed at compile time.
|
||||
This allows a single binary to scale its performance across different CPUs within the same architecture (e.g., RVV or SVE).
|
||||
|
||||
Universal Intrinsics
|
||||
--------------------
|
||||
|
||||
OpenCV's universal intrinsics provides an abstraction to SIMD and VLA vectorization methods and allows the user to use intrinsics without the need to write system specific code.
|
||||
Supported SIMD/VLA technologies are detailed in @ref core_hal_intrin .
|
||||
|
||||
**We will now introduce the available structures and functions:**
|
||||
* Register structures
|
||||
* Load and store
|
||||
* Mathematical Operations
|
||||
* Reduce and Mask
|
||||
|
||||
### Register Structures
|
||||
|
||||
The Universal Intrinsics set implements every register as a structure based on the particular SIMD register.
|
||||
All types contain the `nlanes` enumeration which gives the exact number of values that the type can hold. This eliminates the need to hardcode the number of values during implementations.
|
||||
|
||||
@note Each register structure is under the `cv` namespace.
|
||||
|
||||
There are **two types** of registers:
|
||||
|
||||
* **Variable sized registers**: These structures do not have a fixed size and their exact bit length is deduced during compilation, based on the available SIMD capabilities. Consequently, the value of the `nlanes` enum is determined in compile time.
|
||||
<br>
|
||||
|
||||
Each structure follows the following convention:
|
||||
|
||||
v_[type of value][size of each value in bits]
|
||||
|
||||
For instance, **v_uint8 holds 8-bit unsigned integers** and **v_float32 holds 32-bit floating point values**. We then declare a register like we would declare any object in C++
|
||||
|
||||
Based on the available SIMD instruction set, a particular register will hold different number of values.
|
||||
For example: If your computer supports a maximum of 256bit registers,
|
||||
* *v_uint8* will hold 32 8-bit unsigned integers
|
||||
* *v_float64* will hold 4 64-bit floats (doubles)
|
||||
|
||||
v_uint8 a; // a is a register supporting uint8(char) data
|
||||
int n = a.nlanes; // n holds 32
|
||||
|
||||
Available data type and sizes:
|
||||
|Type|Size in bits|
|
||||
|-:|:-|
|
||||
|uint| 8, 16, 32, 64|
|
||||
|int | 8, 16, 32, 64|
|
||||
|float | 32, 64|
|
||||
|
||||
* **Constant sized registers**: These structures have a fixed bit size and hold a constant number of values. We need to know what SIMD instruction set is supported by the system and select compatible registers. Use these only if exact bit length is necessary.
|
||||
<br>
|
||||
|
||||
Each structure follows the convention:
|
||||
|
||||
v_[type of value][size of each value in bits]x[number of values]
|
||||
|
||||
Suppose we want to store
|
||||
* 32-bit(*size in bits*) signed integers in a **128 bit register**. Since the register size is already known, we can find out the *number of data points in register* (*128/32 = 4*):
|
||||
|
||||
v_int32x8 reg1 // holds 8 32-bit signed integers.
|
||||
|
||||
* 64-bit floats in 512 bit register:
|
||||
|
||||
v_float64x8 reg2 // reg2.nlanes = 8
|
||||
|
||||
### Load and Store operations
|
||||
|
||||
Now that we know how registers work, let us look at the functions used for filling these registers with values.
|
||||
|
||||
* **Load**: Load functions allow you to *load* values into a register.
|
||||
* *Constructors* - When declaring a register structure, we can either provide a memory address from where the register will pick up contiguous values, or provide the values explicitly as multiple arguments (Explicit multiple arguments is available only for Constant Sized Registers):
|
||||
|
||||
float ptr[32] = {1, 2, 3 ..., 32}; // ptr is a pointer to a contiguous memory block of 32 floats
|
||||
|
||||
// Variable Sized Registers //
|
||||
int x = v_float32().nlanes; // set x as the number of values the register can hold
|
||||
|
||||
v_float32 reg1(ptr); // reg1 stores first x values according to the maximum register size available.
|
||||
v_float32 reg2(ptr + x); // reg stores the next x values
|
||||
|
||||
// Constant Sized Registers //
|
||||
v_float32x4 reg1(ptr); // reg1 stores the first 4 floats (1, 2, 3, 4)
|
||||
v_float32x4 reg2(ptr + 4); // reg2 stores the next 4 floats (5, 6, 7, 8)
|
||||
|
||||
// Or we can explicitly write down the values.
|
||||
v_float32x4(1, 2, 3, 4);
|
||||
|
||||
<br>
|
||||
* *Load Function* - We can use the load method and provide the memory address of the data:
|
||||
|
||||
float ptr[32] = {1, 2, 3, ..., 32};
|
||||
v_float32 reg_var;
|
||||
reg_var = vx_load(ptr); // loads values from ptr[0] upto ptr[reg_var.nlanes - 1]
|
||||
|
||||
v_float32x4 reg_128;
|
||||
reg_128 = v_load(ptr); // loads values from ptr[0] upto ptr[3]
|
||||
|
||||
v_float32x8 reg_256;
|
||||
reg_256 = v256_load(ptr); // loads values from ptr[0] upto ptr[7]
|
||||
|
||||
v_float32x16 reg_512;
|
||||
reg_512 = v512_load(ptr); // loads values from ptr[0] upto ptr[15]
|
||||
|
||||
@note The load function assumes data is unaligned. If your data is aligned, you may use the `vx_load_aligned()` function.
|
||||
<br>
|
||||
|
||||
* **Store**: Store functions allow you to *store* the values from a register into a particular memory location.
|
||||
* To store values from a register into a memory location, you may use the *v_store()* function:
|
||||
|
||||
float ptr[4];
|
||||
v_store(ptr, reg); // store the first 128 bits(interpreted as 4x32-bit floats) of reg into ptr.
|
||||
<br>
|
||||
@note Ensure **ptr** has the same type as register. You can also cast the register into the proper type before carrying out operations. Simply typecasting the pointer to a particular type will lead wrong interpretation of data.
|
||||
|
||||
### Binary and Unary Operators
|
||||
|
||||
The universal intrinsics set provides element wise binary and unary operations.
|
||||
|
||||
@note Since OpenCV 4.11, C++ operator overloading (e.g., +, ) in Universal Intrinsics has been deprecated in favor of explicit wrapper functions (e.g., v_add, v_mul) to ensure compatibility with VLA architectures.
|
||||
See also: https://github.com/opencv/opencv/issues/27267
|
||||
|
||||
* **Arithmetics**: We can add, subtract, multiply and divide two registers element-wise. The registers must be of the same width and hold the same type. To multiply two registers, for example:
|
||||
|
||||
v_float32 a, b; // {a1, ..., an}, {b1, ..., bn}
|
||||
v_float32 c = v_add(a, b); // {a1 + b1, ..., an + bn}
|
||||
v_flaot32 d = v_mul(a, b); // {a1 * b1, ..., an * bn}
|
||||
|
||||
<br>
|
||||
|
||||
* **Bitwise Logic and Shifts**: We can left shift or right shift the bits of each element of the register. We can also apply bitwise and, or, xor and not operators between two registers element-wise:
|
||||
|
||||
v_int32 as; // {a1, ..., an}
|
||||
v_int32 al = v_shl(as, 2); // {a1 << 2, ..., an << 2}
|
||||
v_int32 bl = v_shr(as, 2); // {a1 >> 2, ..., an >> 2}
|
||||
|
||||
v_int32 a, b;
|
||||
v_int32 a_and_b = v_and(a, b); // {a1 & b1, ..., an & bn}
|
||||
|
||||
<br>
|
||||
|
||||
* **Comparison Operators**: We can compare values between two registers using the v_lt(<), v_gt(>), v_le(<=) , v_ge(>=), v_eq(==) and v_ne(!=). Since each register contains multiple values, we don't get a single bool for these operations. Instead, for true values, all bits are converted to one (0xff for 8 bits, 0xffff for 16 bits, etc), while false values return bits converted to zero.
|
||||
|
||||
// let us consider the following code is run in a 128-bit register
|
||||
v_uint8 a; // a = {0, 1, 2, ..., 13, 14, 15}
|
||||
v_uint8 b; // b = {15, 14, 13, ..., 2, 1, 0}
|
||||
|
||||
v_uint8 c = v_lt(a, b); // c = {255, 255, 255, ..., 0, 0, 0}
|
||||
|
||||
/*
|
||||
let us look at the first 4 values in binary
|
||||
|
||||
a = |00000000|00000001|00000010|00000011|
|
||||
b = |00001111|00001110|00001101|00001100|
|
||||
c = |11111111|11111111|11111111|11111111|
|
||||
|
||||
If we store the values of c and print them as integers, we will get 255 for true values and 0 for false values.
|
||||
*/
|
||||
---
|
||||
// In a computer supporting 256-bit registers
|
||||
v_int32 a; // a = {1, 2, 3, 4, 5, 6, 7, 8}
|
||||
v_int32 b; // b = {8, 7, 6, 5, 4, 3, 2, 1}
|
||||
|
||||
v_int32 c = v_lt(a, b); // c = {-1, -1, -1, -1, 0, 0, 0, 0}
|
||||
|
||||
/*
|
||||
The true values are 0xffffffff, which in signed 32-bit integer representation is equal to -1.
|
||||
*/
|
||||
<br>
|
||||
|
||||
* **Min/Max operations**: We can use the *v_min()* and *v_max()* functions to return registers containing element-wise min, or max, of the two registers:
|
||||
|
||||
v_int32 a; // {a1, ..., an}
|
||||
v_int32 b; // {b1, ..., bn}
|
||||
|
||||
v_int32 mn = v_min(a, b); // {min(a1, b1), ..., min(an, bn)}
|
||||
v_int32 mx = v_max(a, b); // {max(a1, b1), ..., max(an, bn)}
|
||||
<br>
|
||||
|
||||
@note Comparison and Min/Max operators are not available for 64 bit integers. Bitwise shift and logic operators are available only for integer values. Bitwise shift is available only for 16, 32 and 64 bit registers.
|
||||
|
||||
### Reduce and Mask
|
||||
|
||||
* **Reduce Operations**: The *v_reduce_min()*, *v_reduce_max()* and *v_reduce_sum()* return a single value denoting the min, max or sum of the entire register:
|
||||
|
||||
v_int32 a; // a = {a1, ..., a4}
|
||||
int mn = v_reduce_min(a); // mn = min(a1, ..., an)
|
||||
int sum = v_reduce_sum(a); // sum = a1 + ... + an
|
||||
<br>
|
||||
|
||||
* **Mask Operations**: Mask operations allow us to replicate conditionals in wide registers. These include:
|
||||
* *v_check_all()* - Returns a bool, which is true if all the values in the register are less than zero.
|
||||
* *v_check_any()* - Returns a bool, which is true if any value in the register is less than zero.
|
||||
* *v_select()* - Returns a register, which blends two registers, based on a mask.
|
||||
|
||||
v_uint8 a; // {a1, .., an}
|
||||
v_uint8 b; // {b1, ..., bn}
|
||||
|
||||
v_int32x4 mask: // {0xff, 0, 0, 0xff, ..., 0xff, 0}
|
||||
|
||||
v_uint8 Res = v_select(mask, a, b) // {a1, b2, b3, a4, ..., an-1, bn}
|
||||
|
||||
/*
|
||||
"Res" will contain the value from "a" if mask is true (all bits set to 1),
|
||||
and value from "b" if mask is false (all bits set to 0)
|
||||
|
||||
We can use comparison operators to generate mask and v_select to obtain results based on conditionals.
|
||||
It is common to set all values of b to 0. Thus, v_select will give values of "a" or 0 based on the mask.
|
||||
*/
|
||||
|
||||
## Demonstration
|
||||
In the following section, we will vectorize a simple convolution function for single channel and compare the results to a scalar implementation.
|
||||
@note Not all algorithms are improved by manual vectorization. In fact, in certain cases, the compiler may *autovectorize* the code, thus producing faster results for scalar implementations.
|
||||
|
||||
You may learn more about convolution from the previous tutorial. We use the same naive implementation from the previous tutorial and compare it to the vectorized version.
|
||||
|
||||
The full tutorial code is [here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/core/univ_intrin/univ_intrin.cpp).
|
||||
|
||||
### Vectorizing Convolution
|
||||
|
||||
We will first implement a 1-D convolution and then vectorize it. The 2-D vectorized convolution will perform 1-D convolution across the rows to produce the correct results.
|
||||
|
||||
#### 1-D Convolution: Scalar
|
||||
@snippet univ_intrin.cpp convolution-1D-scalar
|
||||
|
||||
1. We first set up variables and make a border on both sides of the src matrix, to take care of edge cases.
|
||||
@snippet univ_intrin.cpp convolution-1D-border
|
||||
|
||||
2. For the main loop, we select an index *i* and offset it on both sides along with the kernel, using the k variable. We store the value in *value* and add it to the *dst* matrix.
|
||||
@snippet univ_intrin.cpp convolution-1D-scalar-main
|
||||
|
||||
#### 1-D Convolution: Vector
|
||||
|
||||
We will now look at the vectorized version of 1-D convolution.
|
||||
@snippet univ_intrin.cpp convolution-1D-vector
|
||||
|
||||
1. In our case, the kernel is a float. Since the kernel's datatype is the largest, we convert src to float32, forming *src_32*. We also make a border like we did for the naive case.
|
||||
@snippet univ_intrin.cpp convolution-1D-convert
|
||||
|
||||
2. Now, for each column in the *kernel*, we calculate the scalar product of the value with all *window* vectors of length `step`. We add these values to the already stored values in ans
|
||||
@snippet univ_intrin.cpp convolution-1D-main
|
||||
|
||||
* We declare a pointer to the src_32 and kernel and run a loop for each kernel element
|
||||
@snippet univ_intrin.cpp convolution-1D-main-h1
|
||||
|
||||
* We load a register with the current kernel element. A window is shifted from *0* to *len - step* and its product with the kernel_wide array is added to the values stored in *ans*. We store the values back into *ans*
|
||||
@snippet univ_intrin.cpp convolution-1D-main-h2
|
||||
|
||||
* Since the length might not be divisible by steps, we take care of the remaining values directly. The number of *tail* values will always be less than *step* and will not affect the performance significantly. We store all the values to *ans* which is a float pointer. We can also directly store them in a `Mat` object
|
||||
@snippet univ_intrin.cpp convolution-1D-main-h3
|
||||
|
||||
* Here is an iterative example:
|
||||
|
||||
For example:
|
||||
kernel: {k1, k2, k3}
|
||||
src: ...|a1|a2|a3|a4|...
|
||||
|
||||
|
||||
iter1:
|
||||
for each idx i in (0, len), 'step' idx at a time
|
||||
kernel_wide: |k1|k1|k1|k1|
|
||||
window: |a0|a1|a2|a3|
|
||||
ans: ...| 0| 0| 0| 0|...
|
||||
sum = ans + window * kernel_wide
|
||||
= |a0 * k1|a1 * k1|a2 * k1|a3 * k1|
|
||||
|
||||
iter2:
|
||||
kernel_wide: |k2|k2|k2|k2|
|
||||
window: |a1|a2|a3|a4|
|
||||
ans: ...|a0 * k1|a1 * k1|a2 * k1|a3 * k1|...
|
||||
sum = ans + window * kernel_wide
|
||||
= |a0 * k1 + a1 * k2|a1 * k1 + a2 * k2|a2 * k1 + a3 * k2|a3 * k1 + a4 * k2|
|
||||
|
||||
iter3:
|
||||
kernel_wide: |k3|k3|k3|k3|
|
||||
window: |a2|a3|a4|a5|
|
||||
ans: ...|a0 * k1 + a1 * k2|a1 * k1 + a2 * k2|a2 * k1 + a3 * k2|a3 * k1 + a4 * k2|...
|
||||
sum = sum + window * kernel_wide
|
||||
= |a0*k1 + a1*k2 + a2*k3|a1*k1 + a2*k2 + a3*k3|a2*k1 + a3*k2 + a4*k3|a3*k1 + a4*k2 + a5*k3|
|
||||
|
||||
|
||||
@note The function parameters also include *row*, *rowk* and *len*. These values are used when using the function as an intermediate step of 2-D convolution
|
||||
|
||||
#### 2-D Convolution
|
||||
|
||||
Suppose our kernel has *ksize* rows. To compute the values for a particular row, we compute the 1-D convolution of the previous *ksize/2* and the next *ksize/2* rows, with the corresponding kernel row. The final values is simply the sum of the individual 1-D convolutions
|
||||
@snippet univ_intrin.cpp convolution-2D
|
||||
|
||||
1. We first initialize variables and make a border above and below the *src* matrix. The left and right sides are handled by the 1-D convolution function.
|
||||
@snippet univ_intrin.cpp convolution-2D-init
|
||||
|
||||
2. For each row, we calculate the 1-D convolution of the rows above and below it. we then add the values to the *dst* matrix.
|
||||
@snippet univ_intrin.cpp convolution-2D-main
|
||||
|
||||
3. We finally convert the *dst* matrix to a *8-bit* `unsigned char` matrix
|
||||
@snippet univ_intrin.cpp convolution-2D-conv
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
In the tutorial, we used a horizontal gradient kernel. We obtain the same output image for both methods.
|
||||
|
||||
Improvement in runtime varies and will depend on the SIMD capabilities available in your CPU.
|
||||
@@ -0,0 +1,55 @@
|
||||
# How to run custom OCR model {#tutorial_dnn_OCR}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_custom_layers}
|
||||
@next_tutorial{tutorial_dnn_text_spotting}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Zihao Mu |
|
||||
| Compatibility | OpenCV >= 4.3 |
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, we first introduce how to obtain the custom OCR model, then how to transform your own OCR models so that they can be run correctly by the opencv_dnn module. and finally we will provide some pre-trained models.
|
||||
|
||||
## Train your own OCR model
|
||||
|
||||
[This repository](https://github.com/zihaomu/deep-text-recognition-benchmark) is a good start point for training your own OCR model. In repository, the MJSynth+SynthText was set as training set by default. In addition, you can configure the model structure and data set you want.
|
||||
|
||||
## Transform OCR model to ONNX format and Use it in OpenCV DNN
|
||||
|
||||
After completing the model training, please use [transform_to_onnx.py](https://github.com/zihaomu/deep-text-recognition-benchmark/blob/master/transform_to_onnx.py) to convert the model into onnx format.
|
||||
|
||||
### Execute in webcam
|
||||
The Python version example code can be found at [here](https://github.com/opencv/opencv/blob/4.x/samples/dnn/text_detection.py).
|
||||
|
||||
Example:
|
||||
@code{.bash}
|
||||
$ text_detection -m=[path_to_text_detect_model] -ocr=[path_to_text_recognition_model]
|
||||
@endcode
|
||||
|
||||
## Pre-trained ONNX models are provided
|
||||
|
||||
Some pre-trained models can be found at https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing.
|
||||
|
||||
Their performance at different text recognition datasets is shown in the table below:
|
||||
|
||||
| Model name | IIIT5k(%) | SVT(%) | ICDAR03(%) | ICDAR13(%) | ICDAR15(%) | SVTP(%) | CUTE80(%) | average acc (%) | parameter( x10^6 ) |
|
||||
| -------------------- | --------- | ------ | ---------- | ---------- | ---------- | ------- | --------- | --------------- | ------------------ |
|
||||
| DenseNet-CTC | 72.267 | 67.39 | 82.81 | 80 | 48.38 | 49.45 | 42.50 | 63.26 | 0.24 |
|
||||
| DenseNet-BiLSTM-CTC | 73.76 | 72.33 | 86.15 | 83.15 | 50.67 | 57.984 | 49.826 | 67.69 | 3.63 |
|
||||
| VGG-CTC | 75.96 | 75.42 | 85.92 | 83.54 | 54.89 | 57.52 | 50.17 | 69.06 | 5.57 |
|
||||
| CRNN_VGG-BiLSTM-CTC | 82.63 | 82.07 | 92.96 | 88.867 | 66.28 | 71.01 | 62.37 | 78.03 | 8.45 |
|
||||
| ResNet-CTC | 84.00 | 84.08 | 92.39 | 88.96 | 67.74 | 74.73 | 67.60 | 79.93 | 44.28 |
|
||||
|
||||
The performance of the text recognition model were tested on OpenCV DNN, and does not include the text detection model.
|
||||
|
||||
### Model selection suggestion
|
||||
|
||||
The input of text recognition model is the output of the text detection model, which causes the performance of text detection to greatly affect the performance of text recognition.
|
||||
|
||||
DenseNet_CTC has the smallest parameters and best FPS, and it is suitable for edge devices, which are very sensitive to the cost of calculation. If you have limited computing resources and want to achieve better accuracy, VGG_CTC is a good choice.
|
||||
|
||||
CRNN_VGG_BiLSTM_CTC is suitable for scenarios that require high recognition accuracy.
|
||||
@@ -0,0 +1 @@
|
||||
The page was moved to @ref tutorial_android_dnn_intro
|
||||
@@ -0,0 +1,237 @@
|
||||
# Custom deep learning layers support {#tutorial_dnn_custom_layers}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_javascript}
|
||||
@next_tutorial{tutorial_dnn_OCR}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Dmitry Kurtaev |
|
||||
| Compatibility | OpenCV >= 3.4.1 |
|
||||
|
||||
## Introduction
|
||||
Deep learning is a fast-growing area. New approaches to building neural networks
|
||||
usually introduce new types of layers. These could be modifications of existing
|
||||
ones or implementation of outstanding research ideas.
|
||||
|
||||
OpenCV allows importing and running networks from different deep learning frameworks.
|
||||
There is a number of the most popular layers. However, you can face a problem that
|
||||
your network cannot be imported using OpenCV because some layers of your network
|
||||
can be not implemented in the deep learning engine of OpenCV.
|
||||
|
||||
The first solution is to create a feature request at https://github.com/opencv/opencv/issues
|
||||
mentioning details such as a source of a model and a type of new layer.
|
||||
The new layer could be implemented if the OpenCV community shares this need.
|
||||
|
||||
The second way is to define a **custom layer** so that OpenCV's deep learning engine
|
||||
will know how to use it. This tutorial is dedicated to show you a process of deep
|
||||
learning model's import customization.
|
||||
|
||||
## Define a custom layer in C++
|
||||
Deep learning layer is a building block of network's pipeline.
|
||||
It has connections to **input blobs** and produces results to **output blobs**.
|
||||
There are trained **weights** and **hyper-parameters**.
|
||||
Layers' names, types, weights and hyper-parameters are stored in files are
|
||||
generated by native frameworks during training. If OpenCV encounters unknown
|
||||
layer type it throws an exception while trying to read a model:
|
||||
|
||||
```
|
||||
Unspecified error: Can't create layer "layer_name" of type "MyType" in function getLayerInstance
|
||||
```
|
||||
|
||||
To import the model correctly you have to derive a class from cv::dnn::Layer with
|
||||
the following methods:
|
||||
|
||||
@snippet dnn/custom_layers.hpp A custom layer interface
|
||||
|
||||
And register it before the import:
|
||||
|
||||
@snippet dnn/custom_layers.hpp Register a custom layer
|
||||
|
||||
@note `MyType` is a type of unimplemented layer from the thrown exception.
|
||||
|
||||
Let's see what all the methods do:
|
||||
|
||||
- Constructor
|
||||
|
||||
@snippet dnn/custom_layers.hpp MyLayer::MyLayer
|
||||
|
||||
Retrieves hyper-parameters from cv::dnn::LayerParams. If your layer has trainable
|
||||
weights they will be already stored in the Layer's member cv::dnn::Layer::blobs.
|
||||
|
||||
- A static method `create`
|
||||
|
||||
@snippet dnn/custom_layers.hpp MyLayer::create
|
||||
|
||||
This method should create an instance of you layer and return cv::Ptr with it.
|
||||
|
||||
- Output blobs' shape computation
|
||||
|
||||
@snippet dnn/custom_layers.hpp MyLayer::getMemoryShapes
|
||||
|
||||
Returns layer's output shapes depending on input shapes. You may request an extra
|
||||
memory using `internals`.
|
||||
|
||||
- Run a layer
|
||||
|
||||
@snippet dnn/custom_layers.hpp MyLayer::forward
|
||||
|
||||
Implement a layer's logic here. Compute outputs for given inputs.
|
||||
|
||||
@note OpenCV manages memory allocated for layers. In the most cases the same memory
|
||||
can be reused between layers. So your `forward` implementation should not rely on that
|
||||
the second invocation of `forward` will have the same data at `outputs` and `internals`.
|
||||
|
||||
- Optional `finalize` method
|
||||
|
||||
@snippet dnn/custom_layers.hpp MyLayer::finalize
|
||||
|
||||
The chain of methods is the following: OpenCV deep learning engine calls `create`
|
||||
method once, then it calls `getMemoryShapes` for every created layer, then you
|
||||
can make some preparations depend on known input dimensions at cv::dnn::Layer::finalize.
|
||||
After network was initialized only `forward` method is called for every network's input.
|
||||
|
||||
@note Varying input blobs' sizes such height, width or batch size make OpenCV
|
||||
reallocate all the internal memory. That leads to efficiency gaps. Try to initialize
|
||||
and deploy models using a fixed batch size and image's dimensions.
|
||||
|
||||
## Example: custom layer from Caffe
|
||||
Let's create a custom layer `Interp` from https://github.com/cdmh/deeplab-public.
|
||||
It's just a simple resize that takes an input blob of size `N x C x Hi x Wi` and returns
|
||||
an output blob of size `N x C x Ho x Wo` where `N` is a batch size, `C` is a number of channels,
|
||||
`Hi x Wi` and `Ho x Wo` are input and output `height x width` correspondingly.
|
||||
This layer has no trainable weights but it has hyper-parameters to specify an output size.
|
||||
|
||||
In example,
|
||||
~~~~~~~~~~~~~
|
||||
layer {
|
||||
name: "output"
|
||||
type: "Interp"
|
||||
bottom: "input"
|
||||
top: "output"
|
||||
interp_param {
|
||||
height: 9
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This way our implementation can look like:
|
||||
|
||||
@snippet dnn/custom_layers.hpp InterpLayer
|
||||
|
||||
Next we need to register a new layer type and try to import the model.
|
||||
|
||||
@snippet dnn/custom_layers.hpp Register InterpLayer
|
||||
|
||||
## Example: custom layer from TensorFlow
|
||||
This is an example of how to import a network with [tf.image.resize_bilinear](https://www.tensorflow.org/versions/master/api_docs/python/tf/image/resize_bilinear)
|
||||
operation. This is also a resize but with an implementation different from OpenCV's or `Interp` above.
|
||||
|
||||
Let's create a single layer network:
|
||||
~~~~~~~~~~~~~{.py}
|
||||
inp = tf.placeholder(tf.float32, [2, 3, 4, 5], 'input')
|
||||
resized = tf.image.resize_bilinear(inp, size=[9, 8], name='resize_bilinear')
|
||||
~~~~~~~~~~~~~
|
||||
OpenCV sees that TensorFlow's graph in the following way:
|
||||
|
||||
```
|
||||
node {
|
||||
name: "input"
|
||||
op: "Placeholder"
|
||||
attr {
|
||||
key: "dtype"
|
||||
value {
|
||||
type: DT_FLOAT
|
||||
}
|
||||
}
|
||||
}
|
||||
node {
|
||||
name: "resize_bilinear/size"
|
||||
op: "Const"
|
||||
attr {
|
||||
key: "dtype"
|
||||
value {
|
||||
type: DT_INT32
|
||||
}
|
||||
}
|
||||
attr {
|
||||
key: "value"
|
||||
value {
|
||||
tensor {
|
||||
dtype: DT_INT32
|
||||
tensor_shape {
|
||||
dim {
|
||||
size: 2
|
||||
}
|
||||
}
|
||||
tensor_content: "\t\000\000\000\010\000\000\000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node {
|
||||
name: "resize_bilinear"
|
||||
op: "ResizeBilinear"
|
||||
input: "input:0"
|
||||
input: "resize_bilinear/size"
|
||||
attr {
|
||||
key: "T"
|
||||
value {
|
||||
type: DT_FLOAT
|
||||
}
|
||||
}
|
||||
attr {
|
||||
key: "align_corners"
|
||||
value {
|
||||
b: false
|
||||
}
|
||||
}
|
||||
}
|
||||
library {
|
||||
}
|
||||
```
|
||||
Custom layers import from TensorFlow is designed to put all layer's `attr` into
|
||||
cv::dnn::LayerParams but input `Const` blobs into cv::dnn::Layer::blobs.
|
||||
In our case resize's output shape will be stored in layer's `blobs[0]`.
|
||||
|
||||
@snippet dnn/custom_layers.hpp ResizeBilinearLayer
|
||||
|
||||
Next we register a layer and try to import the model.
|
||||
|
||||
@snippet dnn/custom_layers.hpp Register ResizeBilinearLayer
|
||||
|
||||
## Define a custom layer in Python
|
||||
The following example shows how to customize OpenCV's layers in Python.
|
||||
|
||||
Let's consider [Holistically-Nested Edge Detection](https://arxiv.org/abs/1504.06375)
|
||||
deep learning model. That was trained with one and only difference comparing to
|
||||
a current version of [Caffe framework](http://caffe.berkeleyvision.org/). `Crop`
|
||||
layers that receive two input blobs and crop the first one to match spatial dimensions
|
||||
of the second one used to crop from the center. Nowadays Caffe's layer does it
|
||||
from the top-left corner. So using the latest version of Caffe or OpenCV you will
|
||||
get shifted results with filled borders.
|
||||
|
||||
Next we're going to replace OpenCV's `Crop` layer that makes top-left cropping by
|
||||
a centric one.
|
||||
|
||||
- Create a class with `getMemoryShapes` and `forward` methods
|
||||
|
||||
@snippet dnn/edge_detection.py CropLayer
|
||||
|
||||
@note Both methods should return lists.
|
||||
|
||||
- Register a new layer.
|
||||
|
||||
@snippet dnn/edge_detection.py Register
|
||||
|
||||
That's it! We have replaced an implemented OpenCV's layer to a custom one.
|
||||
You may find a full script in the [source code](https://github.com/opencv/opencv/tree/4.x/samples/dnn/edge_detection.py).
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,110 @@
|
||||
# DNN-based Face Detection And Recognition {#tutorial_dnn_face}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_text_spotting}
|
||||
@next_tutorial{pytorch_cls_tutorial_dnn_conversion}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original Author | Chengrui Wang, Yuantao Feng |
|
||||
| Compatibility | OpenCV >= 4.5.4 |
|
||||
|
||||
## Introduction
|
||||
|
||||
In this section, we introduce cv::FaceDetectorYN class for face detection and cv::FaceRecognizerSF class for face recognition.
|
||||
|
||||
## Models
|
||||
|
||||
There are two models (ONNX format) pre-trained and required for this module:
|
||||
- [Face Detection](https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet):
|
||||
- Size: 338KB
|
||||
- Results on WIDER Face Val set: 0.830(easy), 0.824(medium), 0.708(hard)
|
||||
- [Face Recognition](https://github.com/opencv/opencv_zoo/tree/master/models/face_recognition_sface)
|
||||
- Size: 36.9MB
|
||||
- Results:
|
||||
|
||||
| Database | Accuracy | Threshold (normL2) | Threshold (cosine) |
|
||||
| -------- | -------- | ------------------ | ------------------ |
|
||||
| LFW | 99.60% | 1.128 | 0.363 |
|
||||
| CALFW | 93.95% | 1.149 | 0.340 |
|
||||
| CPLFW | 91.05% | 1.204 | 0.275 |
|
||||
| AgeDB-30 | 94.90% | 1.202 | 0.277 |
|
||||
| CFP-FP | 94.80% | 1.253 | 0.212 |
|
||||
|
||||
## Code
|
||||
|
||||
@add_toggle_cpp
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/dnn/face_detect.cpp)
|
||||
|
||||
- **Code at glance:**
|
||||
@include samples/dnn/face_detect.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/4.x/samples/dnn/face_detect.py)
|
||||
|
||||
- **Code at glance:**
|
||||
@include samples/dnn/face_detect.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet dnn/face_detect.cpp initialize_FaceDetectorYN
|
||||
@snippet dnn/face_detect.cpp inference
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet dnn/face_detect.py initialize_FaceDetectorYN
|
||||
@snippet dnn/face_detect.py inference
|
||||
@end_toggle
|
||||
|
||||
The detection output `faces` is a two-dimension array of type CV_32F, whose rows are the detected face instances, columns are the location of a face and 5 facial landmarks. The format of each row is as follows:
|
||||
|
||||
```
|
||||
x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt, x_rcm, y_rcm, x_lcm, y_lcm
|
||||
```
|
||||
, where `x1, y1, w, h` are the top-left coordinates, width and height of the face bounding box, `{x, y}_{re, le, nt, rcm, lcm}` stands for the coordinates of right eye, left eye, nose tip, the right corner and left corner of the mouth respectively.
|
||||
|
||||
|
||||
### Face Recognition
|
||||
|
||||
Following Face Detection, run codes below to extract face feature from facial image.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet dnn/face_detect.cpp initialize_FaceRecognizerSF
|
||||
@snippet dnn/face_detect.cpp facerecognizer
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet dnn/face_detect.py initialize_FaceRecognizerSF
|
||||
@snippet dnn/face_detect.py facerecognizer
|
||||
@end_toggle
|
||||
|
||||
After obtaining face features *feature1* and *feature2* of two facial images, run codes below to calculate the identity discrepancy between the two faces.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet dnn/face_detect.cpp match
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet dnn/face_detect.py match
|
||||
@end_toggle
|
||||
|
||||
For example, two faces have same identity if the cosine distance is greater than or equal to 0.363, or the normL2 distance is less than or equal to 1.128.
|
||||
|
||||
## Reference:
|
||||
|
||||
- https://github.com/ShiqiYu/libfacedetection
|
||||
- https://github.com/ShiqiYu/libfacedetection.train
|
||||
- https://github.com/zhongyy/SFace
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Thanks [Professor Shiqi Yu](https://github.com/ShiqiYu/) and [Yuantao Feng](https://github.com/fengyuentau) for training and providing the face detection model.
|
||||
|
||||
Thanks [Professor Deng](http://www.whdeng.cn/), [PhD Candidate Zhong](https://github.com/zhongyy/) and [Master Candidate Wang](https://github.com/crywang/) for training and providing the face recognition model.
|
||||
@@ -0,0 +1,74 @@
|
||||
Load Caffe framework models {#tutorial_dnn_googlenet}
|
||||
===========================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@next_tutorial{tutorial_dnn_halide}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Vitaliy Lyudvichenko |
|
||||
| Compatibility | OpenCV >= 3.3 |
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
In this tutorial you will learn how to use opencv_dnn module for image classification by using
|
||||
GoogLeNet trained network from [Caffe model zoo](http://caffe.berkeleyvision.org/model_zoo.html).
|
||||
|
||||
We will demonstrate results of this example on the following picture.
|
||||

|
||||
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
We will be using snippets from the example application, that can be downloaded [here](https://github.com/opencv/opencv/blob/4.x/samples/dnn/classification.cpp).
|
||||
|
||||
@include dnn/classification.cpp
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
-# Firstly, download GoogLeNet model files:
|
||||
[bvlc_googlenet.prototxt ](https://github.com/opencv/opencv_extra/blob/4.x/testdata/dnn/bvlc_googlenet.prototxt) and
|
||||
[bvlc_googlenet.caffemodel](http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel)
|
||||
|
||||
Also you need file with names of [ILSVRC2012](http://image-net.org/challenges/LSVRC/2012/browse-synsets) classes:
|
||||
[classification_classes_ILSVRC2012.txt](https://github.com/opencv/opencv/blob/4.x/samples/data/dnn/classification_classes_ILSVRC2012.txt).
|
||||
|
||||
Put these files into working dir of this program example.
|
||||
|
||||
-# Read and initialize network using path to .prototxt and .caffemodel files
|
||||
@snippet dnn/classification.cpp Read and initialize network
|
||||
|
||||
You can skip an argument `framework` if one of the files `model` or `config` has an
|
||||
extension `.caffemodel` or `.prototxt`.
|
||||
This way function cv::dnn::readNet can automatically detects a model's format.
|
||||
|
||||
-# Read input image and convert to the blob, acceptable by GoogleNet
|
||||
@snippet dnn/classification.cpp Open a video file or an image file or a camera stream
|
||||
|
||||
cv::VideoCapture can load both images and videos.
|
||||
|
||||
@snippet dnn/classification.cpp Create a 4D blob from a frame
|
||||
We convert the image to a 4-dimensional blob (so-called batch) with `1x3x224x224` shape
|
||||
after applying necessary pre-processing like resizing and mean subtraction
|
||||
`(-104, -117, -123)` for each blue, green and red channels correspondingly using cv::dnn::blobFromImage function.
|
||||
|
||||
-# Pass the blob to the network
|
||||
@snippet dnn/classification.cpp Set input blob
|
||||
|
||||
-# Make forward pass
|
||||
@snippet dnn/classification.cpp Make forward pass
|
||||
During the forward pass output of each network layer is computed, but in this example we need output from the last layer only.
|
||||
|
||||
-# Determine the best class
|
||||
@snippet dnn/classification.cpp Get a class with a highest score
|
||||
We put the output of network, which contain probabilities for each of 1000 ILSVRC2012 image classes, to the `prob` blob.
|
||||
And find the index of element with maximal value in this one. This index corresponds to the class of the image.
|
||||
|
||||
-# Run an example from command line
|
||||
@code
|
||||
./example_dnn_classification --model=bvlc_googlenet.caffemodel --config=bvlc_googlenet.prototxt --width=224 --height=224 --classes=classification_classes_ILSVRC2012.txt --input=space_shuttle.jpg --mean="104 117 123"
|
||||
@endcode
|
||||
For our image we get prediction of class `space shuttle` with more than 99% sureness.
|
||||
@@ -0,0 +1,88 @@
|
||||
# How to enable Halide backend for improve efficiency {#tutorial_dnn_halide}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_googlenet}
|
||||
@next_tutorial{tutorial_dnn_halide_scheduling}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Dmitry Kurtaev |
|
||||
| Compatibility | OpenCV >= 3.3 |
|
||||
|
||||
## Introduction
|
||||
This tutorial guidelines how to run your models in OpenCV deep learning module
|
||||
using Halide language backend. Halide is an open-source project that let us
|
||||
write image processing algorithms in well-readable format, schedule computations
|
||||
according to specific device and evaluate it with a quite good efficiency.
|
||||
|
||||
An official website of the Halide project: http://halide-lang.org/.
|
||||
|
||||
An up to date efficiency comparison: https://github.com/opencv/opencv/wiki/DNN-Efficiency
|
||||
|
||||
## Requirements
|
||||
### LLVM compiler
|
||||
|
||||
@note LLVM compilation might take a long time.
|
||||
|
||||
- Download LLVM source code from http://releases.llvm.org/4.0.0/llvm-4.0.0.src.tar.xz.
|
||||
Unpack it. Let **llvm_root** is a root directory of source code.
|
||||
|
||||
- Create directory **llvm_root**/tools/clang
|
||||
|
||||
- Download Clang with the same version as LLVM. In our case it will be from
|
||||
http://releases.llvm.org/4.0.0/cfe-4.0.0.src.tar.xz. Unpack it into
|
||||
**llvm_root**/tools/clang. Note that it should be a root for Clang source code.
|
||||
|
||||
- Build LLVM on Linux
|
||||
@code
|
||||
cd llvm_root
|
||||
mkdir build && cd build
|
||||
cmake -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
make -j4
|
||||
@endcode
|
||||
|
||||
- Build LLVM on Windows (Developer Command Prompt)
|
||||
@code
|
||||
mkdir \\path-to-llvm-build\\ && cd \\path-to-llvm-build\\
|
||||
cmake.exe -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\\path-to-llvm-install\\ -G "Visual Studio 14 Win64" \\path-to-llvm-src\\
|
||||
MSBuild.exe /m:4 /t:Build /p:Configuration=Release .\\INSTALL.vcxproj
|
||||
@endcode
|
||||
|
||||
@note `\\path-to-llvm-build\\` and `\\path-to-llvm-install\\` are different directories.
|
||||
|
||||
### Halide language.
|
||||
|
||||
- Download source code from GitHub repository, https://github.com/halide/Halide
|
||||
or using git. The root directory will be a **halide_root**.
|
||||
@code
|
||||
git clone https://github.com/halide/Halide.git
|
||||
@endcode
|
||||
|
||||
- Build Halide on Linux
|
||||
@code
|
||||
cd halide_root
|
||||
mkdir build && cd build
|
||||
cmake -DLLVM_DIR=llvm_root/build/lib/cmake/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_VERSION=40 -DWITH_TESTS=OFF -DWITH_APPS=OFF -DWITH_TUTORIALS=OFF ..
|
||||
make -j4
|
||||
@endcode
|
||||
|
||||
- Build Halide on Windows (Developer Command Prompt)
|
||||
@code
|
||||
cd halide_root
|
||||
mkdir build && cd build
|
||||
cmake.exe -DLLVM_DIR=\\path-to-llvm-install\\lib\\cmake\\llvm -DLLVM_VERSION=40 -DWITH_TESTS=OFF -DWITH_APPS=OFF -DWITH_TUTORIALS=OFF -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 14 Win64" ..
|
||||
MSBuild.exe /m:4 /t:Build /p:Configuration=Release .\\ALL_BUILD.vcxproj
|
||||
@endcode
|
||||
|
||||
## Build OpenCV with Halide backend
|
||||
When you build OpenCV add the following configuration flags:
|
||||
|
||||
- `WITH_HALIDE` - enable Halide linkage
|
||||
|
||||
- `HALIDE_ROOT_DIR` - path to Halide build directory
|
||||
|
||||
## Set Halide as a preferable backend
|
||||
@code
|
||||
net.setPreferableBackend(DNN_BACKEND_HALIDE);
|
||||
@endcode
|
||||
@@ -0,0 +1,92 @@
|
||||
# How to schedule your network for Halide backend {#tutorial_dnn_halide_scheduling}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_halide}
|
||||
@next_tutorial{tutorial_dnn_openvino}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Dmitry Kurtaev |
|
||||
| Compatibility | OpenCV >= 3.3 |
|
||||
|
||||
## Introduction
|
||||
Halide code is the same for every device we use. But for achieving the satisfied
|
||||
efficiency we should schedule computations properly. In this tutorial we describe
|
||||
the ways to schedule your networks using Halide backend in OpenCV deep learning module.
|
||||
|
||||
For better understanding of Halide scheduling you might want to read tutorials @ http://halide-lang.org/tutorials.
|
||||
|
||||
If it's your first meeting with Halide in OpenCV, we recommend to start from @ref tutorial_dnn_halide.
|
||||
|
||||
## Configuration files
|
||||
You can schedule computations of Halide pipeline by writing textual configuration files.
|
||||
It means that you can easily vectorize, parallelize and manage loops order of
|
||||
layers computation. Pass path to file with scheduling directives for specific
|
||||
device into ```cv::dnn::Net::setHalideScheduler``` before the first ```cv::dnn::Net::forward``` call.
|
||||
|
||||
Scheduling configuration files represented as YAML files where each node is a
|
||||
scheduled function or a scheduling directive.
|
||||
@code
|
||||
relu1:
|
||||
reorder: [x, c, y]
|
||||
split: { y: 2, c: 8 }
|
||||
parallel: [yo, co]
|
||||
unroll: yi
|
||||
vectorize: { x: 4 }
|
||||
conv1_constant_exterior:
|
||||
compute_at: { relu1: yi }
|
||||
@endcode
|
||||
|
||||
Considered use variables `n` for batch dimension, `c` for channels,
|
||||
`y` for rows and `x` for columns. For variables after split are used names
|
||||
with the same prefix but `o` and `i` suffixes for outer and inner variables
|
||||
correspondingly. In example, for variable `x` in range `[0, 10)` directive
|
||||
`split: { x: 2 }` gives new ones `xo` in range `[0, 5)` and `xi` in range `[0, 2)`.
|
||||
Variable name `x` is no longer available in the same scheduling node.
|
||||
|
||||
You can find scheduling examples at [opencv_extra/testdata/dnn](https://github.com/opencv/opencv_extra/tree/4.x/testdata/dnn)
|
||||
and use it for schedule your networks.
|
||||
|
||||
## Layers fusing
|
||||
Thanks to layers fusing we can schedule only the top layers of fused sets.
|
||||
Because for every output value we use the fused formula.
|
||||
In example, if you have three layers Convolution + Scale + ReLU one by one,
|
||||
@code
|
||||
conv(x, y, c, n) = sum(...) + bias(c);
|
||||
scale(x, y, c, n) = conv(x, y, c, n) * weights(c);
|
||||
relu(x, y, c, n) = max(scale(x, y, c, n), 0);
|
||||
@endcode
|
||||
|
||||
fused function is something like
|
||||
@code
|
||||
relu(x, y, c, n) = max((sum(...) + bias(c)) * weights(c), 0);
|
||||
@endcode
|
||||
|
||||
So only function called `relu` require scheduling.
|
||||
|
||||
## Scheduling patterns
|
||||
Sometimes networks built using blocked structure that means some layer are
|
||||
identical or quite similar. If you want to apply the same scheduling for
|
||||
different layers accurate to tiling or vectorization factors, define scheduling
|
||||
patterns in section `patterns` at the beginning of scheduling file.
|
||||
Also, your patterns may use some parametric variables.
|
||||
@code
|
||||
# At the beginning of the file
|
||||
patterns:
|
||||
fully_connected:
|
||||
split: { c: c_split }
|
||||
fuse: { src: [x, y, co], dst: block }
|
||||
parallel: block
|
||||
vectorize: { ci: c_split }
|
||||
# Somewhere below
|
||||
fc8:
|
||||
pattern: fully_connected
|
||||
params: { c_split: 8 }
|
||||
@endcode
|
||||
|
||||
## Automatic scheduling
|
||||
You can let DNN to schedule layers automatically. Just skip call of ```cv::dnn::Net::setHalideScheduler```. Sometimes it might be even more efficient than manual scheduling.
|
||||
But if specific layers require be scheduled manually, you would be able to
|
||||
mix both manual and automatic scheduling ways. Write scheduling file
|
||||
and skip layers that you want to be scheduled automatically.
|
||||
@@ -0,0 +1,54 @@
|
||||
# How to run deep networks in browser {#tutorial_dnn_javascript}
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_dnn_yolo}
|
||||
@next_tutorial{tutorial_dnn_custom_layers}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Dmitry Kurtaev |
|
||||
| Compatibility | OpenCV >= 3.3.1 |
|
||||
|
||||
## Introduction
|
||||
This tutorial will show us how to run deep learning models using OpenCV.js right
|
||||
in a browser. Tutorial refers a sample of face detection and face recognition
|
||||
models pipeline.
|
||||
|
||||
## Face detection
|
||||
Face detection network gets BGR image as input and produces set of bounding boxes
|
||||
that might contain faces. All that we need is just select the boxes with a strong
|
||||
confidence.
|
||||
|
||||
## Face recognition
|
||||
Network is called OpenFace (project https://github.com/cmusatyalab/openface).
|
||||
Face recognition model receives RGB face image of size `96x96`. Then it returns
|
||||
`128`-dimensional unit vector that represents input face as a point on the unit
|
||||
multidimensional sphere. So difference between two faces is an angle between two
|
||||
output vectors.
|
||||
|
||||
## Sample
|
||||
All the sample is an HTML page that has JavaScript code to use OpenCV.js functionality.
|
||||
You may see an insertion of this page below. Press `Start` button to begin a demo.
|
||||
Press `Add a person` to name a person that is recognized as an unknown one.
|
||||
Next we'll discuss main parts of the code.
|
||||
|
||||
@htmlinclude js_face_recognition.html
|
||||
|
||||
-# Run face detection network to detect faces on input image.
|
||||
@snippet dnn/js_face_recognition.html Run face detection model
|
||||
You may play with input blob sizes to balance detection quality and efficiency.
|
||||
The bigger input blob the smaller faces may be detected.
|
||||
|
||||
-# Run face recognition network to receive `128`-dimensional unit feature vector by input face image.
|
||||
@snippet dnn/js_face_recognition.html Get 128 floating points feature vector
|
||||
|
||||
-# Perform a recognition.
|
||||
@snippet dnn/js_face_recognition.html Recognize
|
||||
Match a new feature vector with registered ones. Return a name of the best matched person.
|
||||
|
||||
-# The main loop.
|
||||
@snippet dnn/js_face_recognition.html Define frames processing
|
||||
A main loop of our application receives a frames from a camera and makes a recognition
|
||||
of an every detected face on the frame. We start this function ones when OpenCV.js was
|
||||
initialized and deep learning models were downloaded.
|
||||
@@ -0,0 +1,39 @@
|
||||
OpenCV usage with OpenVINO {#tutorial_dnn_openvino}
|
||||
=====================
|
||||
|
||||
@prev_tutorial{tutorial_dnn_halide_scheduling}
|
||||
@next_tutorial{tutorial_dnn_yolo}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Aleksandr Voron |
|
||||
| Compatibility | OpenCV == 4.x |
|
||||
|
||||
This tutorial provides OpenCV installation guidelines how to use OpenCV with OpenVINO.
|
||||
|
||||
Since 2021.1.1 release OpenVINO does not provide pre-built OpenCV.
|
||||
The change does not affect you if you are using OpenVINO runtime directly or OpenVINO samples: it does not have a strong dependency to OpenCV.
|
||||
However, if you are using Open Model Zoo demos or OpenVINO runtime as OpenCV DNN backend you need to get the OpenCV build.
|
||||
|
||||
There are 2 approaches how to get OpenCV:
|
||||
|
||||
- Install pre-built OpenCV from another sources: system repositories, pip, conda, homebrew. Generic pre-built OpenCV package may have several limitations:
|
||||
- OpenCV version may be out-of-date
|
||||
- OpenCV may not contain G-API module with enabled OpenVINO support (e.g. some OMZ demos use G-API functionality)
|
||||
- OpenCV may not be optimized for modern hardware (default builds need to cover wide range of hardware)
|
||||
- OpenCV may not support Intel TBB, Intel Media SDK
|
||||
- OpenCV DNN module may not use OpenVINO as an inference backend
|
||||
- Build OpenCV from source code against specific version of OpenVINO. This approach solves the limitations mentioned above.
|
||||
|
||||
The instruction how to follow both approaches is provided in [OpenCV wiki](https://github.com/opencv/opencv/wiki/BuildOpenCV4OpenVINO).
|
||||
|
||||
## Supported targets
|
||||
|
||||
OpenVINO backend (DNN_BACKEND_INFERENCE_ENGINE) supports the following [targets](https://docs.opencv.org/4.x/d6/d0f/group__dnn.html#ga709af7692ba29788182cf573531b0ff5):
|
||||
|
||||
- **DNN_TARGET_CPU:** Runs on the CPU, no additional dependencies required.
|
||||
- **DNN_TARGET_OPENCL, DNN_TARGET_OPENCL_FP16:** Runs on the iGPU, requires OpenCL drivers. Install [intel-opencl-icd](https://launchpad.net/ubuntu/jammy/+package/intel-opencl-icd) on Ubuntu.
|
||||
- **DNN_TARGET_MYRIAD:** Runs on Intel® VPU like the [Neural Compute Stick](https://www.intel.com/content/www/us/en/products/sku/140109/intel-neural-compute-stick-2/specifications.html), to set up [see](https://www.intel.com/content/www/us/en/developer/archive/tools/neural-compute-stick.html).
|
||||
- **DNN_TARGET_HDDL:** Runs on the Intel® Movidius™ Myriad™ X High Density Deep Learning VPU, for details [see](https://intelsmartedge.github.io/ido-specs/doc/building-blocks/enhanced-platform-awareness/smartedge-open_hddl/).
|
||||
- **DNN_TARGET_FPGA:** Runs on Intel® Altera® series FPGAs [see](https://www.intel.com/content/www/us/en/docs/programmable/768970/2025-1/getting-started-guide.html).
|
||||
- **DNN_TARGET_NPU:** Runs on the integrated Intel® AI Boost processor, requires [Linux drivers](https://github.com/intel/linux-npu-driver/releases/tag/v1.17.0) OR [Windows drivers](https://www.intel.com/content/www/us/en/download/794734/intel-npu-driver-windows.html).
|
||||
|
After Width: | Height: | Size: 59 KiB |