|
|
|
@ -10,7 +10,7 @@ Goal |
|
|
|
|
We'll seek answers for the following questions: |
|
|
|
|
|
|
|
|
|
- How to go through each and every pixel of an image? |
|
|
|
|
- How is OpenCV matrix values stored? |
|
|
|
|
- How are OpenCV matrix values stored? |
|
|
|
|
- How to measure the performance of our algorithm? |
|
|
|
|
- What are lookup tables and why use them? |
|
|
|
|
|
|
|
|
@ -45,13 +45,13 @@ operation. In case of the *uchar* system this is 256 to be exact. |
|
|
|
|
Therefore, for larger images it would be wise to calculate all possible values beforehand and during |
|
|
|
|
the assignment just make the assignment, by using a lookup table. Lookup tables are simple arrays |
|
|
|
|
(having one or more dimensions) that for a given input value variation holds the final output value. |
|
|
|
|
Its strength lies that we do not need to make the calculation, we just need to read the result. |
|
|
|
|
Its strength is that we do not need to make the calculation, we just need to read the result. |
|
|
|
|
|
|
|
|
|
Our test case program (and the sample presented here) will do the following: read in a console line |
|
|
|
|
argument image (that may be either color or gray scale - console line argument too) and apply the |
|
|
|
|
reduction with the given console line argument integer value. In OpenCV, at the moment there are |
|
|
|
|
Our test case program (and the code sample below) will do the following: read in an image passed |
|
|
|
|
as a command line argument (it may be either color or grayscale) and apply the reduction |
|
|
|
|
with the given command line argument integer value. In OpenCV, at the moment there are |
|
|
|
|
three major ways of going through an image pixel by pixel. To make things a little more interesting |
|
|
|
|
will make the scanning for each image using all of these methods, and print out how long it took. |
|
|
|
|
we'll make the scanning of the image using each of these methods, and print out how long it took. |
|
|
|
|
|
|
|
|
|
You can download the full source code [here |
|
|
|
|
](https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/core/how_to_scan_images/how_to_scan_images.cpp) or look it up in |
|
|
|
@ -59,7 +59,7 @@ the samples directory of OpenCV at the cpp tutorial code for the core section. I |
|
|
|
|
@code{.bash} |
|
|
|
|
how_to_scan_images imageName.jpg intValueToReduce [G] |
|
|
|
|
@endcode |
|
|
|
|
The final argument is optional. If given the image will be loaded in gray scale format, otherwise |
|
|
|
|
The final argument is optional. If given the image will be loaded in grayscale format, otherwise |
|
|
|
|
the BGR color space is used. The first thing is to calculate the lookup table. |
|
|
|
|
|
|
|
|
|
@snippet how_to_scan_images.cpp dividewith |
|
|
|
@ -71,8 +71,8 @@ No OpenCV specific stuff here. |
|
|
|
|
Another issue is how do we measure time? Well OpenCV offers two simple functions to achieve this |
|
|
|
|
cv::getTickCount() and cv::getTickFrequency() . The first returns the number of ticks of |
|
|
|
|
your systems CPU from a certain event (like since you booted your system). The second returns how |
|
|
|
|
many times your CPU emits a tick during a second. So to measure in seconds the number of time |
|
|
|
|
elapsed between two operations is easy as: |
|
|
|
|
many times your CPU emits a tick during a second. So, measuring amount of time elapsed between |
|
|
|
|
two operations is as easy as: |
|
|
|
|
@code{.cpp} |
|
|
|
|
double t = (double)getTickCount(); |
|
|
|
|
// do something ... |
|
|
|
@ -85,8 +85,8 @@ How is the image matrix stored in memory? |
|
|
|
|
----------------------------------------- |
|
|
|
|
|
|
|
|
|
As you could already read in my @ref tutorial_mat_the_basic_image_container tutorial the size of the matrix |
|
|
|
|
depends on the color system used. More accurately, it depends from the number of channels used. In |
|
|
|
|
case of a gray scale image we have something like: |
|
|
|
|
depends on the color system used. More accurately, it depends on the number of channels used. In |
|
|
|
|
case of a grayscale image we have something like: |
|
|
|
|
|
|
|
|
|
![](tutorial_how_matrix_stored_1.png) |
|
|
|
|
|
|
|
|
@ -117,12 +117,12 @@ three channels so we need to pass through three times more items in each row. |
|
|
|
|
There's another way of this. The *data* data member of a *Mat* object returns the pointer to the |
|
|
|
|
first row, first column. If this pointer is null you have no valid input in that object. Checking |
|
|
|
|
this is the simplest method to check if your image loading was a success. In case the storage is |
|
|
|
|
continuous we can use this to go through the whole data pointer. In case of a gray scale image this |
|
|
|
|
continuous we can use this to go through the whole data pointer. In case of a grayscale image this |
|
|
|
|
would look like: |
|
|
|
|
@code{.cpp} |
|
|
|
|
uchar* p = I.data; |
|
|
|
|
|
|
|
|
|
for( unsigned int i =0; i < ncol*nrows; ++i) |
|
|
|
|
for( unsigned int i = 0; i < ncol*nrows; ++i) |
|
|
|
|
*p++ = table[*p]; |
|
|
|
|
@endcode |
|
|
|
|
You would get the same result. However, this code is a lot harder to read later on. It gets even |
|
|
|
@ -135,7 +135,7 @@ The iterator (safe) method |
|
|
|
|
|
|
|
|
|
In case of the efficient way making sure that you pass through the right amount of *uchar* fields |
|
|
|
|
and to skip the gaps that may occur between the rows was your responsibility. The iterator method is |
|
|
|
|
considered a safer way as it takes over these tasks from the user. All you need to do is ask the |
|
|
|
|
considered a safer way as it takes over these tasks from the user. All you need to do is to ask the |
|
|
|
|
begin and the end of the image matrix and then just increase the begin iterator until you reach the |
|
|
|
|
end. To acquire the value *pointed* by the iterator use the \* operator (add it before it). |
|
|
|
|
|
|
|
|
@ -152,17 +152,17 @@ On-the-fly address calculation with reference returning |
|
|
|
|
|
|
|
|
|
The final method isn't recommended for scanning. It was made to acquire or modify somehow random |
|
|
|
|
elements in the image. Its basic usage is to specify the row and column number of the item you want |
|
|
|
|
to access. During our earlier scanning methods you could already observe that is important through |
|
|
|
|
to access. During our earlier scanning methods you could already notice that it is important through |
|
|
|
|
what type we are looking at the image. It's no different here as you need to manually specify what |
|
|
|
|
type to use at the automatic lookup. You can observe this in case of the gray scale images for the |
|
|
|
|
type to use at the automatic lookup. You can observe this in case of the grayscale images for the |
|
|
|
|
following source code (the usage of the + cv::Mat::at() function): |
|
|
|
|
|
|
|
|
|
@snippet how_to_scan_images.cpp scan-random |
|
|
|
|
|
|
|
|
|
The functions takes your input type and coordinates and calculates on the fly the address of the |
|
|
|
|
The function takes your input type and coordinates and calculates the address of the |
|
|
|
|
queried item. Then returns a reference to that. This may be a constant when you *get* the value and |
|
|
|
|
non-constant when you *set* the value. As a safety step in **debug mode only**\* there is performed |
|
|
|
|
a check that your input coordinates are valid and does exist. If this isn't the case you'll get a |
|
|
|
|
non-constant when you *set* the value. As a safety step in **debug mode only**\* there is a check |
|
|
|
|
performed that your input coordinates are valid and do exist. If this isn't the case you'll get a |
|
|
|
|
nice output message of this on the standard error output stream. Compared to the efficient way in |
|
|
|
|
release mode the only difference in using this is that for every element of the image you'll get a |
|
|
|
|
new row pointer for what we use the C operator[] to acquire the column element. |
|
|
|
@ -173,7 +173,7 @@ OpenCV has a cv::Mat_ data type. It's the same as Mat with the extra need that a |
|
|
|
|
you need to specify the data type through what to look at the data matrix, however in return you can |
|
|
|
|
use the operator() for fast access of items. To make things even better this is easily convertible |
|
|
|
|
from and to the usual cv::Mat data type. A sample usage of this you can see in case of the |
|
|
|
|
color images of the upper function. Nevertheless, it's important to note that the same operation |
|
|
|
|
color images of the function above. Nevertheless, it's important to note that the same operation |
|
|
|
|
(with the same runtime speed) could have been done with the cv::Mat::at function. It's just a less |
|
|
|
|
to write for the lazy programmer trick. |
|
|
|
|
|
|
|
|
@ -195,7 +195,7 @@ Finally call the function (I is our input image and J the output one): |
|
|
|
|
Performance Difference |
|
|
|
|
---------------------- |
|
|
|
|
|
|
|
|
|
For the best result compile the program and run it on your own speed. To make the differences more |
|
|
|
|
For the best result compile the program and run it yourself. To make the differences more |
|
|
|
|
clear, I've used a quite large (2560 X 1600) image. The performance presented here are for |
|
|
|
|
color images. For a more accurate value I've averaged the value I got from the call of the function |
|
|
|
|
for hundred times. |
|
|
|
|