feat: 切换后端至PaddleOCR-NCNN,切换工程为CMake

1.项目后端整体迁移至PaddleOCR-NCNN算法,已通过基本的兼容性测试
2.工程改为使用CMake组织,后续为了更好地兼容第三方库,不再提供QMake工程
3.重整权利声明文件,重整代码工程,确保最小化侵权风险

Log: 切换后端至PaddleOCR-NCNN,切换工程为CMake
Change-Id: I4d5d2c5d37505a4a24b389b1a4c5d12f17bfa38c
This commit is contained in:
wangzhengyang
2022-05-10 09:54:44 +08:00
parent ecdd171c6f
commit 718c41634f
10018 changed files with 3593797 additions and 186748 deletions

View File

@ -0,0 +1,88 @@
Canny Edge Detection {#tutorial_js_canny}
====================
Goal
----
- Concept of Canny edge detection
- OpenCV functions for that : **cv.Canny()**
Theory
------
Canny Edge Detection is a popular edge detection algorithm. It was developed by John F. Canny in 1986. It is a multi-stage algorithm and we will go through each stages.
-# **Noise Reduction**
Since edge detection is susceptible to noise in the image, first step is to remove the noise in the
image with a 5x5 Gaussian filter. We have already seen this in previous chapters.
-# **Finding Intensity Gradient of the Image**
Smoothened image is then filtered with a Sobel kernel in both horizontal and vertical direction to
get first derivative in horizontal direction (\f$G_x\f$) and vertical direction (\f$G_y\f$). From these two
images, we can find edge gradient and direction for each pixel as follows:
\f[
Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2} \\
Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg)
\f]
Gradient direction is always perpendicular to edges. It is rounded to one of four angles
representing vertical, horizontal and two diagonal directions.
-# **Non-maximum Suppression**
After getting gradient magnitude and direction, a full scan of image is done to remove any unwanted
pixels which may not constitute the edge. For this, at every pixel, pixel is checked if it is a
local maximum in its neighborhood in the direction of gradient. Check the image below:
![image](images/nms.jpg)
Point A is on the edge ( in vertical direction). Gradient direction is normal to the edge. Point B
and C are in gradient directions. So point A is checked with point B and C to see if it forms a
local maximum. If so, it is considered for next stage, otherwise, it is suppressed ( put to zero).
In short, the result you get is a binary image with "thin edges".
-# **Hysteresis Thresholding**
This stage decides which are all edges are really edges and which are not. For this, we need two
threshold values, minVal and maxVal. Any edges with intensity gradient more than maxVal are sure to
be edges and those below minVal are sure to be non-edges, so discarded. Those who lie between these
two thresholds are classified edges or non-edges based on their connectivity. If they are connected
to "sure-edge" pixels, they are considered to be part of edges. Otherwise, they are also discarded.
See the image below:
![image](images/hysteresis.jpg)
The edge A is above the maxVal, so considered as "sure-edge". Although edge C is below maxVal, it is
connected to edge A, so that also considered as valid edge and we get that full curve. But edge B,
although it is above minVal and is in same region as that of edge C, it is not connected to any
"sure-edge", so that is discarded. So it is very important that we have to select minVal and maxVal
accordingly to get the correct result.
This stage also removes small pixels noises on the assumption that edges are long lines.
So what we finally get is strong edges in the image.
Canny Edge Detection in OpenCV
------------------------------
We use the function: **cv.Canny(image, edges, threshold1, threshold2, apertureSize = 3, L2gradient = false)**
@param image 8-bit input image.
@param edges output edge map; single channels 8-bit image, which has the same size as image.
@param threshold1 first threshold for the hysteresis procedure.
@param threshold2 second threshold for the hysteresis procedure..
@param apertureSize aperture size for the Sobel operator.
@param L2gradient specifies the equation for finding gradient
magnitude. If it is True, it uses the equation mentioned above which is more accurate, otherwise it uses this function: \f$Edge\_Gradient \; (G) = |G_x| + |G_y|\f$.
Try it
------
\htmlonly
<iframe src="../../js_canny.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,52 @@
Changing Colorspaces {#tutorial_js_colorspaces}
====================
Goal
----
- In this tutorial, you will learn how to convert images from one color-space to another, like
RGB \f$\leftrightarrow\f$ Gray, RGB \f$\leftrightarrow\f$ HSV etc.
- You will learn following functions : **cv.cvtColor()**, **cv.inRange()** etc.
cvtColor
--------------------
There are more than 150 color-space conversion methods available in OpenCV. But we will look into
the most widely used one: RGB \f$\leftrightarrow\f$ Gray.
We use the function: **cv.cvtColor (src, dst, code, dstCn = 0)**
@param src input image.
@param dst output image of the same size and depth as src
@param code color space conversion code(see **cv.ColorConversionCodes**).
@param dstCn number of channels in the destination image; if the parameter is 0, the number of the channels is derived automatically from src and code.
For RGB \f$\rightarrow\f$ Gray conversion we use the code cv.COLOR_RGBA2GRAY.
Try it
------
\htmlonly
<iframe src="../../js_colorspaces_cvtColor.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
inRange
---------------
Checks if array elements lie between the elements of two other arrays.
We use the function: **cv.inRange (src, lowerb, upperb, dst)**
@param src first input image.
@param lowerb inclusive lower boundary Mat of the same size as src.
@param upperb inclusive upper boundary Mat of the same size as src.
@param dst output image of the same size as src and cv.CV_8U type.
Try it
------
\htmlonly
<iframe src="../../js_colorspaces_inRange.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,255 @@
Contour Features {#tutorial_js_contour_features}
================
@prev_tutorial{tutorial_js_contours_begin}
@next_tutorial{tutorial_js_contour_properties}
Goal
----
- To find the different features of contours, like area, perimeter, centroid, bounding box etc
- You will learn plenty of functions related to contours.
1. Moments
----------
Image moments help you to calculate some features like center of mass of the object, area of the
object etc. Check out the wikipedia page on [Image
Moments](http://en.wikipedia.org/wiki/Image_moment)
We use the function: **cv.moments (array, binaryImage = false)**
@param array raster image (single-channel, 8-bit or floating-point 2D array) or an array ( 1×N or N×1 ) of 2D points.
@param binaryImage if it is true, all non-zero image pixels are treated as 1's. The parameter is used for images only.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_moments.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
From this moments, you can extract useful data like area, centroid etc. Centroid is given by the
relations, \f$C_x = \frac{M_{10}}{M_{00}}\f$ and \f$C_y = \frac{M_{01}}{M_{00}}\f$. This can be done as
follows:
@code{.js}
let cx = M.m10/M.m00
let cy = M.m01/M.m00
@endcode
2. Contour Area
---------------
Contour area is given by the function **cv.contourArea()** or from moments, **M['m00']**.
We use the function: **cv.contourArea (contour, oriented = false)**
@param contour input vector of 2D points (contour vertices)
@param oriented oriented area flag. If it is true, the function returns a signed area value, depending on the contour orientation (clockwise or counter-clockwise). Using this feature you can determine orientation of a contour by taking the sign of an area. By default, the parameter is false, which means that the absolute value is returned.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_area.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
3. Contour Perimeter
--------------------
It is also called arc length. It can be found out using **cv.arcLength()** function.
We use the function: **cv.arcLength (curve, closed)**
@param curve input vector of 2D points.
@param closed flag indicating whether the curve is closed or not.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_perimeter.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
4. Contour Approximation
------------------------
It approximates a contour shape to another shape with less number of vertices depending upon the
precision we specify. It is an implementation of [Douglas-Peucker
algorithm](http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm). Check the wikipedia page
for algorithm and demonstration.
We use the function: **cv.approxPolyDP (curve, approxCurve, epsilon, closed)**
@param curve input vector of 2D points stored in cv.Mat.
@param approxCurve result of the approximation. The type should match the type of the input curve.
@param epsilon parameter specifying the approximation accuracy. This is the maximum distance between the original curve and its approximation.
@param closed If true, the approximated curve is closed (its first and last vertices are connected). Otherwise, it is not closed.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_approxPolyDP.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
5. Convex Hull
--------------
Convex Hull will look similar to contour approximation, but it is not (Both may provide same results
in some cases). Here, **cv.convexHull()** function checks a curve for convexity defects and
corrects it. Generally speaking, convex curves are the curves which are always bulged out, or
at-least flat. And if it is bulged inside, it is called convexity defects. For example, check the
below image of hand. Red line shows the convex hull of hand. The double-sided arrow marks shows the
convexity defects, which are the local maximum deviations of hull from contours.
![image](images/convexitydefects.jpg)
We use the function: **cv.convexHull (points, hull, clockwise = false, returnPoints = true)**
@param points input 2D point set.
@param hull output convex hull.
@param clockwise orientation flag. If it is true, the output convex hull is oriented clockwise. Otherwise, it is oriented counter-clockwise. The assumed coordinate system has its X axis pointing to the right, and its Y axis pointing upwards.
@param returnPoints operation flag. In case of a matrix, when the flag is true, the function returns convex hull points. Otherwise, it returns indices of the convex hull points.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_convexHull.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
6. Checking Convexity
---------------------
There is a function to check if a curve is convex or not, **cv.isContourConvex()**. It just return
whether True or False. Not a big deal.
@code{.js}
cv.isContourConvex(cnt);
@endcode
7. Bounding Rectangle
---------------------
There are two types of bounding rectangles.
### 7.a. Straight Bounding Rectangle
It is a straight rectangle, it doesn't consider the rotation of the object. So area of the bounding
rectangle won't be minimum.
We use the function: **cv.boundingRect (points)**
@param points input 2D point set.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_boundingRect.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 7.b. Rotated Rectangle
Here, bounding rectangle is drawn with minimum area, so it considers the rotation also.
We use the function: **cv.minAreaRect (points)**
@param points input 2D point set.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_minAreaRect.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
8. Minimum Enclosing Circle
---------------------------
Next we find the circumcircle of an object using the function **cv.minEnclosingCircle()**. It is a
circle which completely covers the object with minimum area.
We use the functions: **cv.minEnclosingCircle (points)**
@param points input 2D point set.
**cv.circle (img, center, radius, color, thickness = 1, lineType = cv.LINE_8, shift = 0)**
@param img image where the circle is drawn.
@param center center of the circle.
@param radius radius of the circle.
@param color circle color.
@param thickness thickness of the circle outline, if positive. Negative thickness means that a filled circle is to be drawn.
@param lineType type of the circle boundary.
@param shift number of fractional bits in the coordinates of the center and in the radius value.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_minEnclosingCircle.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
9. Fitting an Ellipse
---------------------
Next one is to fit an ellipse to an object. It returns the rotated rectangle in which the ellipse is
inscribed.
We use the functions: **cv.fitEllipse (points)**
@param points input 2D point set.
**cv.ellipse1 (img, box, color, thickness = 1, lineType = cv.LINE_8)**
@param img image.
@param box alternative ellipse representation via RotatedRect. This means that the function draws an ellipse inscribed in the rotated rectangle.
@param color ellipse color.
@param thickness thickness of the ellipse arc outline, if positive. Otherwise, this indicates that a filled ellipse sector is to be drawn.
@param lineType type of the ellipse boundary.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_fitEllipse.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
10. Fitting a Line
------------------
Similarly we can fit a line to a set of points. We can approximate a straight line to it.
We use the functions: **cv.fitLine (points, line, distType, param, reps, aeps)**
@param points input 2D point set.
@param line output line parameters. It should be a Mat of 4 elements[vx, vy, x0, y0], where [vx, vy] is a normalized vector collinear to the line and [x0, y0] is a point on the line.
@param distType distance used by the M-estimator(see cv.DistanceTypes).
@param param numerical parameter ( C ) for some types of distances. If it is 0, an optimal value is chosen.
@param reps sufficient accuracy for the radius (distance between the coordinate origin and the line).
@param aeps sufficient accuracy for the angle. 0.01 would be a good default value for reps and aeps.
**cv.line (img, pt1, pt2, color, thickness = 1, lineType = cv.LINE_8, shift = 0)**
@param img image.
@param pt1 first point of the line segment.
@param pt2 second point of the line segment.
@param color line color.
@param thickness line thickness.
@param lineType type of the line,.
@param shift number of fractional bits in the point coordinates.
Try it
------
\htmlonly
<iframe src="../../js_contour_features_fitLine.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,113 @@
Contour Properties {#tutorial_js_contour_properties}
==================
@prev_tutorial{tutorial_js_contour_features}
@next_tutorial{tutorial_js_contours_more_functions}
Goal
----
- Here we will learn to extract some frequently used properties of objects like Solidity, Equivalent
Diameter, Mask image, Mean Intensity etc.
1. Aspect Ratio
---------------
It is the ratio of width to height of bounding rect of the object.
\f[Aspect \; Ratio = \frac{Width}{Height}\f]
@code{.js}
let rect = cv.boundingRect(cnt);
let aspectRatio = rect.width / rect.height;
@endcode
2. Extent
---------
Extent is the ratio of contour area to bounding rectangle area.
\f[Extent = \frac{Object \; Area}{Bounding \; Rectangle \; Area}\f]
@code{.js}
let area = cv.contourArea(cnt, false);
let rect = cv.boundingRect(cnt));
let rectArea = rect.width * rect.height;
let extent = area / rectArea;
@endcode
3. Solidity
-----------
Solidity is the ratio of contour area to its convex hull area.
\f[Solidity = \frac{Contour \; Area}{Convex \; Hull \; Area}\f]
@code{.js}
let area = cv.contourArea(cnt, false);
cv.convexHull(cnt, hull, false, true);
let hullArea = cv.contourArea(hull, false);
let solidity = area / hullArea;
@endcode
4. Equivalent Diameter
----------------------
Equivalent Diameter is the diameter of the circle whose area is same as the contour area.
\f[Equivalent \; Diameter = \sqrt{\frac{4 \times Contour \; Area}{\pi}}\f]
@code{.js}
let area = cv.contourArea(cnt, false);
let equiDiameter = Math.sqrt(4 * area / Math.PI);
@endcode
5. Orientation
--------------
Orientation is the angle at which object is directed. Following method also gives the Major Axis and
Minor Axis lengths.
@code{.js}
let rotatedRect = cv.fitEllipse(cnt);
let angle = rotatedRect.angle;
@endcode
6. Mask and Pixel Points
------------------------
In some cases, we may need all the points which comprises that object.
We use the function: **cv.transpose (src, dst)**
@param src input array.
@param dst output array of the same type as src.
\htmlonly
<iframe src="../../js_contour_properties_transpose.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
7. Maximum Value, Minimum Value and their locations
---------------------------------------------------
We use the function: **cv.minMaxLoc(src, mask)**
@param src input single-channel array.
@param mask optional mask used to select a sub-array.
@code{.js}
let result = cv.minMaxLoc(src, mask);
let minVal = result.minVal;
let maxVal = result.maxVal;
let minLoc = result.minLoc;
let maxLoc = result.maxLoc;
@endcode
8. Mean Color or Mean Intensity
-------------------------------
Here, we can find the average color of an object. Or it can be average intensity of the object in
grayscale mode. We again use the same mask to do it.
We use the function: **cv.mean (src, mask)**
@param src input array that should have from 1 to 4 channels so that the result can be stored in Scalar.
@param mask optional operation mask.
@code{.js}
let average = cv.mean(src, mask);
@endcode

View File

@ -0,0 +1,74 @@
Contours : Getting Started {#tutorial_js_contours_begin}
==========================
@next_tutorial{tutorial_js_contour_features}
Goal
----
- Understand what contours are.
- Learn to find contours, draw contours etc
- You will learn these functions : **cv.findContours()**, **cv.drawContours()**
What are contours?
------------------
Contours can be explained simply as a curve joining all the continuous points (along the boundary),
having same color or intensity. The contours are a useful tool for shape analysis and object
detection and recognition.
- For better accuracy, use binary images. So before finding contours, apply threshold or canny
edge detection.
- Since opencv 3.2 source image is not modified by this function.
- In OpenCV, finding contours is like finding white object from black background. So remember,
object to be found should be white and background should be black.
How to draw the contours?
-------------------------
To draw the contours, cv.drawContours function is used. It can also be used to draw any shape
provided you have its boundary points.
We use the functions: **cv.findContours (image, contours, hierarchy, mode, method, offset = new cv.Point(0, 0))**
@param image source, an 8-bit single-channel image. Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary.
@param contours detected contours.
@param hierarchy containing information about the image topology. It has as many elements as the number of contours.
@param mode contour retrieval mode(see cv.RetrievalModes).
@param method contour approximation method(see cv.ContourApproximationModes).
@param offset optional offset by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context.
**cv.drawContours (image, contours, contourIdx, color, thickness = 1, lineType = cv.LINE_8, hierarchy = new cv.Mat(), maxLevel = INT_MAX, offset = new cv.Point(0, 0))**
@param image destination image.
@param contours all the input contours.
@param contourIdx parameter indicating a contour to draw. If it is negative, all the contours are drawn.
@param color color of the contours.
@param thickness thickness of lines the contours are drawn with. If it is negative, the contour interiors are drawn.
@param lineType line connectivity(see cv.LineTypes).
@param hierarchy optional information about hierarchy. It is only needed if you want to draw only some of the contours(see maxLevel).
@param maxLevel maximal level for drawn contours. If it is 0, only the specified contour is drawn. If it is 1, the function draws the contour(s) and all the nested contours. If it is 2, the function draws the contours, all the nested contours, all the nested-to-nested contours, and so on. This parameter is only taken into account when there is hierarchy available.
@param offset optional contour shift parameter.
Try it
------
\htmlonly
<iframe src="../../js_contours_begin_contours.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Contour Approximation Method
============================
This is the fifth argument in cv.findContours function. What does it denote actually?
Above, we told that contours are the boundaries of a shape with same intensity. It stores the (x,y)
coordinates of the boundary of a shape. But does it store all the coordinates ? That is specified by
this contour approximation method.
If you pass cv.ContourApproximationModes.CHAIN_APPROX_NONE.value, all the boundary points are stored. But actually do we need all
the points? For eg, you found the contour of a straight line. Do you need all the points on the line
to represent that line? No, we need just two end points of that line. This is what
cv.CHAIN_APPROX_SIMPLE does. It removes all redundant points and compresses the contour, thereby
saving memory.

View File

@ -0,0 +1,160 @@
Contours Hierarchy {#tutorial_js_contours_hierarchy}
==================
@prev_tutorial{tutorial_js_contours_more_functions}
Goal
----
- This time, we learn about the hierarchy of contours, i.e. the parent-child relationship in Contours.
Theory
------
In the last few articles on contours, we have worked with several functions related to contours
provided by OpenCV. But when we found the contours in image using **cv.findContours()** function,
we have passed an argument, **Contour Retrieval Mode**. We usually passed **cv.RETR_LIST** or
**cv.RETR_TREE** and it worked nice. But what does it actually mean ?
Also, in the output, we got three arrays, first is the image, second is our contours, and one more
output which we named as **hierarchy** (Please checkout the codes in previous articles). But we
never used this hierarchy anywhere. Then what is this hierarchy and what is it for ? What is its
relationship with the previous mentioned function argument ?
That is what we are going to deal in this article.
### What is Hierarchy?
Normally we use the **cv.findContours()** function to detect objects in an image, right ? Sometimes
objects are in different locations. But in some cases, some shapes are inside other shapes. Just
like nested figures. In this case, we call outer one as **parent** and inner one as **child**. This
way, contours in an image has some relationship to each other. And we can specify how one contour is
connected to each other, like, is it child of some other contour, or is it a parent etc.
Representation of this relationship is called the **Hierarchy**.
Consider an example image below :
![image](images/hierarchy.png)
In this image, there are a few shapes which I have numbered from **0-5**. *2 and 2a* denotes the
external and internal contours of the outermost box.
Here, contours 0,1,2 are **external or outermost**. We can say, they are in **hierarchy-0** or
simply they are in **same hierarchy level**.
Next comes **contour-2a**. It can be considered as a **child of contour-2** (or in opposite way,
contour-2 is parent of contour-2a). So let it be in **hierarchy-1**. Similarly contour-3 is child of
contour-2a and it comes in next hierarchy. Finally contours 4,5 are the children of contour-3a, and
they come in the last hierarchy level. From the way I numbered the boxes, I would say contour-4 is
the first child of contour-3a (It can be contour-5 also).
I mentioned these things to understand terms like **same hierarchy level**, **external contour**,
**child contour**, **parent contour**, **first child** etc. Now let's get into OpenCV.
### Hierarchy Representation in OpenCV
So each contour has its own information regarding what hierarchy it is, who is its child, who is its
parent etc. OpenCV represents it as an array of four values : **[Next, Previous, First_Child,
Parent]**
<center>*"Next denotes next contour at the same hierarchical level."*</center>
For eg, take contour-0 in our picture. Who is next contour in its same level ? It is contour-1. So
simply put Next = 1. Similarly for Contour-1, next is contour-2. So Next = 2.
What about contour-2? There is no next contour in the same level. So simply, put Next = -1. What
about contour-4? It is in same level with contour-5. So its next contour is contour-5, so Next = 5.
<center>*"Previous denotes previous contour at the same hierarchical level."*</center>
It is same as above. Previous contour of contour-1 is contour-0 in the same level. Similarly for
contour-2, it is contour-1. And for contour-0, there is no previous, so put it as -1.
<center>*"First_Child denotes its first child contour."*</center>
There is no need of any explanation. For contour-2, child is contour-2a. So it gets the
corresponding index value of contour-2a. What about contour-3a? It has two children. But we take
only first child. And it is contour-4. So First_Child = 4 for contour-3a.
<center>*"Parent denotes index of its parent contour."*</center>
It is just opposite of **First_Child**. Both for contour-4 and contour-5, parent contour is
contour-3a. For contour-3a, it is contour-3 and so on.
@note If there is no child or parent, that field is taken as -1
So now we know about the hierarchy style used in OpenCV, we can check into Contour Retrieval Modes
in OpenCV with the help of same image given above. ie what do flags like cv.RETR_LIST,
cv.RETR_TREE, cv.RETR_CCOMP, cv.RETR_EXTERNAL etc mean?
Contour Retrieval Mode
----------------------
### 1. RETR_LIST
This is the simplest of the four flags (from explanation point of view). It simply retrieves all the
contours, but doesn't create any parent-child relationship. **Parents and kids are equal under this
rule, and they are just contours**. ie they all belongs to same hierarchy level.
So here, 3rd and 4th term in hierarchy array is always -1. But obviously, Next and Previous terms
will have their corresponding values.
### 2. RETR_EXTERNAL
If you use this flag, it returns only extreme outer flags. All child contours are left behind. **We
can say, under this law, Only the eldest in every family is taken care of. It doesn't care about
other members of the family)**.
### 3. RETR_CCOMP
This flag retrieves all the contours and arranges them to a 2-level hierarchy. ie external contours
of the object (ie its boundary) are placed in hierarchy-1. And the contours of holes inside object
(if any) is placed in hierarchy-2. If any object inside it, its contour is placed again in
hierarchy-1 only. And its hole in hierarchy-2 and so on.
Just consider the image of a "big white zero" on a black background. Outer circle of zero belongs to
first hierarchy, and inner circle of zero belongs to second hierarchy.
We can explain it with a simple image. Here I have labelled the order of contours in red color and
the hierarchy they belongs to, in green color (either 1 or 2). The order is same as the order OpenCV
detects contours.
![image](images/ccomp_hierarchy.png)
So consider first contour, ie contour-0. It is hierarchy-1. It has two holes, contours 1&2, and they
belong to hierarchy-2. So for contour-0, Next contour in same hierarchy level is contour-3. And
there is no previous one. And its first is child is contour-1 in hierarchy-2. It has no parent,
because it is in hierarchy-1. So its hierarchy array is [3,-1,1,-1]
Now take contour-1. It is in hierarchy-2. Next one in same hierarchy (under the parenthood of
contour-1) is contour-2. No previous one. No child, but parent is contour-0. So array is
[2,-1,-1,0].
Similarly contour-2 : It is in hierarchy-2. There is not next contour in same hierarchy under
contour-0. So no Next. Previous is contour-1. No child, parent is contour-0. So array is
[-1,1,-1,0].
Contour - 3 : Next in hierarchy-1 is contour-5. Previous is contour-0. Child is contour-4 and no
parent. So array is [5,0,4,-1].
Contour - 4 : It is in hierarchy 2 under contour-3 and it has no sibling. So no next, no previous,
no child, parent is contour-3. So array is [-1,-1,-1,3].
### 4. RETR_TREE
And this is the final guy, Mr.Perfect. It retrieves all the contours and creates a full family
hierarchy list. **It even tells, who is the grandpa, father, son, grandson and even beyond... :)**.
For example, I took above image, rewrite the code for cv.RETR_TREE, reorder the contours as per the
result given by OpenCV and analyze it. Again, red letters give the contour number and green letters
give the hierarchy order.
![image](images/tree_hierarchy.png)
Take contour-0 : It is in hierarchy-0. Next contour in same hierarchy is contour-7. No previous
contours. Child is contour-1. And no parent. So array is [7,-1,1,-1].
Take contour-2 : It is in hierarchy-1. No contour in same level. No previous one. Child is
contour-2. Parent is contour-0. So array is [-1,-1,2,0].

View File

@ -0,0 +1,75 @@
Contours : More Functions {#tutorial_js_contours_more_functions}
=========================
@prev_tutorial{tutorial_js_contour_properties}
@next_tutorial{tutorial_js_contours_hierarchy}
Goal
----
- Convexity defects and how to find them.
- Finding shortest distance from a point to a polygon
- Matching different shapes
Theory and Code
---------------
### 1. Convexity Defects
We saw what is convex hull in second chapter about contours. Any deviation of the object from this
hull can be considered as convexity defect.We can visualize it using an image. We draw a
line joining start point and end point, then draw a circle at the farthest point.
@note Remember we have to pass returnPoints = False while finding convex hull, in order to find
convexity defects.
We use the function: **cv.convexityDefects (contour, convexhull, convexityDefect)**
@param contour input contour.
@param convexhull convex hull obtained using convexHull that should contain indices of the contour points that make the hull
@param convexityDefect the output vector of convexity defects. Each convexity defect is represented as 4-element(start_index, end_index, farthest_pt_index, fixpt_depth), where indices are 0-based indices in the original contour of the convexity defect beginning, end and the farthest point, and fixpt_depth is fixed-point approximation (with 8 fractional bits) of the distance between the farthest contour point and the hull. That is, to get the floating-point value of the depth will be fixpt_depth/256.0.
Try it
------
\htmlonly
<iframe src="../../js_contours_more_functions_convexityDefects.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 2. Point Polygon Test
This function finds the shortest distance between a point in the image and a contour. It returns the
distance which is negative when point is outside the contour, positive when point is inside and zero
if point is on the contour.
We use the function: **cv.pointPolygonTest (contour, pt, measureDist)**
@param contour input contour.
@param pt point tested against the contour.
@param measureDist if true, the function estimates the signed distance from the point to the nearest contour edge. Otherwise, the function only checks if the point is inside a contour or not.
@code{.js}
let dist = cv.pointPolygonTest(cnt, new cv.Point(50, 50), true);
@endcode
### 3. Match Shapes
OpenCV comes with a function **cv.matchShapes()** which enables us to compare two shapes, or two
contours and returns a metric showing the similarity. The lower the result, the better match it is.
It is calculated based on the hu-moment values. Different measurement methods are explained in the
docs.
We use the function: **cv.matchShapes (contour1, contour2, method, parameter)**
@param contour1 first contour or grayscale image.
@param contour2 second contour or grayscale image.
@param method comparison method, see cv::ShapeMatchModes
@param parameter method-specific parameter(not supported now).
Try it
------
\htmlonly
<iframe src="../../js_contours_more_functions_shape.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,26 @@
Contours in OpenCV.js {#tutorial_js_table_of_contents_contours}
==================
- @subpage tutorial_js_contours_begin
Learn to find and draw Contours.
- @subpage tutorial_js_contour_features
Learn
to find different features of contours like area, perimeter, bounding rectangle etc.
- @subpage tutorial_js_contour_properties
Learn
to find different properties of contours like Solidity, Mean Intensity etc.
- @subpage tutorial_js_contours_more_functions
Learn
to find convexity defects, pointPolygonTest, match different shapes etc.
- @subpage tutorial_js_contours_hierarchy
Learn
about Contour Hierarchy

View File

@ -0,0 +1,163 @@
Smoothing Images {#tutorial_js_filtering}
================
Goals
-----
- Blur the images with various low pass filters
- Apply custom-made filters to images (2D convolution)
2D Convolution ( Image Filtering )
----------------------------------
As in one-dimensional signals, images also can be filtered with various low-pass filters(LPF),
high-pass filters(HPF) etc. LPF helps in removing noises, blurring the images etc. HPF filters helps
in finding edges in the images.
OpenCV provides a function **cv.filter2D()** to convolve a kernel with an image. As an example, we
will try an averaging filter on an image. A 5x5 averaging filter kernel will look like below:
\f[K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}\f]
We use the functions: **cv.filter2D (src, dst, ddepth, kernel, anchor = new cv.Point(-1, -1), delta = 0, borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image of the same size and the same number of channels as src.
@param ddepth desired depth of the destination image.
@param kernel convolution kernel (or rather a correlation kernel), a single-channel floating point matrix; if you want to apply different kernels to different channels, split the image into separate color planes using split and process them individually.
@param anchor anchor of the kernel that indicates the relative position of a filtered point within the kernel; the anchor should lie within the kernel; default value new cv.Point(-1, -1) means that the anchor is at the kernel center.
@param delta optional value added to the filtered pixels before storing them in dst.
@param borderType pixel extrapolation method(see cv.BorderTypes).
Try it
------
\htmlonly
<iframe src="../../js_filtering_filter.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Image Blurring (Image Smoothing)
--------------------------------
Image blurring is achieved by convolving the image with a low-pass filter kernel. It is useful for
removing noises. It actually removes high frequency content (eg: noise, edges) from the image. So
edges are blurred a little bit in this operation. (Well, there are blurring techniques which doesn't
blur the edges too). OpenCV provides mainly four types of blurring techniques.
### 1. Averaging
This is done by convolving image with a normalized box filter. It simply takes the average of all
the pixels under kernel area and replace the central element. This is done by the function
**cv.blur()** or **cv.boxFilter()**. Check the docs for more details about the kernel. We should
specify the width and height of kernel. A 3x3 normalized box filter would look like below:
\f[K = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix}\f]
We use the functions: **cv.blur (src, dst, ksize, anchor = new cv.Point(-1, -1), borderType = cv.BORDER_DEFAULT)**
@param src input image; it can have any number of channels, which are processed independently, but the depth should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst output image of the same size and type as src.
@param ksize blurring kernel size.
@param anchor anchor point; anchor = new cv.Point(-1, -1) means that the anchor is at the kernel center.
@param borderType border mode used to extrapolate pixels outside of the image(see cv.BorderTypes).
**cv.boxFilter (src, dst, ddepth, ksize, anchor = new cv.Point(-1, -1), normalize = true, borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image of the same size and type as src.
@param ddepth the output image depth (-1 to use src.depth()).
@param ksize blurring kernel size.
@param anchor anchor point; anchor = new cv.Point(-1, -1) means that the anchor is at the kernel center.
@param normalize flag, specifying whether the kernel is normalized by its area or not.
@param borderType border mode used to extrapolate pixels outside of the image(see cv.BorderTypes).
@note If you don't want to use normalized box filter, use **cv.boxFilter()**. Pass an argument
normalize = false to the function.
Try it
------
\htmlonly
<iframe src="../../js_filtering_blur.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 2. Gaussian Blurring
In this, instead of box filter, gaussian kernel is used.
We use the function: **cv.GaussianBlur (src, dst, ksize, sigmaX, sigmaY = 0, borderType = cv.BORDER_DEFAULT)**
@param src input image; the image can have any number of channels, which are processed independently, but the depth should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst output image of the same size and type as src.
@param ksize blurring kernel size.
@param sigmaX Gaussian kernel standard deviation in X direction.
@param sigmaY Gaussian kernel standard deviation in Y direction; if sigmaY is zero, it is set to be equal to sigmaX, if both sigmas are zeros, they are computed from ksize.width and ksize.height, to fully control the result regardless of possible future modifications of all this semantics, it is recommended to specify all of ksize, sigmaX, and sigmaY.
@param borderType pixel extrapolation method(see cv.BorderTypes).
Try it
------
\htmlonly
<iframe src="../../js_filtering_GaussianBlur.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 3. Median Blurring
Here, the function **cv.medianBlur()** takes median of all the pixels under kernel area and central
element is replaced with this median value. This is highly effective against salt-and-pepper noise
in the images. Interesting thing is that, in the above filters, central element is a newly
calculated value which may be a pixel value in the image or a new value. But in median blurring,
central element is always replaced by some pixel value in the image. It reduces the noise
effectively. Its kernel size should be a positive odd integer.
We use the function: **cv.medianBlur (src, dst, ksize)**
@param src input 1, 3, or 4 channel image; when ksize is 3 or 5, the image depth should be cv.CV_8U, cv.CV_16U, or cv.CV_32F, for larger aperture sizes, it can only be cv.CV_8U.
@param dst destination array of the same size and type as src.
@param ksize aperture linear size; it must be odd and greater than 1, for example: 3, 5, 7 ...
@note The median filter uses cv.BORDER_REPLICATE internally to cope with border pixels.
Try it
------
\htmlonly
<iframe src="../../js_filtering_medianBlur.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 4. Bilateral Filtering
**cv.bilateralFilter()** is highly effective in noise removal while keeping edges sharp. But the
operation is slower compared to other filters. We already saw that gaussian filter takes the a
neighbourhood around the pixel and find its gaussian weighted average. This gaussian filter is a
function of space alone, that is, nearby pixels are considered while filtering. It doesn't consider
whether pixels have almost same intensity. It doesn't consider whether pixel is an edge pixel or
not. So it blurs the edges also, which we don't want to do.
Bilateral filter also takes a gaussian filter in space, but one more gaussian filter which is a
function of pixel difference. Gaussian function of space make sure only nearby pixels are considered
for blurring while gaussian function of intensity difference make sure only those pixels with
similar intensity to central pixel is considered for blurring. So it preserves the edges since
pixels at edges will have large intensity variation.
We use the function: **cv.bilateralFilter (src, dst, d, sigmaColor, sigmaSpace, borderType = cv.BORDER_DEFAULT)**
@param src source 8-bit or floating-point, 1-channel or 3-channel image.
@param dst output image of the same size and type as src.
@param d diameter of each pixel neighborhood that is used during filtering. If it is non-positive, it is computed from sigmaSpace.
@param sigmaColor filter sigma in the color space. A larger value of the parameter means that farther colors within the pixel neighborhood will be mixed together, resulting in larger areas of semi-equal color.
@param sigmaSpace filter sigma in the coordinate space. A larger value of the parameter means that farther pixels will influence each other as long as their colors are close enough. When d>0, it specifies the neighborhood size regardless of sigmaSpace. Otherwise, d is proportional to sigmaSpace.
@param borderType border mode used to extrapolate pixels outside of the image(see cv.BorderTypes).
@note For simplicity, you can set the 2 sigma values to be the same. If they are small (< 10), the filter will not have much effect, whereas if they are large (> 150), they will have a very strong effect, making the image look "cartoonish". Large filters (d > 5) are very slow, so it is recommended to use d=5 for real-time applications, and perhaps d=9 for offline applications that need heavy noise filtering.
Try it
------
\htmlonly
<iframe src="../../js_filtering_bilateralFilter.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,145 @@
Geometric Transformations of Images {#tutorial_js_geometric_transformations}
===================================
Goals
-----
- Learn how to apply different geometric transformation to images like translation, rotation, affine
transformation etc.
- You will learn these functions: **cv.resize**, **cv.warpAffine**, **cv.getAffineTransform** and **cv.warpPerspective**
Transformations
---------------
### Scaling
Scaling is just resizing of the image. OpenCV comes with a function **cv.resize()** for this
purpose. The size of the image can be specified manually, or you can specify the scaling factor.
Different interpolation methods are used. Preferable interpolation methods are **cv.INTER_AREA**
for shrinking and **cv.INTER_CUBIC** (slow) & **cv.INTER_LINEAR** for zooming.
We use the function: **cv.resize (src, dst, dsize, fx = 0, fy = 0, interpolation = cv.INTER_LINEAR)**
@param src input image
@param dst output image; it has the size dsize (when it is non-zero) or the size computed from src.size(), fx, and fy; the type of dst is the same as of src.
@param dsize output image size; if it equals zero, it is computed as:
\f[𝚍𝚜𝚒𝚣𝚎 = 𝚂𝚒𝚣𝚎(𝚛𝚘𝚞𝚗𝚍(𝚏𝚡*𝚜𝚛𝚌.𝚌𝚘𝚕𝚜), 𝚛𝚘𝚞𝚗𝚍(𝚏𝚢*𝚜𝚛𝚌.𝚛𝚘𝚠𝚜))\f]
Either dsize or both fx and fy must be non-zero.
@param fx scale factor along the horizontal axis; when it equals 0, it is computed as \f[(𝚍𝚘𝚞𝚋𝚕𝚎)𝚍𝚜𝚒𝚣𝚎.𝚠𝚒𝚍𝚝𝚑/𝚜𝚛𝚌.𝚌𝚘𝚕𝚜\f]
@param fy scale factor along the vertical axis; when it equals 0, it is computed as \f[(𝚍𝚘𝚞𝚋𝚕𝚎)𝚍𝚜𝚒𝚣𝚎.𝚑𝚎𝚒𝚐𝚑𝚝/𝚜𝚛𝚌.𝚛𝚘𝚠𝚜\f]
@param interpolation interpolation method(see **cv.InterpolationFlags**)
Try it
------
\htmlonly
<iframe src="../../js_geometric_transformations_resize.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### Translation
Translation is the shifting of object's location. If you know the shift in (x,y) direction, let it
be \f$(t_x,t_y)\f$, you can create the transformation matrix \f$\textbf{M}\f$ as follows:
\f[M = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \end{bmatrix}\f]
We use the function: **cv.warpAffine (src, dst, M, dsize, flags = cv.INTER_LINEAR, borderMode = cv.BORDER_CONSTANT, borderValue = new cv.Scalar())**
@param src input image.
@param dst output image that has the size dsize and the same type as src.
@param Mat 2 × 3 transformation matrix(cv.CV_64FC1 type).
@param dsize size of the output image.
@param flags combination of interpolation methods(see cv.InterpolationFlags) and the optional flag WARP_INVERSE_MAP that means that M is the inverse transformation ( 𝚍𝚜𝚝→𝚜𝚛𝚌 )
@param borderMode pixel extrapolation method (see cv.BorderTypes); when borderMode = BORDER_TRANSPARENT, it means that the pixels in the destination image corresponding to the "outliers" in the source image are not modified by the function.
@param borderValue value used in case of a constant border; by default, it is 0.
rows.
Try it
------
\htmlonly
<iframe src="../../js_geometric_transformations_warpAffine.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### Rotation
Rotation of an image for an angle \f$\theta\f$ is achieved by the transformation matrix of the form
\f[M = \begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{bmatrix}\f]
But OpenCV provides scaled rotation with adjustable center of rotation so that you can rotate at any
location you prefer. Modified transformation matrix is given by
\f[\begin{bmatrix} \alpha & \beta & (1- \alpha ) \cdot center.x - \beta \cdot center.y \\ - \beta & \alpha & \beta \cdot center.x + (1- \alpha ) \cdot center.y \end{bmatrix}\f]
where:
\f[\begin{array}{l} \alpha = scale \cdot \cos \theta , \\ \beta = scale \cdot \sin \theta \end{array}\f]
We use the function: **cv.getRotationMatrix2D (center, angle, scale)**
@param center center of the rotation in the source image.
@param angle rotation angle in degrees. Positive values mean counter-clockwise rotation (the coordinate origin is assumed to be the top-left corner).
@param scale isotropic scale factor.
Try it
------
\htmlonly
<iframe src="../../js_geometric_transformations_rotateWarpAffine.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### Affine Transformation
In affine transformation, all parallel lines in the original image will still be parallel in the
output image. To find the transformation matrix, we need three points from input image and their
corresponding locations in output image. Then **cv.getAffineTransform** will create a 2x3 matrix
which is to be passed to **cv.warpAffine**.
We use the function: **cv.getAffineTransform (src, dst)**
@param src three points([3, 1] size and cv.CV_32FC2 type) from input imag.
@param dst three corresponding points([3, 1] size and cv.CV_32FC2 type) in output image.
Try it
------
\htmlonly
<iframe src="../../js_geometric_transformations_getAffineTransform.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### Perspective Transformation
For perspective transformation, you need a 3x3 transformation matrix. Straight lines will remain straight even after the transformation. To find this transformation matrix, you need 4 points on the input image and corresponding points on the output image. Among these 4 points, 3 of them should not be collinear. Then transformation matrix can be found by the function **cv.getPerspectiveTransform**. Then apply **cv.warpPerspective** with this 3x3 transformation matrix.
We use the functions: **cv.warpPerspective (src, dst, M, dsize, flags = cv.INTER_LINEAR, borderMode = cv.BORDER_CONSTANT, borderValue = new cv.Scalar())**
@param src input image.
@param dst output image that has the size dsize and the same type as src.
@param Mat 3 × 3 transformation matrix(cv.CV_64FC1 type).
@param dsize size of the output image.
@param flags combination of interpolation methods (cv.INTER_LINEAR or cv.INTER_NEAREST) and the optional flag WARP_INVERSE_MAP, that sets M as the inverse transformation (𝚍𝚜𝚝→𝚜𝚛𝚌).
@param borderMode pixel extrapolation method (cv.BORDER_CONSTANT or cv.BORDER_REPLICATE).
@param borderValue value used in case of a constant border; by default, it is 0.
**cv.getPerspectiveTransform (src, dst)**
@param src coordinates of quadrangle vertices in the source image.
@param dst coordinates of the corresponding quadrangle vertices in the destination image.
Try it
------
\htmlonly
<iframe src="../../js_geometric_transformations_warpPerspective.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,76 @@
Foreground Extraction using GrabCut Algorithm {#tutorial_js_grabcut}
=========================================================
Goal
----
- We will learn GrabCut algorithm to extract foreground in images
Theory
------
GrabCut algorithm was designed by Carsten Rother, Vladimir Kolmogorov & Andrew Blake from Microsoft
Research Cambridge, UK. in their paper, ["GrabCut": interactive foreground extraction using iterated
graph cuts](http://dl.acm.org/citation.cfm?id=1015720) . An algorithm was needed for foreground
extraction with minimal user interaction, and the result was GrabCut.
How it works from user point of view ? Initially user draws a rectangle around the foreground region
(foreground region should be completely inside the rectangle). Then algorithm segments it
iteratively to get the best result. Done. But in some cases, the segmentation won't be fine, like,
it may have marked some foreground region as background and vice versa. In that case, user need to
do fine touch-ups. Just give some strokes on the images where some faulty results are there. Strokes
basically says *"Hey, this region should be foreground, you marked it background, correct it in next
iteration"* or its opposite for background. Then in the next iteration, you get better results.
What happens in background ?
- User inputs the rectangle. Everything outside this rectangle will be taken as sure background
(That is the reason it is mentioned before that your rectangle should include all the
objects). Everything inside rectangle is unknown. Similarly any user input specifying
foreground and background are considered as hard-labelling which means they won't change in
the process.
- Computer does an initial labelling depending on the data we gave. It labels the foreground and
background pixels (or it hard-labels)
- Now a Gaussian Mixture Model(GMM) is used to model the foreground and background.
- Depending on the data we gave, GMM learns and create new pixel distribution. That is, the
unknown pixels are labelled either probable foreground or probable background depending on its
relation with the other hard-labelled pixels in terms of color statistics (It is just like
clustering).
- A graph is built from this pixel distribution. Nodes in the graphs are pixels. Additional two
nodes are added, **Source node** and **Sink node**. Every foreground pixel is connected to
Source node and every background pixel is connected to Sink node.
- The weights of edges connecting pixels to source node/end node are defined by the probability
of a pixel being foreground/background. The weights between the pixels are defined by the edge
information or pixel similarity. If there is a large difference in pixel color, the edge
between them will get a low weight.
- Then a mincut algorithm is used to segment the graph. It cuts the graph into two separating
source node and sink node with minimum cost function. The cost function is the sum of all
weights of the edges that are cut. After the cut, all the pixels connected to Source node
become foreground and those connected to Sink node become background.
- The process is continued until the classification converges.
It is illustrated in below image (Image Courtesy: <http://www.cs.ru.ac.za/research/g02m1682/>)
![image](images/grabcut_scheme.jpg)
Demo
----
We use the function: **cv.grabCut (image, mask, rect, bgdModel, fgdModel, iterCount, mode = cv.GC_EVAL)**
@param image input 8-bit 3-channel image.
@param mask input/output 8-bit single-channel mask. The mask is initialized by the function when mode is set to GC_INIT_WITH_RECT. Its elements may have one of the cv.grabCutClasses.
@param rect ROI containing a segmented object. The pixels outside of the ROI are marked as "obvious background". The parameter is only used when mode==GC_INIT_WITH_RECT.
@param bgdModel temporary array for the background model. Do not modify it while you are processing the same image.
@param fgdModel temporary arrays for the foreground model. Do not modify it while you are processing the same image.
@param iterCount number of iterations the algorithm should make before returning the result. Note that the result can be refined with further calls with mode==GC_INIT_WITH_MASK or mode==GC_EVAL .
@param mode operation mode that could be one of the cv::GrabCutModes
Try it
------
\htmlonly
<iframe src="../../js_grabcut_grabCut.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,100 @@
Image Gradients {#tutorial_js_gradients}
===============
Goal
----
- Find Image gradients, edges etc
- We will learn following functions : **cv.Sobel()**, **cv.Scharr()**, **cv.Laplacian()** etc
Theory
------
OpenCV provides three types of gradient filters or High-pass filters, Sobel, Scharr and Laplacian.
We will see each one of them.
### 1. Sobel and Scharr Derivatives
Sobel operators is a joint Gausssian smoothing plus differentiation operation, so it is more
resistant to noise. You can specify the direction of derivatives to be taken, vertical or horizontal
(by the arguments, yorder and xorder respectively). You can also specify the size of kernel by the
argument ksize. If ksize = -1, a 3x3 Scharr filter is used which gives better results than 3x3 Sobel
filter. Please see the docs for kernels used.
We use the functions: **cv.Sobel (src, dst, ddepth, dx, dy, ksize = 3, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image of the same size and the same number of channels as src.
@param ddepth output image depth(see cv.combinations); in the case of 8-bit input images it will result in truncated derivatives.
@param dx order of the derivative x.
@param dy order of the derivative y.
@param ksize size of the extended Sobel kernel; it must be 1, 3, 5, or 7.
@param scale optional scale factor for the computed derivative values.
@param delta optional delta value that is added to the results prior to storing them in dst.
@param borderType pixel extrapolation method(see cv.BorderTypes).
**cv.Scharr (src, dst, ddepth, dx, dy, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image of the same size and the same number of channels as src.
@param ddepth output image depth(see cv.combinations).
@param dx order of the derivative x.
@param dy order of the derivative y.
@param scale optional scale factor for the computed derivative values.
@param delta optional delta value that is added to the results prior to storing them in dst.
@param borderType pixel extrapolation method(see cv.BorderTypes).
Try it
------
\htmlonly
<iframe src="../../js_gradients_Sobel.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 2. Laplacian Derivatives
It calculates the Laplacian of the image given by the relation,
\f$\Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2}\f$ where
each derivative is found using Sobel derivatives. If ksize = 1, then following kernel is used for
filtering:
\f[kernel = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix}\f]
We use the function: **cv.Laplacian (src, dst, ddepth, ksize = 1, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image of the same size and the same number of channels as src.
@param ddepth output image depth.
@param ksize aperture size used to compute the second-derivative filters.
@param scale optional scale factor for the computed Laplacian values.
@param delta optional delta value that is added to the results prior to storing them in dst.
@param borderType pixel extrapolation method(see cv.BorderTypes).
Try it
------
\htmlonly
<iframe src="../../js_gradients_Laplacian.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
One Important Matter!
---------------------
In our last example, output datatype is cv.CV_8U. But there is a slight problem with
that. Black-to-White transition is taken as Positive slope (it has a positive value) while
White-to-Black transition is taken as a Negative slope (It has negative value). So when you convert
data to cv.CV_8U, all negative slopes are made zero. In simple words, you miss that edge.
If you want to detect both edges, better option is to keep the output datatype to some higher forms,
like cv.CV_16S, cv.CV_64F etc, take its absolute value and then convert back to cv.CV_8U.
Below code demonstrates this procedure for a horizontal Sobel filter and difference in results.
Try it
------
\htmlonly
<iframe src="../../js_gradients_absSobel.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,59 @@
Histogram - 3 : Histogram Backprojection {#tutorial_js_histogram_backprojection}
========================================
Goal
----
- We will learn about histogram backprojection.
Theory
------
It was proposed by **Michael J. Swain , Dana H. Ballard** in their paper **Indexing via color
histograms**.
**What is it actually in simple words?** It is used for image segmentation or finding objects of
interest in an image. In simple words, it creates an image of the same size (but single channel) as
that of our input image, where each pixel corresponds to the probability of that pixel belonging to
our object. In more simpler worlds, the output image will have our object of interest in more white
compared to remaining part. Well, that is an intuitive explanation. (I can't make it more simpler).
Histogram Backprojection is used with camshift algorithm etc.
**How do we do it ?** We create a histogram of an image containing our object of interest (in our
case, the ground, leaving player and other things). The object should fill the image as far as
possible for better results. And a color histogram is preferred over grayscale histogram, because
color of the object is a better way to define the object than its grayscale intensity. We then
"back-project" this histogram over our test image where we need to find the object, ie in other
words, we calculate the probability of every pixel belonging to the ground and show it. The
resulting output on proper thresholding gives us the ground alone.
Backprojection in OpenCV
------------------------
We use the functions: **cv.calcBackProject (images, channels, hist, dst, ranges, scale)**
@param images source arrays. They all should have the same depth, cv.CV_8U, cv.CV_16U or cv.CV_32F , and the same size. Each of them can have an arbitrary number of channels.
@param channels the list of channels used to compute the back projection. The number of channels must match the histogram dimensionality.
@param hist input histogram that can be dense or sparse.
@param dst destination back projection array that is a single-channel array of the same size and depth as images[0].
@param ranges array of arrays of the histogram bin boundaries in each dimension(see cv.calcHist).
@param scale optional scale factor for the output back projection.
**cv.normalize (src, dst, alpha = 1, beta = 0, norm_type = cv.NORM_L2, dtype = -1, mask = new cv.Mat())**
@param src input array.
@param dst output array of the same size as src .
@param alpha norm value to normalize to or the lower range boundary in case of the range normalization.
@param beta upper range boundary in case of the range normalization; it is not used for the norm normalization.
@param norm_type normalization type (see cv.NormTypes).
@param dtype when negative, the output array has the same type as src; otherwise, it has the same number of channels as src and the depth = CV_MAT_DEPTH(dtype).
@param mask optional operation mask.
Try it
------
\htmlonly
<iframe src="../../js_histogram_backprojection_calcBackProject.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,51 @@
Histograms - 1 : Find, Plot, Analyze !!! {#tutorial_js_histogram_begins}
========================================
Goal
----
- Find histograms
- Plot histograms
- You will learn the function: **cv.calcHist()**.
Theory
------
So what is histogram ? You can consider histogram as a graph or plot, which gives you an overall
idea about the intensity distribution of an image. It is a plot with pixel values (ranging from 0 to
255, not always) in X-axis and corresponding number of pixels in the image on Y-axis.
It is just another way of understanding the image. By looking at the histogram of an image, you get
intuition about contrast, brightness, intensity distribution etc of that image. Almost all image
processing tools today, provides features on histogram. Below is an image from [Cambridge in Color
website](http://www.cambridgeincolour.com/tutorials/histograms1.htm), and I recommend you to visit
the site for more details.
![image](histogram_sample.jpg)
You can see the image and its histogram. (Remember, this histogram is drawn for grayscale image, not
color image). Left region of histogram shows the amount of darker pixels in image and right region
shows the amount of brighter pixels. From the histogram, you can see dark region is more than
brighter region, and amount of midtones (pixel values in mid-range, say around 127) are very less.
Find Histogram
--------------
We use the function: **cv.calcHist (image, channels, mask, hist, histSize, ranges, accumulate = false)**
@param image source arrays. They all should have the same depth, cv.CV_8U, cv.CV_16U or cv.CV_32F , and the same size. Each of them can have an arbitrary number of channels.
@param channels list of the dims channels used to compute the histogram.
@param mask optional mask. If the matrix is not empty, it must be an 8-bit array of the same size as images[i] . The non-zero mask elements mark the array elements counted in the histogram.
@param hist output histogram(cv.CV_32F type), which is a dense or sparse dims -dimensional array.
@param histSize array of histogram sizes in each dimension.
@param ranges array of the dims arrays of the histogram bin boundaries in each dimension.
@param accumulate accumulation flag. If it is set, the histogram is not cleared in the beginning when it is allocated. This feature enables you to compute a single histogram from several sets of arrays, or to update the histogram in time.
Try it
------
\htmlonly
<iframe src="../../js_histogram_begins_calcHist.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,63 @@
Histograms - 2: Histogram Equalization {#tutorial_js_histogram_equalization}
======================================
Goal
----
- We will learn the concepts of histogram equalization and use it to improve the contrast of our
images.
Theory
------
Consider an image whose pixel values are confined to some specific range of values only. For eg,
brighter image will have all pixels confined to high values. But a good image will have pixels from
all regions of the image. So you need to stretch this histogram to either ends (as given in below
image, from wikipedia) and that is what Histogram Equalization does (in simple words). This normally
improves the contrast of the image.
![image](images/histogram_equalization.png)
I would recommend you to read the wikipedia page on [Histogram
Equalization](http://en.wikipedia.org/wiki/Histogram_equalization) for more details about it. It has
a very good explanation with worked out examples, so that you would understand almost everything
after reading that.
Histograms Equalization in OpenCV
---------------------------------
We use the function: **cv.equalizeHist (src, dst)**
@param src source 8-bit single channel image.
@param dst destination image of the same size and type as src.
Try it
------
\htmlonly
<iframe src="../../js_histogram_equalization_equalizeHist.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
CLAHE (Contrast Limited Adaptive Histogram Equalization)
--------------------------------------------------------
In **adaptive histogram equalization**, image is divided into small blocks called "tiles" (tileSize is 8x8 by default in OpenCV). Then each of these blocks are histogram equalized as usual. So in a small area, histogram would confine to a small region
(unless there is noise). If noise is there, it will be amplified. To avoid this, **contrast limiting** is applied. If any histogram bin is above the specified contrast limit (by default 40 in OpenCV), those pixels are clipped and distributed uniformly to other bins before applying histogram equalization. After equalization, to remove artifacts in tile borders, bilinear interpolation is applied.
We use the class: **cv.CLAHE (clipLimit = 40, tileGridSize = new cv.Size(8, 8))**
@param clipLimit threshold for contrast limiting.
@param tileGridSize size of grid for histogram equalization. Input image will be divided into equally sized rectangular tiles. tileGridSize defines the number of tiles in row and column.
@note Don't forget to delete CLAHE!
Try it
------
\htmlonly
<iframe src="../../js_histogram_equalization_createCLAHE.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,14 @@
Histograms in OpenCV.js {#tutorial_js_table_of_contents_histograms}
====================
- @subpage tutorial_js_histogram_begins
Learn the basics of histograms
- @subpage tutorial_js_histogram_equalization
Learn to Equalize Histograms to get better contrast for images
- @subpage tutorial_js_histogram_backprojection
Learn histogram backprojection to segment colored objects

View File

@ -0,0 +1,38 @@
Hough Circle Transform {#tutorial_js_houghcircles}
======================
Goal
----
- We will learn to use Hough Transform to find circles in an image.
- We will learn these functions: **cv.HoughCircles()**
Theory
------
A circle is represented mathematically as \f$(x-x_{center})^2 + (y - y_{center})^2 = r^2\f$ where
\f$(x_{center},y_{center})\f$ is the center of the circle, and \f$r\f$ is the radius of the circle. From
equation, we can see we have 3 parameters, so we need a 3D accumulator for hough transform, which
would be highly ineffective. So OpenCV uses more trickier method, **Hough Gradient Method** which
uses the gradient information of edges.
We use the function: **cv.HoughCircles (image, circles, method, dp, minDist, param1 = 100, param2 = 100, minRadius = 0, maxRadius = 0)**
@param image 8-bit, single-channel, grayscale input image.
@param circles output vector of found circles(cv.CV_32FC3 type). Each vector is encoded as a 3-element floating-point vector (x,y,radius) .
@param method detection method(see cv.HoughModes). Currently, the only implemented method is HOUGH_GRADIENT
@param dp inverse ratio of the accumulator resolution to the image resolution. For example, if dp = 1 , the accumulator has the same resolution as the input image. If dp = 2 , the accumulator has half as big width and height.
@param minDist minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.
@param param1 first method-specific parameter. In case of HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller).
@param param2 second method-specific parameter. In case of HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first.
@param minRadius minimum circle radius.
@param maxRadius maximum circle radius.
Try it
------
\htmlonly
<iframe src="../../js_houghcircles_HoughCirclesP.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,119 @@
Hough Line Transform {#tutorial_js_houghlines}
====================
Goal
----
- We will understand the concept of the Hough Transform.
- We will learn how to use it to detect lines in an image.
- We will learn the following functions: **cv.HoughLines()**, **cv.HoughLinesP()**
Theory
------
The Hough Transform is a popular technique to detect any shape, if you can represent that shape in a
mathematical form. It can detect the shape even if it is broken or distorted a little bit. We will
see how it works for a line.
A line can be represented as \f$y = mx+c\f$ or in a parametric form, as
\f$\rho = x \cos \theta + y \sin \theta\f$ where \f$\rho\f$ is the perpendicular distance from the origin to the
line, and \f$\theta\f$ is the angle formed by this perpendicular line and the horizontal axis measured in
counter-clockwise (That direction varies on how you represent the coordinate system. This
representation is used in OpenCV). Check the image below:
![image](images/houghlines1.svg)
So if the line is passing below the origin, it will have a positive rho and an angle less than 180. If it
is going above the origin, instead of taking an angle greater than 180, the angle is taken less than 180,
and rho is taken negative. Any vertical line will have 0 degree and horizontal lines will have 90
degree.
Now let's see how the Hough Transform works for lines. Any line can be represented in these two terms,
\f$(\rho, \theta)\f$. So first it creates a 2D array or accumulator (to hold the values of the two parameters)
and it is set to 0 initially. Let rows denote the \f$\rho\f$ and columns denote the \f$\theta\f$. Size of
array depends on the accuracy you need. Suppose you want the accuracy of angles to be 1 degree, you will
need 180 columns. For \f$\rho\f$, the maximum distance possible is the diagonal length of the image. So
taking one pixel accuracy, the number of rows can be the diagonal length of the image.
Consider a 100x100 image with a horizontal line at the middle. Take the first point of the line. You
know its (x,y) values. Now in the line equation, put the values \f$\theta = 0,1,2,....,180\f$ and check
the \f$\rho\f$ you get. For every \f$(\rho, \theta)\f$ pair, you increment value by one in our accumulator
in its corresponding \f$(\rho, \theta)\f$ cells. So now in accumulator, the cell (50,90) = 1 along with
some other cells.
Now take the second point on the line. Do the same as above. Increment the values in the cells
corresponding to \f$(\rho, \theta)\f$ you got. This time, the cell (50,90) = 2. What you actually
do is voting the \f$(\rho, \theta)\f$ values. You continue this process for every point on the line. At
each point, the cell (50,90) will be incremented or voted up, while other cells may or may not be
voted up. This way, at the end, the cell (50,90) will have maximum votes. So if you search the
accumulator for maximum votes, you get the value (50,90) which says, there is a line in this image
at a distance 50 from the origin and at angle 90 degrees. It is well shown in the below animation (Image
Courtesy: [Amos Storkey](http://homepages.inf.ed.ac.uk/amos/hough.html) )
![](houghlinesdemo.gif)
This is how hough transform works for lines. It is simple. Below is an image which shows the accumulator. Bright spots at some locations
denote they are the parameters of possible lines in the image. (Image courtesy: [Wikipedia](http://en.wikipedia.org/wiki/Hough_transform) )
![](houghlines2.jpg)
Hough Transform in OpenCV
=========================
Everything explained above is encapsulated in the OpenCV function, **cv.HoughLines()**. It simply returns an array of (\f$(\rho, \theta)\f$ values. \f$\rho\f$ is measured in pixels and \f$\theta\f$ is measured in radians. First parameter,
Input image should be a binary image, so apply threshold or use canny edge detection before
applying hough transform.
We use the function: **cv.HoughLines (image, lines, rho, theta, threshold, srn = 0, stn = 0, min_theta = 0, max_theta = Math.PI)**
@param image 8-bit, single-channel binary source image. The image may be modified by the function.
@param lines output vector of lines(cv.32FC2 type). Each line is represented by a two-element vector (ρ,θ) . ρ is the distance from the coordinate origin (0,0). θ is the line rotation angle in radians.
@param rho distance resolution of the accumulator in pixels.
@param theta angle resolution of the accumulator in radians.
@param threshold accumulator threshold parameter. Only those lines are returned that get enough votes
@param srn for the multi-scale Hough transform, it is a divisor for the distance resolution rho . The coarse accumulator distance resolution is rho and the accurate accumulator resolution is rho/srn . If both srn=0 and stn=0 , the classical Hough transform is used. Otherwise, both these parameters should be positive.
@param stn for the multi-scale Hough transform, it is a divisor for the distance resolution theta.
@param min_theta for standard and multi-scale Hough transform, minimum angle to check for lines. Must fall between 0 and max_theta.
@param max_theta for standard and multi-scale Hough transform, maximum angle to check for lines. Must fall between min_theta and CV_PI.
Try it
------
\htmlonly
<iframe src="../../js_houghlines_HoughLines.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Probabilistic Hough Transform
-----------------------------
In the hough transform, you can see that even for a line with two arguments, it takes a lot of
computation. Probabilistic Hough Transform is an optimization of the Hough Transform we saw. It doesn't
take all the points into consideration. Instead, it takes only a random subset of points which is
sufficient for line detection. Just we have to decrease the threshold. See image below which compares
Hough Transform and Probabilistic Hough Transform in Hough space. (Image Courtesy :
[Franck Bettinger's home page](http://phdfb1.free.fr/robot/mscthesis/node14.html) )
![image](images/houghlines4.png)
OpenCV implementation is based on Robust Detection of Lines Using the Progressive Probabilistic
Hough Transform by Matas, J. and Galambos, C. and Kittler, J.V. @cite Matas00.
We use the function: **cv.HoughLinesP (image, lines, rho, theta, threshold, minLineLength = 0, maxLineGap = 0)**
@param image 8-bit, single-channel binary source image. The image may be modified by the function.
@param lines output vector of lines(cv.32SC4 type). Each line is represented by a 4-element vector (x1,y1,x2,y2) ,where (x1,y1) and (x2,y2) are the ending points of each detected line segment.
@param rho distance resolution of the accumulator in pixels.
@param theta angle resolution of the accumulator in radians.
@param threshold accumulator threshold parameter. Only those lines are returned that get enough votes
@param minLineLength minimum line length. Line segments shorter than that are rejected.
@param maxLineGap maximum allowed gap between points on the same line to link them.
Try it
------
\htmlonly
<iframe src="../../js_houghlines_HoughLinesP.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,14 @@
Image Processing for Video Capture {#tutorial_js_imgproc_camera}
==================================
Goal
----
- learn image processing for video capture.
\htmlonly
<iframe src="../../js_imgproc_camera.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,16 @@
Intelligent Scissors Demo {#tutorial_js_intelligent_scissors}
=========================
Goal
----
- Here you can check how to use IntelligentScissors tool for image segmentation task.
- Available methods and parameters: @ref cv::segmentation::IntelligentScissorsMB
@note The feature is integrated into [CVAT](https://github.com/openvinotoolkit/cvat) annotation tool and you can try it online on https://cvat.org
\htmlonly
<iframe src="../../js_intelligent_scissors.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,177 @@
Morphological Transformations {#tutorial_js_morphological_ops}
=============================
Goal
----
- We will learn different morphological operations like Erosion, Dilation, Opening, Closing
etc.
- We will learn different functions like : **cv.erode()**, **cv.dilate()**,
**cv.morphologyEx()** etc.
Theory
------
Morphological transformations are some simple operations based on the image shape. It is normally
performed on binary images. It needs two inputs, one is our original image, second one is called
**structuring element** or **kernel** which decides the nature of operation. Two basic morphological
operators are Erosion and Dilation. Then its variant forms like Opening, Closing, Gradient etc also
comes into play. We will see them one-by-one with help of following image:
![image](shape.jpg)
### 1. Erosion
The basic idea of erosion is just like soil erosion only, it erodes away the boundaries of
foreground object (Always try to keep foreground in white). So what it does? The kernel slides
through the image (as in 2D convolution). A pixel in the original image (either 1 or 0) will be
considered 1 only if all the pixels under the kernel is 1, otherwise it is eroded (made to zero).
So what happends is that, all the pixels near boundary will be discarded depending upon the size of
kernel. So the thickness or size of the foreground object decreases or simply white region decreases
in the image. It is useful for removing small white noises (as we have seen in colorspace chapter),
detach two connected objects etc.
We use the function: **cv.erode (src, dst, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())**
@param src input image; the number of channels can be arbitrary, but the depth should be one of cv.CV_8U, cv.CV_16U, cv.CV_16S, cv.CV_32F or cv.CV_64F.
@param dst output image of the same size and type as src.
@param kernel structuring element used for erosion.
@param anchor position of the anchor within the element; default value new cv.Point(-1, -1) means that the anchor is at the element center.
@param iterations number of times erosion is applied.
@param borderType pixel extrapolation method(see cv.BorderTypes).
@param borderValue border value in case of a constant border
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_erode.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 2. Dilation
It is just opposite of erosion. Here, a pixel element is '1' if atleast one pixel under the kernel
is '1'. So it increases the white region in the image or size of foreground object increases.
Normally, in cases like noise removal, erosion is followed by dilation. Because, erosion removes
white noises, but it also shrinks our object. So we dilate it. Since noise is gone, they won't come
back, but our object area increases. It is also useful in joining broken parts of an object.
We use the function: **cv.dilate (src, dst, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())**
@param src input image; the number of channels can be arbitrary, but the depth should be one of cv.CV_8U, cv.CV_16U, cv.CV_16S, cv.CV_32F or cv.CV_64F.
@param dst output image of the same size and type as src.
@param kernel structuring element used for dilation.
@param anchor position of the anchor within the element; default value new cv.Point(-1, -1) means that the anchor is at the element center.
@param iterations number of times dilation is applied.
@param borderType pixel extrapolation method(see cv.BorderTypes).
@param borderValue border value in case of a constant border
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_dilate.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 3. Opening
Opening is just another name of **erosion followed by dilation**. It is useful in removing noise.
We use the function: **cv.morphologyEx (src, dst, op, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())**
@param src source image. The number of channels can be arbitrary. The depth should be one of cv.CV_8U, cv.CV_16U, cv.CV_16S, cv.CV_32F or cv.CV_64F
@param dst destination image of the same size and type as source image.
@param op type of a morphological operation, (see cv.MorphTypes).
@param kernel structuring element. It can be created using cv.getStructuringElement.
@param anchor anchor position with the kernel. Negative values mean that the anchor is at the kernel center.
@param iterations number of times dilation is applied.
@param borderType pixel extrapolation method(see cv.BorderTypes).
@param borderValue border value in case of a constant border. The default value has a special meaning.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_opening.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 4. Closing
Closing is reverse of Opening, **Dilation followed by Erosion**. It is useful in closing small holes
inside the foreground objects, or small black points on the object.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_closing.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 5. Morphological Gradient
It is the difference between dilation and erosion of an image.
The result will look like the outline of the object.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_gradient.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 6. Top Hat
It is the difference between input image and Opening of the image.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_topHat.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
### 7. Black Hat
It is the difference between the closing of the input image and input image.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_blackHat.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Structuring Element
-------------------
We manually created a structuring elements in the previous examples with help of cv.Mat.ones. It is
rectangular shape. But in some cases, you may need elliptical/circular shaped kernels. So for this
purpose, OpenCV has a function, **cv.getStructuringElement()**. You just pass the shape and size of
the kernel, you get the desired kernel.
We use the function: **cv.getStructuringElement (shape, ksize, anchor = new cv.Point(-1, -1))**
@param shape element shape that could be one of cv.MorphShapes
@param ksize size of the structuring element.
@param anchor anchor position within the element. The default value [1,1] means that the anchor is at the center. Note that only the shape of a cross-shaped element depends on the anchor position. In other cases the anchor just regulates how much the result of the morphological operation is shifted.
Try it
------
\htmlonly
<iframe src="../../js_morphological_ops_getStructuringElement.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,70 @@
Image Pyramids {#tutorial_js_pyramids}
==============
Goal
----
- We will learn about Image Pyramids
- We will learn these functions: **cv.pyrUp()**, **cv.pyrDown()**
Theory
------
Normally, we used to work with an image of constant size. But on some occasions, we need to work
with (the same) images in different resolution. For example, while searching for something in
an image, like face, we are not sure at what size the object will be present in said image. In that
case, we will need to create a set of the same image with different resolutions and search for object
in all of them. These set of images with different resolutions are called **Image Pyramids** (because
when they are kept in a stack with the highest resolution image at the bottom and the lowest resolution
image at top, it looks like a pyramid).
There are two kinds of Image Pyramids. 1) **Gaussian Pyramid** and 2) **Laplacian Pyramids**
Higher level (Low resolution) in a Gaussian Pyramid is formed by removing consecutive rows and
columns in Lower level (higher resolution) image. Then each pixel in higher level is formed by the
contribution from 5 pixels in underlying level with gaussian weights. By doing so, a \f$M \times N\f$
image becomes \f$M/2 \times N/2\f$ image. So area reduces to one-fourth of original area. It is called
an Octave. The same pattern continues as we go upper in pyramid (ie, resolution decreases).
Similarly while expanding, area becomes 4 times in each level. We can find Gaussian pyramids using
**cv.pyrDown()** and **cv.pyrUp()** functions.
Laplacian Pyramids are formed from the Gaussian Pyramids. There is no exclusive function for that.
Laplacian pyramid images are like edge images only. Most of its elements are zeros. They are used in
image compression. A level in Laplacian Pyramid is formed by the difference between that level in
Gaussian Pyramid and expanded version of its upper level in Gaussian Pyramid.
Downsample
------
We use the function: **cv.pyrDown (src, dst, dstsize = new cv.Size(0, 0), borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image; it has the specified size and the same type as src.
@param dstsize size of the output image.
@param borderType pixel extrapolation method(see cv.BorderTypes, cv.BORDER_CONSTANT isn't supported).
Try it
------
\htmlonly
<iframe src="../../js_pyramids_pyrDown.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Upsample
------
We use the function: **cv.pyrUp (src, dst, dstsize = new cv.Size(0, 0), borderType = cv.BORDER_DEFAULT)**
@param src input image.
@param dst output image; it has the specified size and the same type as src.
@param dstsize size of the output image.
@param borderType pixel extrapolation method(see cv.BorderTypes, only cv.BORDER_DEFAULT is supported).
Try it
------
\htmlonly
<iframe src="../../js_pyramids_pyrUp.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,83 @@
Image Processing {#tutorial_js_table_of_contents_imgproc}
==========================
- @subpage tutorial_js_colorspaces
Learn how to change images between different color spaces.
- @subpage tutorial_js_geometric_transformations
Learn how to apply different geometric transformations to images like rotation, translation etc.
- @subpage tutorial_js_thresholding
Learn
how to convert images to binary images using global thresholding, Adaptive thresholding, Otsu's
binarization etc.
- @subpage tutorial_js_filtering
Learn
how to blur the images, filter the images with custom kernels etc.
- @subpage tutorial_js_morphological_ops
Learn about morphological transformations like Erosion, Dilation, Opening, Closing etc.
- @subpage tutorial_js_gradients
Learn
how to find image gradients, edges etc.
- @subpage tutorial_js_canny
Learn
how to find edges with Canny Edge Detection.
- @subpage tutorial_js_pyramids
Learn about image pyramids and how to use them for image blending.
- @subpage tutorial_js_table_of_contents_contours
Learn
about Contours in OpenCV.js.
- @subpage tutorial_js_table_of_contents_histograms
Learn
about histograms in OpenCV.js.
- @subpage tutorial_js_table_of_contents_transforms
Learn
different Image Transforms in OpenCV.js like Fourier Transform, Cosine Transform etc.
- @subpage tutorial_js_template_matching
Learn
how to search for an object in an image using Template Matching.
- @subpage tutorial_js_houghlines
Learn how to detect lines in an image.
- @subpage tutorial_js_houghcircles
Learn how to detect circles in an image.
- @subpage tutorial_js_watershed
Learn how to segment images with watershed segmentation.
- @subpage tutorial_js_grabcut
Learn how to extract foreground with GrabCut algorithm.
- @subpage tutorial_js_imgproc_camera
Learn image processing for video capture.
- @subpage tutorial_js_intelligent_scissors
Learn how to use IntelligentScissors tool for image segmentation task.

View File

@ -0,0 +1,45 @@
Template Matching {#tutorial_js_template_matching}
=================
Goals
-----
- To find objects in an image using Template Matching
- You will learn these functions : **cv.matchTemplate()**, **cv.minMaxLoc()**
Theory
------
Template Matching is a method for searching and finding the location of a template image in a larger
image. OpenCV comes with a function **cv.matchTemplate()** for this purpose. It simply slides the
template image over the input image (as in 2D convolution) and compares the template and patch of
input image under the template image. Several comparison methods are implemented in OpenCV. (You can
check docs for more details). It returns a grayscale image, where each pixel denotes how much does
the neighbourhood of that pixel match with template.
If input image is of size (WxH) and template image is of size (wxh), output image will have a size
of (W-w+1, H-h+1). Once you got the result, you can use **cv.minMaxLoc()** function to find where
is the maximum/minimum value. Take it as the top-left corner of rectangle and take (w,h) as width
and height of the rectangle. That rectangle is your region of template.
@note If you are using cv.TM_SQDIFF as comparison method, minimum value gives the best match.
Template Matching in OpenCV
---------------------------
We use the function: **cv.matchTemplate (image, templ, result, method, mask = new cv.Mat())**
@param image image where the search is running. It must be 8-bit or 32-bit floating-point.
@param templ searched template. It must be not greater than the source image and have the same data type.
@param result map of comparison results. It must be single-channel 32-bit floating-point.
@param method parameter specifying the comparison method(see cv.TemplateMatchModes).
@param mask mask of searched template. It must have the same datatype and size with templ. It is not set by default.
Try it
------
\htmlonly
<iframe src="../../js_template_matching_matchTemplate.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,74 @@
Image Thresholding {#tutorial_js_thresholding}
==================
Goal
----
- In this tutorial, you will learn Simple thresholding, Adaptive thresholding, Otsu's thresholding
etc.
- You will learn these functions : **cv.threshold**, **cv.adaptiveThreshold** etc.
Simple Thresholding
-------------------
Here, the matter is straight forward. If pixel value is greater than a threshold value, it is
assigned one value (may be white), else it is assigned another value (may be black).
We use the function: **cv.threshold (src, dst, thresh, maxval, type)**
@param src input array.
@param dst output array of the same size and type and the same number of channels as src.
@param thresh threshold value.
@param maxval maximum value to use with the cv.THRESH_BINARY and cv.THRESH_BINARY_INV thresholding types.
@param type thresholding type(see cv.ThresholdTypes).
**thresholding type** - OpenCV provides different styles of thresholding and it is decided
by the fourth parameter of the function. Different types are:
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_OTSU
- cv.THRESH_TRIANGLE
@note Input image should be single channel only in case of cv.THRESH_OTSU or cv.THRESH_TRIANGLE flags
Try it
------
\htmlonly
<iframe src="../../js_thresholding_threshold.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Adaptive Thresholding
---------------------
In the previous section, we used a global value as threshold value. But it may not be good in all
the conditions where image has different lighting conditions in different areas. In that case, we go
for adaptive thresholding. In this, the algorithm calculate the threshold for a small regions of the
image. So we get different thresholds for different regions of the same image and it gives us better
results for images with varying illumination.
We use the function: **cv.adaptiveThreshold (src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C)**
@param src source 8-bit single-channel image.
@param dst destination image of the same size and the same type as src.
@param maxValue non-zero value assigned to the pixels for which the condition is satisfied
@param adaptiveMethod adaptive thresholding algorithm to use.
@param thresholdType thresholding type that must be either cv.THRESH_BINARY or cv.THRESH_BINARY_INV.
@param blockSize size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on.
@param C constant subtracted from the mean or weighted mean (see the details below). Normally, it is positive but may be zero or negative as well.
**adaptiveMethod** - It decides how thresholding value is calculated:
- cv.ADAPTIVE_THRESH_MEAN_C
- cv.ADAPTIVE_THRESH_GAUSSIAN_C
Try it
------
\htmlonly
<iframe src="../../js_thresholding_adaptiveThreshold.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,89 @@
Fourier Transform {#tutorial_js_fourier_transform}
=================
Goal
----
- To find the Fourier Transform of images using OpenCV
- Some applications of Fourier Transform
- We will learn following functions : **cv.dft()** etc
Theory
------
Fourier Transform is used to analyze the frequency characteristics of various filters. For images,
**2D Discrete Fourier Transform (DFT)** is used to find the frequency domain. A fast algorithm
called **Fast Fourier Transform (FFT)** is used for calculation of DFT. Details about these can be
found in any image processing or signal processing textbooks.
For a sinusoidal signal, \f$x(t) = A \sin(2 \pi ft)\f$, we can say \f$f\f$ is the frequency of signal, and
if its frequency domain is taken, we can see a spike at \f$f\f$. If signal is sampled to form a discrete
signal, we get the same frequency domain, but is periodic in the range \f$[- \pi, \pi]\f$ or \f$[0,2\pi]\f$
(or \f$[0,N]\f$ for N-point DFT). You can consider an image as a signal which is sampled in two
directions. So taking fourier transform in both X and Y directions gives you the frequency
representation of image.
More intuitively, for the sinusoidal signal, if the amplitude varies so fast in short time, you can
say it is a high frequency signal. If it varies slowly, it is a low frequency signal. You can extend
the same idea to images. Where does the amplitude varies drastically in images ? At the edge points,
or noises. So we can say, edges and noises are high frequency contents in an image. If there is no
much changes in amplitude, it is a low frequency component.
Performance of DFT calculation is better for some array size. It is fastest when array size is power
of two. The arrays whose size is a product of 2s, 3s, and 5s are also processed quite
efficiently. So if you are worried about the performance of your code, you can modify the size of
the array to any optimal size (by padding zeros) before finding DFT. OpenCV provides a function, **cv.getOptimalDFTSize()** for this.
Now we will see how to find the Fourier Transform.
Fourier Transform in OpenCV
---------------------------
Performance of DFT calculation is better for some array size. It is fastest when array size is power of two. The arrays whose size is a product of 2s, 3s, and 5s are also processed quite efficiently. So if you are worried about the performance of your code, you can modify the size of the array to any optimal size (by padding zeros). So how do we find this optimal size ? OpenCV provides a function, cv.getOptimalDFTSize() for this.
We use the functions: **cv.dft (src, dst, flags = 0, nonzeroRows = 0)**
@param src input array that could be real or complex.
@param dst output array whose size and type depends on the flags.
@param flags transformation flags, representing a combination of the cv.DftFlags
@param nonzeroRows when the parameter is not zero, the function assumes that only the first nonzeroRows rows of the input array (DFT_INVERSE is not set) or only the first nonzeroRows of the output array (DFT_INVERSE is set) contain non-zeros, thus, the function can handle the rest of the rows more efficiently and save some time; this technique is very useful for calculating array cross-correlation or convolution using DFT.
**cv.getOptimalDFTSize (vecsize)**
@param vecsize vector size.
**cv.copyMakeBorder (src, dst, top, bottom, left, right, borderType, value = new cv.Scalar())**
@param src input array that could be real or complex.
@param dst output array whose size and type depends on the flags.
@param top parameter specifying how many top pixels in each direction from the source image rectangle to extrapolate.
@param bottom parameter specifying how many bottom pixels in each direction from the source image rectangle to extrapolate.
@param left parameter specifying how many left pixels in each direction from the source image rectangle to extrapolate.
@param right parameter specifying how many right pixels in each direction from the source image rectangle to extrapolate.
@param borderType border type.
@param value border value if borderType == cv.BORDER_CONSTANT.
**cv.magnitude (x, y, magnitude)**
@param x floating-point array of x-coordinates of the vectors.
@param y floating-point array of y-coordinates of the vectors; it must have the same size as x.
@param magnitude output array of the same size and type as x.
**cv.split (m, mv)**
@param m input multi-channel array.
@param mv output vector of arrays; the arrays themselves are reallocated, if needed.
**cv.merge (mv, dst)**
@param mv input vector of matrices to be merged; all the matrices in mv must have the same size and the same depth.
@param dst output array of the same size and the same depth as mv[0]; The number of channels will be the total number of channels in the matrix array.
Try it
------
\htmlonly
<iframe src="../../js_fourier_transform_dft.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly

View File

@ -0,0 +1,5 @@
Image Transforms in OpenCV.js {#tutorial_js_table_of_contents_transforms}
==========================
- @subpage tutorial_js_fourier_transform
Learn to find the Fourier Transform of images

View File

@ -0,0 +1,144 @@
Image Segmentation with Watershed Algorithm {#tutorial_js_watershed}
===========================================
Goal
----
- We will learn how to use marker-based image segmentation using watershed algorithm
- We will learn: **cv.watershed()**
Theory
------
Any grayscale image can be viewed as a topographic surface where high intensity denotes peaks and
hills while low intensity denotes valleys. You start filling every isolated valleys (local minima)
with different colored water (labels). As the water rises, depending on the peaks (gradients)
nearby, water from different valleys, obviously with different colors will start to merge. To avoid
that, you build barriers in the locations where water merges. You continue the work of filling water
and building barriers until all the peaks are under water. Then the barriers you created gives you
the segmentation result. This is the "philosophy" behind the watershed. You can visit the [CMM
webpage on watershed](http://cmm.ensmp.fr/~beucher/wtshed.html) to understand it with the help of
some animations.
But this approach gives you oversegmented result due to noise or any other irregularities in the
image. So OpenCV implemented a marker-based watershed algorithm where you specify which are all
valley points are to be merged and which are not. It is an interactive image segmentation. What we
do is to give different labels for our object we know. Label the region which we are sure of being
the foreground or object with one color (or intensity), label the region which we are sure of being
background or non-object with another color and finally the region which we are not sure of
anything, label it with 0. That is our marker. Then apply watershed algorithm. Then our marker will
be updated with the labels we gave, and the boundaries of objects will have a value of -1.
Code
----
Below we will see an example on how to use the Distance Transform along with watershed to segment
mutually touching objects.
Consider the coins image below, the coins are touching each other. Even if you threshold it, it will
be touching each other.
We start with finding an approximate estimate of the coins. For that, we can use the Otsu's
binarization.
Try it
------
\htmlonly
<iframe src="../../js_watershed_threshold.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Now we need to remove any small white noises in the image. For that we can use morphological
opening. To remove any small holes in the object, we can use morphological closing. So, now we know
for sure that region near to center of objects are foreground and region much away from the object
are background. Only region we are not sure is the boundary region of coins.
So we need to extract the area which we are sure they are coins. Erosion removes the boundary
pixels. So whatever remaining, we can be sure it is coin. That would work if objects were not
touching each other. But since they are touching each other, another good option would be to find
the distance transform and apply a proper threshold. Next we need to find the area which we are sure
they are not coins. For that, we dilate the result. Dilation increases object boundary to
background. This way, we can make sure whatever region in background in result is really a
background, since boundary region is removed. See the image below.
Try it
------
\htmlonly
<iframe src="../../js_watershed_background.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
The remaining regions are those which we don't have any idea, whether it is coins or background.
Watershed algorithm should find it. These areas are normally around the boundaries of coins where
foreground and background meet (Or even two different coins meet). We call it border. It can be
obtained from subtracting sure_fg area from sure_bg area.
We use the function: **cv.distanceTransform (src, dst, distanceType, maskSize, labelType = cv.CV_32F)**
@param src 8-bit, single-channel (binary) source image.
@param dst output image with calculated distances. It is a 8-bit or 32-bit floating-point, single-channel image of the same size as src.
@param distanceType type of distance(see cv.DistanceTypes).
@param maskSize size of the distance transform mask, see (cv.DistanceTransformMasks).
@param labelType type of output image. It can be cv.CV_8U or cv.CV_32F. Type cv.CV_8U can be used only for the first variant of the function and distanceType == DIST_L1.
Try it
------
\htmlonly
<iframe src="../../js_watershed_distanceTransform.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
In the thresholded image, we get some regions of coins which we are sure of coins
and they are detached now. (In some cases, you may be interested in only foreground segmentation,
not in separating the mutually touching objects. In that case, you need not use distance transform,
just erosion is sufficient. Erosion is just another method to extract sure foreground area, that's
all.)
Try it
------
\htmlonly
<iframe src="../../js_watershed_foreground.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly
Now we know for sure which are region of coins, which are background and all. So we create marker
(it is an array of same size as that of original image, but with int32 datatype) and label the
regions inside it. The regions we know for sure (whether foreground or background) are labelled with
any positive integers, but different integers, and the area we don't know for sure are just left as
zero. For this we use **cv.connectedComponents()**. It labels background of the image with 0, then
other objects are labelled with integers starting from 1.
But we know that if background is marked with 0, watershed will consider it as unknown area. So we
want to mark it with different integer. Instead, we will mark unknown region, defined by unknown,
with 0.
Now our marker is ready. It is time for final step, apply watershed. Then marker image will be
modified. The boundary region will be marked with -1.
We use the function: **cv.connectedComponents (image, labels, connectivity = 8, ltype = cv.CV_32S)**
@param image the 8-bit single-channel image to be labeled.
@param labels destination labeled image(cv.CV_32SC1 type).
@param connectivity 8 or 4 for 8-way or 4-way connectivity respectively.
@param ltype output image label type. Currently cv.CV_32S and cv.CV_16U are supported.
We use the function: **cv.watershed (image, markers)**
@param image input 8-bit 3-channel image.
@param markers input/output 32-bit single-channel image (map) of markers. It should have the same size as image .
Try it
------
\htmlonly
<iframe src="../../js_watershed_watershed.html" width="100%"
onload="this.style.height=this.contentDocument.body.scrollHeight +'px';">
</iframe>
\endhtmlonly