mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
Update documentation ( tutorials )
This commit is contained in:
@@ -71,7 +71,7 @@ Explanation
|
||||
|
||||
- Load an image (can be BGR or grayscale)
|
||||
- Create two windows (one for dilation output, the other for erosion)
|
||||
- Create a set of 02 Trackbars for each operation:
|
||||
- Create a set of two Trackbars for each operation:
|
||||
- The first trackbar "Element" returns either **erosion_elem** or **dilation_elem**
|
||||
- The second trackbar "Kernel size" return **erosion_size** or **dilation_size** for the
|
||||
corresponding operation.
|
||||
@@ -81,23 +81,8 @@ Explanation
|
||||
Let's analyze these two functions:
|
||||
|
||||
-# **erosion:**
|
||||
@code{.cpp}
|
||||
/* @function Erosion */
|
||||
void Erosion( int, void* )
|
||||
{
|
||||
int erosion_type;
|
||||
if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
|
||||
else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
|
||||
else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp erosion
|
||||
|
||||
Mat element = getStructuringElement( erosion_type,
|
||||
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
|
||||
Point( erosion_size, erosion_size ) );
|
||||
/// Apply the erosion operation
|
||||
erode( src, erosion_dst, element );
|
||||
imshow( "Erosion Demo", erosion_dst );
|
||||
}
|
||||
@endcode
|
||||
- The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
|
||||
receives three arguments:
|
||||
- *src*: The source image
|
||||
@@ -105,11 +90,8 @@ Explanation
|
||||
- *element*: This is the kernel we will use to perform the operation. If we do not
|
||||
specify, the default is a simple `3x3` matrix. Otherwise, we can specify its
|
||||
shape. For this, we need to use the function cv::getStructuringElement :
|
||||
@code{.cpp}
|
||||
Mat element = getStructuringElement( erosion_type,
|
||||
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
|
||||
Point( erosion_size, erosion_size ) );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp kernel
|
||||
|
||||
We can choose any of three shapes for our kernel:
|
||||
|
||||
- Rectangular box: MORPH_RECT
|
||||
@@ -129,23 +111,7 @@ Reference for more details.
|
||||
The code is below. As you can see, it is completely similar to the snippet of code for **erosion**.
|
||||
Here we also have the option of defining our kernel, its anchor point and the size of the operator
|
||||
to be used.
|
||||
@code{.cpp}
|
||||
/* @function Dilation */
|
||||
void Dilation( int, void* )
|
||||
{
|
||||
int dilation_type;
|
||||
if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
|
||||
else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
|
||||
else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
|
||||
|
||||
Mat element = getStructuringElement( dilation_type,
|
||||
Size( 2*dilation_size + 1, 2*dilation_size+1 ),
|
||||
Point( dilation_size, dilation_size ) );
|
||||
/// Apply the dilation operation
|
||||
dilate( src, dilation_dst, element );
|
||||
imshow( "Dilation Demo", dilation_dst );
|
||||
}
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp dilation
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
+10
-112
@@ -16,8 +16,7 @@ Theory
|
||||
------
|
||||
|
||||
@note The explanation below belongs to the book [Computer Vision: Algorithms and
|
||||
Applications](http://szeliski.org/Book/) by Richard Szeliski and to *LearningOpenCV* .. container::
|
||||
enumeratevisibleitemswithsquare
|
||||
Applications](http://szeliski.org/Book/) by Richard Szeliski and to *LearningOpenCV*
|
||||
|
||||
- *Smoothing*, also called *blurring*, is a simple and frequently used image processing
|
||||
operation.
|
||||
@@ -96,96 +95,7 @@ Code
|
||||
- **Downloadable code**: Click
|
||||
[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Smoothing.cpp)
|
||||
- **Code at glance:**
|
||||
@code{.cpp}
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
int DELAY_CAPTION = 1500;
|
||||
int DELAY_BLUR = 100;
|
||||
int MAX_KERNEL_LENGTH = 31;
|
||||
|
||||
Mat src; Mat dst;
|
||||
char window_name[] = "Filter Demo 1";
|
||||
|
||||
/// Function headers
|
||||
int display_caption( char* caption );
|
||||
int display_dst( int delay );
|
||||
|
||||
/*
|
||||
* function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Load the source image
|
||||
src = imread( "../images/lena.jpg", 1 );
|
||||
|
||||
if( display_caption( "Original Image" ) != 0 ) { return 0; }
|
||||
|
||||
dst = src.clone();
|
||||
if( display_dst( DELAY_CAPTION ) != 0 ) { return 0; }
|
||||
|
||||
/// Applying Homogeneous blur
|
||||
if( display_caption( "Homogeneous Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ blur( src, dst, Size( i, i ), Point(-1,-1) );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Gaussian blur
|
||||
if( display_caption( "Gaussian Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ GaussianBlur( src, dst, Size( i, i ), 0, 0 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Median blur
|
||||
if( display_caption( "Median Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ medianBlur ( src, dst, i );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Bilateral Filter
|
||||
if( display_caption( "Bilateral Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ bilateralFilter ( src, dst, i, i*2, i/2 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Wait until user press a key
|
||||
display_caption( "End: Press a key!" );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int display_caption( char* caption )
|
||||
{
|
||||
dst = Mat::zeros( src.size(), src.type() );
|
||||
putText( dst, caption,
|
||||
Point( src.cols/4, src.rows/2),
|
||||
FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );
|
||||
|
||||
imshow( window_name, dst );
|
||||
int c = waitKey( DELAY_CAPTION );
|
||||
if( c >= 0 ) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
int display_dst( int delay )
|
||||
{
|
||||
imshow( window_name, dst );
|
||||
int c = waitKey ( delay );
|
||||
if( c >= 0 ) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
@endcode
|
||||
@include samples/cpp/tutorial_code/ImgProc/Smoothing.cpp
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
@@ -195,11 +105,8 @@ Explanation
|
||||
-# **Normalized Block Filter:**
|
||||
|
||||
OpenCV offers the function @ref cv::blur to perform smoothing with this filter.
|
||||
@code{.cpp}
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ blur( src, dst, Size( i, i ), Point(-1,-1) );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Smoothing.cpp blur
|
||||
|
||||
We specify 4 arguments (more details, check the Reference):
|
||||
|
||||
- *src*: Source image
|
||||
@@ -213,11 +120,8 @@ Explanation
|
||||
-# **Gaussian Filter:**
|
||||
|
||||
It is performed by the function @ref cv::GaussianBlur :
|
||||
@code{.cpp}
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ GaussianBlur( src, dst, Size( i, i ), 0, 0 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Smoothing.cpp gaussianblur
|
||||
|
||||
Here we use 4 arguments (more details, check the OpenCV reference):
|
||||
|
||||
- *src*: Source image
|
||||
@@ -233,11 +137,8 @@ Explanation
|
||||
-# **Median Filter:**
|
||||
|
||||
This filter is provided by the @ref cv::medianBlur function:
|
||||
@code{.cpp}
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ medianBlur ( src, dst, i );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Smoothing.cpp medianblur
|
||||
|
||||
We use three arguments:
|
||||
|
||||
- *src*: Source image
|
||||
@@ -247,11 +148,8 @@ Explanation
|
||||
-# **Bilateral Filter**
|
||||
|
||||
Provided by OpenCV function @ref cv::bilateralFilter
|
||||
@code{.cpp}
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ bilateralFilter ( src, dst, i, i*2, i/2 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Smoothing.cpp bilateralfilter
|
||||
|
||||
We use 5 arguments:
|
||||
|
||||
- *src*: Source image
|
||||
|
||||
@@ -81,17 +81,8 @@ Explanation
|
||||
-----------
|
||||
|
||||
-# Create some needed variables:
|
||||
@code{.cpp}
|
||||
Mat src, src_gray;
|
||||
Mat dst, detected_edges;
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp variables
|
||||
|
||||
int edgeThresh = 1;
|
||||
int lowThreshold;
|
||||
int const max_lowThreshold = 100;
|
||||
int ratio = 3;
|
||||
int kernel_size = 3;
|
||||
char* window_name = "Edge Map";
|
||||
@endcode
|
||||
Note the following:
|
||||
|
||||
-# We establish a ratio of lower:upper threshold of 3:1 (with the variable *ratio*)
|
||||
@@ -100,29 +91,16 @@ Explanation
|
||||
-# We set a maximum value for the lower Threshold of \f$100\f$.
|
||||
|
||||
-# Loads the source image:
|
||||
@code{.cpp}
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp load
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
@endcode
|
||||
-# Create a matrix of the same type and size of *src* (to be *dst*)
|
||||
@code{.cpp}
|
||||
dst.create( src.size(), src.type() );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp create_mat
|
||||
-# Convert the image to grayscale (using the function @ref cv::cvtColor :
|
||||
@code{.cpp}
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp convert_to_gray
|
||||
-# Create a window to display the results
|
||||
@code{.cpp}
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp create_window
|
||||
-# Create a Trackbar for the user to enter the lower threshold for our Canny detector:
|
||||
@code{.cpp}
|
||||
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp create_trackbar
|
||||
Observe the following:
|
||||
|
||||
-# The variable to be controlled by the Trackbar is *lowThreshold* with a limit of
|
||||
@@ -132,13 +110,9 @@ Explanation
|
||||
|
||||
-# Let's check the *CannyThreshold* function, step by step:
|
||||
-# First, we blur the image with a filter of kernel size 3:
|
||||
@code{.cpp}
|
||||
blur( src_gray, detected_edges, Size(3,3) );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp reduce_noise
|
||||
-# Second, we apply the OpenCV function @ref cv::Canny :
|
||||
@code{.cpp}
|
||||
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp canny
|
||||
where the arguments are:
|
||||
|
||||
- *detected_edges*: Source image, grayscale
|
||||
@@ -150,23 +124,16 @@ Explanation
|
||||
internally)
|
||||
|
||||
-# We fill a *dst* image with zeros (meaning the image is completely black).
|
||||
@code{.cpp}
|
||||
dst = Scalar::all(0);
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp fill
|
||||
-# Finally, we will use the function @ref cv::Mat::copyTo to map only the areas of the image that are
|
||||
identified as edges (on a black background).
|
||||
@code{.cpp}
|
||||
src.copyTo( dst, detected_edges);
|
||||
@endcode
|
||||
@ref cv::Mat::copyTo copy the *src* image onto *dst*. However, it will only copy the pixels in the
|
||||
locations where they have non-zero values. Since the output of the Canny detector is the edge
|
||||
contours on a black background, the resulting *dst* will be black in all the area but the
|
||||
detected edges.
|
||||
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp copyto
|
||||
-# We display our result:
|
||||
@code{.cpp}
|
||||
imshow( window_name, dst );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp display
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
@@ -53,61 +53,29 @@ Explanation
|
||||
-----------
|
||||
|
||||
-# First we declare the variables we are going to use:
|
||||
@code{.cpp}
|
||||
Mat src, dst;
|
||||
int top, bottom, left, right;
|
||||
int borderType;
|
||||
Scalar value;
|
||||
char* window_name = "copyMakeBorder Demo";
|
||||
RNG rng(12345);
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp variables
|
||||
|
||||
Especial attention deserves the variable *rng* which is a random number generator. We use it to
|
||||
generate the random border color, as we will see soon.
|
||||
|
||||
-# As usual we load our source image *src*:
|
||||
@code{.cpp}
|
||||
src = imread( argv[1] );
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp load
|
||||
|
||||
if( !src.data )
|
||||
{ return -1;
|
||||
printf(" No data entered, please enter the path to an image file \n");
|
||||
}
|
||||
@endcode
|
||||
-# After giving a short intro of how to use the program, we create a window:
|
||||
@code{.cpp}
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp create_window
|
||||
-# Now we initialize the argument that defines the size of the borders (*top*, *bottom*, *left* and
|
||||
*right*). We give them a value of 5% the size of *src*.
|
||||
@code{.cpp}
|
||||
top = (int) (0.05*src.rows); bottom = (int) (0.05*src.rows);
|
||||
left = (int) (0.05*src.cols); right = (int) (0.05*src.cols);
|
||||
@endcode
|
||||
-# The program begins a *while* loop. If the user presses 'c' or 'r', the *borderType* variable
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp init_arguments
|
||||
-# The program runs in a **for** loop. If the user presses 'c' or 'r', the *borderType* variable
|
||||
takes the value of *BORDER_CONSTANT* or *BORDER_REPLICATE* respectively:
|
||||
@code{.cpp}
|
||||
while( true )
|
||||
{
|
||||
c = waitKey(500);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
else if( (char)c == 'c' )
|
||||
{ borderType = BORDER_CONSTANT; }
|
||||
else if( (char)c == 'r' )
|
||||
{ borderType = BORDER_REPLICATE; }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp check_keypress
|
||||
-# In each iteration (after 0.5 seconds), the variable *value* is updated...
|
||||
@code{.cpp}
|
||||
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp update_value
|
||||
with a random value generated by the **RNG** variable *rng*. This value is a number picked
|
||||
randomly in the range \f$[0,255]\f$
|
||||
|
||||
-# Finally, we call the function @ref cv::copyMakeBorder to apply the respective padding:
|
||||
@code{.cpp}
|
||||
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp copymakeborder
|
||||
The arguments are:
|
||||
|
||||
-# *src*: Source image
|
||||
@@ -120,9 +88,7 @@ Explanation
|
||||
pixels.
|
||||
|
||||
-# We display our output image in the image created previously
|
||||
@code{.cpp}
|
||||
imshow( window_name, dst );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp display
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
@@ -63,100 +63,25 @@ Code
|
||||
|
||||
-# The tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/filter2D_demo.cpp)
|
||||
@code{.cpp}
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@include cpp/tutorial_code/ImgTrans/filter2D_demo.cpp
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/* @function main */
|
||||
int main ( int argc, char** argv )
|
||||
{
|
||||
/// Declare variables
|
||||
Mat src, dst;
|
||||
|
||||
Mat kernel;
|
||||
Point anchor;
|
||||
double delta;
|
||||
int ddepth;
|
||||
int kernel_size;
|
||||
char* window_name = "filter2D Demo";
|
||||
|
||||
int c;
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Initialize arguments for the filter
|
||||
anchor = Point( -1, -1 );
|
||||
delta = 0;
|
||||
ddepth = -1;
|
||||
|
||||
/// Loop - Will filter the image with different kernel sizes each 0.5 seconds
|
||||
int ind = 0;
|
||||
while( true )
|
||||
{
|
||||
c = waitKey(500);
|
||||
/// Press 'ESC' to exit the program
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
|
||||
/// Update kernel size for a normalized box filter
|
||||
kernel_size = 3 + 2*( ind%5 );
|
||||
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
|
||||
|
||||
/// Apply filter
|
||||
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
|
||||
imshow( window_name, dst );
|
||||
ind++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@endcode
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
-# Load an image
|
||||
@code{.cpp}
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
@endcode
|
||||
-# Create a window to display the result
|
||||
@code{.cpp}
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/filter2D_demo.cpp load
|
||||
-# Initialize the arguments for the linear filter
|
||||
@code{.cpp}
|
||||
anchor = Point( -1, -1 );
|
||||
delta = 0;
|
||||
ddepth = -1;
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/filter2D_demo.cpp init_arguments
|
||||
-# Perform an infinite loop updating the kernel size and applying our linear filter to the input
|
||||
image. Let's analyze that more in detail:
|
||||
-# First we define the kernel our filter is going to use. Here it is:
|
||||
@code{.cpp}
|
||||
kernel_size = 3 + 2*( ind%5 );
|
||||
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/filter2D_demo.cpp update_kernel
|
||||
The first line is to update the *kernel_size* to odd values in the range: \f$[3,11]\f$. The second
|
||||
line actually builds the kernel by setting its value to a matrix filled with \f$1's\f$ and
|
||||
normalizing it by dividing it between the number of elements.
|
||||
|
||||
-# After setting the kernel, we can generate the filter by using the function @ref cv::filter2D :
|
||||
@code{.cpp}
|
||||
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/filter2D_demo.cpp apply_filter
|
||||
The arguments denote:
|
||||
|
||||
-# *src*: Source image
|
||||
|
||||
@@ -48,63 +48,33 @@ Explanation
|
||||
-----------
|
||||
|
||||
-# Load an image
|
||||
@code{.cpp}
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
@endcode
|
||||
@snippet samples/cpp/houghcircles.cpp load
|
||||
-# Convert it to grayscale:
|
||||
@code{.cpp}
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
@endcode
|
||||
-# Apply a Gaussian blur to reduce noise and avoid false circle detection:
|
||||
@code{.cpp}
|
||||
GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );
|
||||
@endcode
|
||||
@snippet samples/cpp/houghcircles.cpp convert_to_gray
|
||||
-# Apply a Median blur to reduce noise and avoid false circle detection:
|
||||
@snippet samples/cpp/houghcircles.cpp reduce_noise
|
||||
-# Proceed to apply Hough Circle Transform:
|
||||
@code{.cpp}
|
||||
vector<Vec3f> circles;
|
||||
|
||||
HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );
|
||||
@endcode
|
||||
@snippet samples/cpp/houghcircles.cpp houghcircles
|
||||
with the arguments:
|
||||
|
||||
- *src_gray*: Input image (grayscale).
|
||||
- *gray*: Input image (grayscale).
|
||||
- *circles*: A vector that stores sets of 3 values: \f$x_{c}, y_{c}, r\f$ for each detected
|
||||
circle.
|
||||
- *HOUGH_GRADIENT*: Define the detection method. Currently this is the only one available in
|
||||
OpenCV.
|
||||
- *dp = 1*: The inverse ratio of resolution.
|
||||
- *min_dist = src_gray.rows/8*: Minimum distance between detected centers.
|
||||
- *min_dist = gray.rows/16*: Minimum distance between detected centers.
|
||||
- *param_1 = 200*: Upper threshold for the internal Canny edge detector.
|
||||
- *param_2* = 100\*: Threshold for center detection.
|
||||
- *min_radius = 0*: Minimum radio to be detected. If unknown, put zero as default.
|
||||
- *max_radius = 0*: Maximum radius to be detected. If unknown, put zero as default.
|
||||
|
||||
-# Draw the detected circles:
|
||||
@code{.cpp}
|
||||
for( size_t i = 0; i < circles.size(); i++ )
|
||||
{
|
||||
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
|
||||
int radius = cvRound(circles[i][2]);
|
||||
// circle center
|
||||
circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
|
||||
// circle outline
|
||||
circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
|
||||
}
|
||||
@endcode
|
||||
@snippet samples/cpp/houghcircles.cpp draw
|
||||
You can see that we will draw the circle(s) on red and the center(s) with a small green dot
|
||||
|
||||
-# Display the detected circle(s):
|
||||
@code{.cpp}
|
||||
namedWindow( "Hough Circle Transform Demo", WINDOW_AUTOSIZE );
|
||||
imshow( "Hough Circle Transform Demo", src );
|
||||
@endcode
|
||||
-# Wait for the user to exit the program
|
||||
@code{.cpp}
|
||||
waitKey(0);
|
||||
@endcode
|
||||
-# Display the detected circle(s) and wait for the user to exit the program:
|
||||
@snippet samples/cpp/houghcircles.cpp display
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
@@ -58,33 +58,15 @@ Explanation
|
||||
-----------
|
||||
|
||||
-# Create some needed variables:
|
||||
@code{.cpp}
|
||||
Mat src, src_gray, dst;
|
||||
int kernel_size = 3;
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
char* window_name = "Laplace Demo";
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp variables
|
||||
-# Loads the source image:
|
||||
@code{.cpp}
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp load
|
||||
-# Apply a Gaussian blur to reduce noise:
|
||||
@code{.cpp}
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp reduce_noise
|
||||
-# Convert the image to grayscale using @ref cv::cvtColor
|
||||
@code{.cpp}
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp convert_to_gray
|
||||
-# Apply the Laplacian operator to the grayscale image:
|
||||
@code{.cpp}
|
||||
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp laplacian
|
||||
where the arguments are:
|
||||
|
||||
- *src_gray*: The input image.
|
||||
@@ -96,13 +78,9 @@ Explanation
|
||||
- *scale*, *delta* and *BORDER_DEFAULT*: We leave them as default values.
|
||||
|
||||
-# Convert the output from the Laplacian operator to a *CV_8U* image:
|
||||
@code{.cpp}
|
||||
convertScaleAbs( dst, abs_dst );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp convert
|
||||
-# Display the result in a window:
|
||||
@code{.cpp}
|
||||
imshow( window_name, abs_dst );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp display
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
@@ -115,40 +115,16 @@ Explanation
|
||||
-----------
|
||||
|
||||
-# First we declare the variables we are going to use:
|
||||
@code{.cpp}
|
||||
Mat src, src_gray;
|
||||
Mat grad;
|
||||
char* window_name = "Sobel Demo - Simple Edge Detector";
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp variables
|
||||
-# As usual we load our source image *src*:
|
||||
@code{.cpp}
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp load
|
||||
-# First, we apply a @ref cv::GaussianBlur to our image to reduce the noise ( kernel size = 3 )
|
||||
@code{.cpp}
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp reduce_noise
|
||||
-# Now we convert our filtered image to grayscale:
|
||||
@code{.cpp}
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp convert_to_gray
|
||||
-# Second, we calculate the "*derivatives*" in *x* and *y* directions. For this, we use the
|
||||
function @ref cv::Sobel as shown below:
|
||||
@code{.cpp}
|
||||
Mat grad_x, grad_y;
|
||||
Mat abs_grad_x, abs_grad_y;
|
||||
|
||||
/// Gradient X
|
||||
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
|
||||
/// Gradient Y
|
||||
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp sobel
|
||||
The function takes the following arguments:
|
||||
|
||||
- *src_gray*: In our example, the input image. Here it is *CV_8U*
|
||||
@@ -162,19 +138,12 @@ Explanation
|
||||
\f$y_{order} = 0\f$. We do analogously for the *y* direction.
|
||||
|
||||
-# We convert our partial results back to *CV_8U*:
|
||||
@code{.cpp}
|
||||
convertScaleAbs( grad_x, abs_grad_x );
|
||||
convertScaleAbs( grad_y, abs_grad_y );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp convert
|
||||
-# Finally, we try to approximate the *gradient* by adding both directional gradients (note that
|
||||
this is not an exact calculation at all! but it is good for our purposes).
|
||||
@code{.cpp}
|
||||
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp blend
|
||||
-# Finally, we show our result:
|
||||
@code{.cpp}
|
||||
imshow( window_name, grad );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp display
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
@@ -81,76 +81,7 @@ Code
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from
|
||||
[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp)
|
||||
@code{.cpp}
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
Mat src, dst;
|
||||
|
||||
int morph_elem = 0;
|
||||
int morph_size = 0;
|
||||
int morph_operator = 0;
|
||||
int const max_operator = 4;
|
||||
int const max_elem = 2;
|
||||
int const max_kernel_size = 21;
|
||||
|
||||
char* window_name = "Morphology Transformations Demo";
|
||||
|
||||
/* Function Headers */
|
||||
void Morphology_Operations( int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create Trackbar to select Morphology operation
|
||||
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
|
||||
|
||||
/// Create Trackbar to select kernel type
|
||||
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
|
||||
&morph_elem, max_elem,
|
||||
Morphology_Operations );
|
||||
|
||||
/// Create Trackbar to choose kernel size
|
||||
createTrackbar( "Kernel size:\n 2n +1", window_name,
|
||||
&morph_size, max_kernel_size,
|
||||
Morphology_Operations );
|
||||
|
||||
/// Default start
|
||||
Morphology_Operations( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @function Morphology_Operations
|
||||
*/
|
||||
void Morphology_Operations( int, void* )
|
||||
{
|
||||
// Since MORPH_X : 2,3,4,5 and 6
|
||||
int operation = morph_operator + 2;
|
||||
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
/// Apply the specified morphology operation
|
||||
morphologyEx( src, dst, operation, element );
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
@endcode
|
||||
@include cpp/tutorial_code/ImgProc/Morphology_2.cpp
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
@@ -158,47 +89,23 @@ Explanation
|
||||
-# Let's check the general structure of the program:
|
||||
- Load an image
|
||||
- Create a window to display results of the Morphological operations
|
||||
- Create 03 Trackbars for the user to enter parameters:
|
||||
- The first trackbar **"Operator"** returns the kind of morphology operation to use
|
||||
- Create three Trackbars for the user to enter parameters:
|
||||
- The first trackbar **Operator** returns the kind of morphology operation to use
|
||||
(**morph_operator**).
|
||||
@code{.cpp}
|
||||
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat",
|
||||
window_name, &morph_operator, max_operator,
|
||||
Morphology_Operations );
|
||||
@endcode
|
||||
- The second trackbar **"Element"** returns **morph_elem**, which indicates what kind of
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_2.cpp create_trackbar1
|
||||
|
||||
- The second trackbar **Element** returns **morph_elem**, which indicates what kind of
|
||||
structure our kernel is:
|
||||
@code{.cpp}
|
||||
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
|
||||
&morph_elem, max_elem,
|
||||
Morphology_Operations );
|
||||
@endcode
|
||||
- The final trackbar **"Kernel Size"** returns the size of the kernel to be used
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_2.cpp create_trackbar2
|
||||
|
||||
- The final trackbar **Kernel Size** returns the size of the kernel to be used
|
||||
(**morph_size**)
|
||||
@code{.cpp}
|
||||
createTrackbar( "Kernel size:\n 2n +1", window_name,
|
||||
&morph_size, max_kernel_size,
|
||||
Morphology_Operations );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_2.cpp create_trackbar3
|
||||
|
||||
- Every time we move any slider, the user's function **Morphology_Operations** will be called
|
||||
to effectuate a new morphology operation and it will update the output image based on the
|
||||
current trackbar values.
|
||||
@code{.cpp}
|
||||
/*
|
||||
* @function Morphology_Operations
|
||||
*/
|
||||
void Morphology_Operations( int, void* )
|
||||
{
|
||||
// Since MORPH_X : 2,3,4,5 and 6
|
||||
int operation = morph_operator + 2;
|
||||
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
/// Apply the specified morphology operation
|
||||
morphologyEx( src, dst, operation, element );
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_2.cpp morphology_operations
|
||||
|
||||
We can observe that the key function to perform the morphology transformations is @ref
|
||||
cv::morphologyEx . In this example we use four arguments (leaving the rest as defaults):
|
||||
@@ -216,9 +123,7 @@ Explanation
|
||||
|
||||
As you can see the values range from \<2-6\>, that is why we add (+2) to the values
|
||||
entered by the Trackbar:
|
||||
@code{.cpp}
|
||||
int operation = morph_operator + 2;
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Morphology_2.cpp operation
|
||||
- **element**: The kernel to be used. We use the function @ref cv::getStructuringElement
|
||||
to define our own structure.
|
||||
|
||||
|
||||
@@ -77,13 +77,7 @@ Let's check the general structure of the program:
|
||||
|
||||
- Load an image (in this case it is defined in the program, the user does not have to enter it
|
||||
as an argument)
|
||||
@code{.cpp}
|
||||
/// Test image - Make sure it s divisible by 2^{n}
|
||||
src = imread( "../images/chicky_512.jpg" );
|
||||
if( !src.data )
|
||||
{ printf(" No data! -- Exiting the program \n");
|
||||
return -1; }
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp load
|
||||
|
||||
- Create a Mat object to store the result of the operations (*dst*) and one to save temporal
|
||||
results (*tmp*).
|
||||
@@ -95,40 +89,15 @@ Let's check the general structure of the program:
|
||||
@endcode
|
||||
|
||||
- Create a window to display the result
|
||||
@code{.cpp}
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
imshow( window_name, dst );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp create_window
|
||||
|
||||
- Perform an infinite loop waiting for user input.
|
||||
@code{.cpp}
|
||||
while( true )
|
||||
{
|
||||
int c;
|
||||
c = waitKey(10);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
if( (char)c == 'u' )
|
||||
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
|
||||
printf( "** Zoom In: Image x 2 \n" );
|
||||
}
|
||||
else if( (char)c == 'd' )
|
||||
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
|
||||
printf( "** Zoom Out: Image / 2 \n" );
|
||||
}
|
||||
|
||||
imshow( window_name, dst );
|
||||
tmp = dst;
|
||||
}
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp infinite_loop
|
||||
Our program exits if the user presses *ESC*. Besides, it has two options:
|
||||
|
||||
- **Perform upsampling (after pressing 'u')**
|
||||
@code{.cpp}
|
||||
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )
|
||||
@endcode
|
||||
We use the function @ref cv::pyrUp with 03 arguments:
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp pyrup
|
||||
We use the function @ref cv::pyrUp with three arguments:
|
||||
|
||||
- *tmp*: The current image, it is initialized with the *src* original image.
|
||||
- *dst*: The destination image (to be shown on screen, supposedly the double of the
|
||||
@@ -136,11 +105,8 @@ Let's check the general structure of the program:
|
||||
- *Size( tmp.cols*2, tmp.rows\*2 )\* : The destination size. Since we are upsampling,
|
||||
@ref cv::pyrUp expects a size double than the input image (in this case *tmp*).
|
||||
- **Perform downsampling (after pressing 'd')**
|
||||
@code{.cpp}
|
||||
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )
|
||||
@endcode
|
||||
Similarly as with @ref cv::pyrUp , we use the function @ref cv::pyrDown with 03
|
||||
arguments:
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp pyrdown
|
||||
Similarly as with @ref cv::pyrUp , we use the function @ref cv::pyrDown with three arguments:
|
||||
|
||||
- *tmp*: The current image, it is initialized with the *src* original image.
|
||||
- *dst*: The destination image (to be shown on screen, supposedly half the input
|
||||
@@ -151,15 +117,13 @@ Let's check the general structure of the program:
|
||||
both dimensions). Otherwise, an error will be shown.
|
||||
- Finally, we update the input image **tmp** with the current image displayed, so the
|
||||
subsequent operations are performed on it.
|
||||
@code{.cpp}
|
||||
tmp = dst;
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Pyramids.cpp update_tmp
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
- After compiling the code above we can test it. The program calls an image **chicky_512.jpg**
|
||||
that comes in the *tutorial_code/image* folder. Notice that this image is \f$512 \times 512\f$,
|
||||
that comes in the *samples/data* folder. Notice that this image is \f$512 \times 512\f$,
|
||||
hence a downsample won't generate any error (\f$512 = 2^{9}\f$). The original image is shown below:
|
||||
|
||||

|
||||
|
||||
@@ -106,51 +106,23 @@ Explanation
|
||||
-# Let's check the general structure of the program:
|
||||
- Load an image. If it is BGR we convert it to Grayscale. For this, remember that we can use
|
||||
the function @ref cv::cvtColor :
|
||||
@code{.cpp}
|
||||
src = imread( argv[1], 1 );
|
||||
@snippet cpp/tutorial_code/ImgProc/Threshold.cpp load
|
||||
|
||||
/// Convert the image to Gray
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
@endcode
|
||||
- Create a window to display the result
|
||||
@code{.cpp}
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
@endcode
|
||||
@snippet cpp/tutorial_code/ImgProc/Threshold.cpp window
|
||||
|
||||
- Create \f$2\f$ trackbars for the user to enter user input:
|
||||
|
||||
- **Type of thresholding**: Binary, To Zero, etc...
|
||||
- **Threshold value**
|
||||
@code{.cpp}
|
||||
createTrackbar( trackbar_type,
|
||||
window_name, &threshold_type,
|
||||
max_type, Threshold_Demo );
|
||||
@snippet cpp/tutorial_code/ImgProc/Threshold.cpp trackbar
|
||||
|
||||
createTrackbar( trackbar_value,
|
||||
window_name, &threshold_value,
|
||||
max_value, Threshold_Demo );
|
||||
@endcode
|
||||
- Wait until the user enters the threshold value, the type of thresholding (or until the
|
||||
program exits)
|
||||
- Whenever the user changes the value of any of the Trackbars, the function *Threshold_Demo*
|
||||
is called:
|
||||
@code{.cpp}
|
||||
/*
|
||||
* @function Threshold_Demo
|
||||
*/
|
||||
void Threshold_Demo( int, void* )
|
||||
{
|
||||
/* 0: Binary
|
||||
1: Binary Inverted
|
||||
2: Threshold Truncated
|
||||
3: Threshold to Zero
|
||||
4: Threshold to Zero Inverted
|
||||
*/
|
||||
@snippet cpp/tutorial_code/ImgProc/Threshold.cpp Threshold_Demo
|
||||
|
||||
threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );
|
||||
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
@endcode
|
||||
As you can see, the function @ref cv::threshold is invoked. We give \f$5\f$ parameters:
|
||||
|
||||
- *src_gray*: Our input image
|
||||
|
||||
Reference in New Issue
Block a user