Merge branch 2.4

pull/249/merge
Andrey Kamaev 12 years ago
commit 962884cdec
  1. 2
      CMakeLists.txt
  2. 22
      android/android.toolchain.cmake
  3. 4
      android/service/CMakeLists.txt
  4. 22
      android/service/ReadMe.txt
  5. 44
      android/service/doc/UseCases.rst
  6. 4
      android/service/engine/jni/BinderComponent/OpenCVEngine.cpp
  7. 12
      android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp
  8. 8
      android/service/engine/jni/NativeService/CommonPackageManager.cpp
  9. 8
      android/service/engine/jni/NativeService/PackageInfo.cpp
  10. 2
      android/service/engine/jni/include/EngineCommon.h
  11. 28
      android/service/readme.txt
  12. 2
      cmake/OpenCVPCHSupport.cmake
  13. 2
      doc/tutorials/imgproc/shapedescriptors/bounding_rects_circles/bounding_rects_circles.rst
  14. 24
      doc/tutorials/introduction/android_binary_package/O4A_SDK.rst
  15. 16
      doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.rst
  16. BIN
      doc/tutorials/introduction/android_binary_package/images/install_opencv_manager_with_adb.png
  17. 8
      doc/user_guide/ug_mat.rst
  18. 2
      modules/core/include/opencv2/core/operations.hpp
  19. 1
      modules/core/src/persistence.cpp
  20. 6
      modules/highgui/src/cap_gstreamer.cpp
  21. 2
      modules/imgproc/doc/miscellaneous_transformations.rst
  22. 2
      modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst
  23. 73
      modules/imgproc/perf/perf_histogram.cpp
  24. 789
      modules/imgproc/src/histogram.cpp
  25. 19
      modules/java/generator/gen_javadoc.py
  26. 2
      modules/java/generator/src/java/android+CameraBridgeViewBase.java
  27. 2
      modules/java/generator/src/java/android+OpenCVLoader.java
  28. 4
      modules/ml/test/test_save_load.cpp
  29. 6
      modules/objdetect/perf/perf_cascadeclassifier.cpp
  30. 4
      modules/ts/include/opencv2/ts/ts_gtest.h
  31. 1
      modules/ts/src/ts_gtest.cpp
  32. 41
      modules/ts/src/ts_perf.cpp
  33. 42
      samples/android/tutorial-5-cameracontrol/src/org/opencv/samples/tutorial5/Sample5CameraControl.java
  34. 16
      samples/android/tutorial-5-cameracontrol/src/org/opencv/samples/tutorial5/SampleJavaCameraView.java
  35. 2
      samples/ocl/CMakeLists.txt

@ -458,7 +458,7 @@ if(BUILD_EXAMPLES OR BUILD_ANDROID_EXAMPLES OR INSTALL_PYTHON_EXAMPLES)
add_subdirectory(samples)
endif()
if(BUILD_ANDROID_SERVICE)
if(ANDROID)
add_subdirectory(android/service)
endif()

@ -280,6 +280,9 @@
# - November 2012
# [+] updated for NDK r8c
# [+] added support for clang compiler
# - December 2012
# [~] fixed ccache full path search
# [+] updated for NDK r8d
# ------------------------------------------------------------------------------
cmake_minimum_required( VERSION 2.6.3 )
@ -302,7 +305,7 @@ set( CMAKE_SYSTEM_VERSION 1 )
# rpath makes low sence for Android
set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." )
set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS)
if( CMAKE_HOST_WIN32 )
file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
@ -962,7 +965,11 @@ if( BUILD_WITH_ANDROID_NDK )
set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" )
set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" )
elseif( ANDROID_STL MATCHES "stlport" )
if( NOT ANDROID_NDK_RELEASE STRLESS "r8d" )
set( ANDROID_EXCEPTIONS ON )
else()
set( ANDROID_EXCEPTIONS OFF )
endif()
if( ANDROID_NDK_RELEASE STRLESS "r7" )
set( ANDROID_RTTI OFF )
else()
@ -974,7 +981,13 @@ if( BUILD_WITH_ANDROID_NDK )
set( ANDROID_EXCEPTIONS ON )
set( ANDROID_RTTI ON )
if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" )
# gnustl binary for 4.7 compiler is buggy :(
# TODO: look for right fix
set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" )
else()
set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
endif()
else()
set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" )
endif()
@ -1031,6 +1044,9 @@ endif()
# ccache support
__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE )
if( _ndk_ccache )
if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE )
unset( NDK_CCACHE CACHE )
endif()
find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary")
else()
unset( NDK_CCACHE CACHE )
@ -1260,7 +1276,7 @@ endif()
if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" )
if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE STRGREATER "r8b") AND (ARMEABI OR ARMEABI_V7A OR X86) )
set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" )
elseif( ANDROID_NDK_RELEASE STREQUAL "r8c")
elseif( ANDROID_NDK_RELEASE STRGREATER "r8b")
set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" )
elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE )
message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342
@ -1520,7 +1536,7 @@ endif()
# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used
# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform
# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86" or "mips" depending on ANDROID_ABI
# ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c; set only for NDK
# ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c, r8d; set only for NDK
# ANDROID_ARCH_NAME : "arm" or "x86" or "mips" depending on ANDROID_ABI
# ANDROID_SYSROOT : path to the compiler sysroot
# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform

@ -1,2 +1,6 @@
if(BUILD_ANDROID_SERVICE)
add_subdirectory(engine)
#add_subdirectory(engine_test)
endif()
install(FILES "readme.txt" DESTINATION "apk/" COMPONENT main)

@ -1,22 +0,0 @@
***************
Package Content
***************
The package provides new OpenCV SDK that uses OpenCV Manager for library initialization. OpenCV Manager provides the following benefits:
* Less memory usage. All apps use the same binaries from service and do not keep native libs inside them self;
* Hardware specific optimizations for all supported platforms;
* Trusted OpenCV library source. All packages with OpenCV are published on Google Play service;
* Regular updates and bug fixes;
Package consists from Library Project for Java development with Eclipse, C++ headers and libraries for native application development, javadoc samples and prebuilt binaries for ARM and X86 platforms.
To try new SDK on serial device with Google Play just install sample package and follow application messages (Google Play service access will be needed).
TO start example on device without Google Play you need to install OpenCV manager package and OpenCV binary pack for your platform from apk folder before.
See docs/doc/tutorials/introduction/android_binary_package/android_binary_package.html and docs/android/refmain.html for details about service.
On-line documentation will be available at address: http://docs.opencv.org/trunk
********
Contacts
********
Please send all feedback to Alexander Smorkalov mailto: alexander.smorkalov@itseez.com

@ -1,6 +1,40 @@
*******************************************
Manager Workflow
*******************************************
****************
.. _manager_selection:
OpenCV Manager selection
------------------------
Since version 1.7 several packages of OpenCV Manager is built. Every package includes OpenCV library
for package target platform. The internal library is used for most cases, except the rare one, when
arm-v7a without NEON instruction set processor is detected. In this case additional binary package
for arm-v7a is used. The new package selection logic in most cases simplifies OpenCV installation
on end user devices. In most cases OpenCV Manager may be installed automatically from Google Play.
For such case, when Google Play is not available, i.e. emulator, developer board, etc, you can
install it manually using adb tool.
.. code-block:: sh
:linenos:
adb install OpenCV-2.4.3-android-sdk/apk/OpenCV_2.4.3_Manager_2.0_<platform_name>.apk
Use table to determine right OpenCV Manager package:
+----------------------------+-----------------+-----------------------------------------------------+
| Hardware Platform | Android version | Package name |
+============================+=================+=====================================================+
| Intel x86 | >= 2.3 | OpenCV_2.4.3_Manager_2.0_x86.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| MIPS | >= 2.3 | OpenCV_2.4.3_Manager_2.0_mips.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi (arm-v5, arm-v6) | >= 2.3 | OpenCV_2.4.3_Manager_2.0_armeabi.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi-v7a (arm-v7a-NEON) | >= 2.3 | OpenCV_2.4.3_Manager_2.0_armv7a-neon.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi-v7a (arm-v7a-NEON) | 2.2 | OpenCV_2.4.3.1_Manager_2.3_armv7a-neon-android8.apk |
+----------------------------+-----------------+-----------------------------------------------------+
First application start
-----------------------
@ -9,10 +43,10 @@ There is no OpenCV Manager or OpenCV libraries:
.. image:: img/NoService.png
Aditional library package installation
--------------------------------------
Additional library package installation
---------------------------------------
There is an OpenCV Manager service, but there is no apropriate OpenCV library.
There is an OpenCV Manager service, but it does not contain appropriate OpenCV library.
If OpenCV library installation has been approved\:
.. image:: img/LibInstallAproved.png

@ -130,7 +130,7 @@ android::String16 OpenCVEngine::GetLibraryList(android::String16 version)
LOGD("Trying to load info library \"%s\"", tmp.c_str());
void* handle;
const char* (*info_func)();
InfoFunctionType info_func;
handle = dlopen(tmp.c_str(), RTLD_LAZY);
if (handle)
@ -138,7 +138,7 @@ android::String16 OpenCVEngine::GetLibraryList(android::String16 version)
const char* error;
dlerror();
*(void **) (&info_func) = dlsym(handle, "GetLibraryList");
info_func = (InfoFunctionType)dlsym(handle, "GetLibraryList");
if ((error = dlerror()) == NULL)
{
result = String16((*info_func)());

@ -24,12 +24,12 @@ JNIEXPORT jlong JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_open
JNIEXPORT jstring JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_getPackageName
(JNIEnv* env, jobject, jlong handle)
{
const char* (*info_func)();
InfoFunctionType info_func;
const char* result;
const char* error;
dlerror();
*(void **) (&info_func) = dlsym((void*)handle, "GetPackageName");
info_func = (InfoFunctionType)dlsym((void*)handle, "GetPackageName");
if ((error = dlerror()) == NULL)
result = (*info_func)();
else
@ -44,12 +44,12 @@ JNIEXPORT jstring JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_getPackageNam
JNIEXPORT jstring JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_getLibraryList
(JNIEnv* env, jobject, jlong handle)
{
const char* (*info_func)();
InfoFunctionType info_func;
const char* result;
const char* error;
dlerror();
*(void **) (&info_func) = dlsym((void*)handle, "GetLibraryList");
info_func = (InfoFunctionType)dlsym((void*)handle, "GetLibraryList");
if ((error = dlerror()) == NULL)
result = (*info_func)();
else
@ -64,12 +64,12 @@ JNIEXPORT jstring JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_getLibraryLis
JNIEXPORT jstring JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_getVersionName
(JNIEnv* env, jobject, jlong handle)
{
const char* (*info_func)();
InfoFunctionType info_func;
const char* result;
const char* error;
dlerror();
*(void **) (&info_func) = dlsym((void*)handle, "GetRevision");
info_func = (InfoFunctionType)dlsym((void*)handle, "GetRevision");
if ((error = dlerror()) == NULL)
result = (*info_func)();
else

@ -144,6 +144,13 @@ int CommonPackageManager::GetHardwareRating(int platform, int cpu_id, const std:
{
int result = -1;
if ((cpu_id & ARCH_X86) || (cpu_id & ARCH_X64) || (cpu_id & ARCH_MIPS))
// Note: No raiting for x86, x64 and MIPS
// only one package is used
result = 0;
else
{
// Calculate rating for Arm
for (size_t i = 0; i < group.size(); i++)
{
if (group[i] == std::pair<int, int>(platform, cpu_id))
@ -152,6 +159,7 @@ int CommonPackageManager::GetHardwareRating(int platform, int cpu_id, const std:
break;
}
}
}
return result;
}

@ -342,8 +342,8 @@ InstallPath(install_path)
LOGD("Trying to load info library \"%s\"", tmp.c_str());
void* handle;
const char* (*name_func)();
const char* (*revision_func)();
InfoFunctionType name_func;
InfoFunctionType revision_func;
handle = dlopen(tmp.c_str(), RTLD_LAZY);
if (handle)
@ -351,8 +351,8 @@ InstallPath(install_path)
const char* error;
dlerror();
*(void **) (&name_func) = dlsym(handle, "GetPackageName");
*(void **) (&revision_func) = dlsym(handle, "GetRevision");
name_func = (InfoFunctionType)dlsym(handle, "GetPackageName");
revision_func = (InfoFunctionType)dlsym(handle, "GetRevision");
error = dlerror();
if (!error && revision_func && name_func)

@ -17,4 +17,6 @@
// Class name of OpenCV engine binder object. Is needned for connection to service
#define OPECV_ENGINE_CLASSNAME "org.opencv.engine.OpenCVEngineInterface"
typedef const char* (*InfoFunctionType)();
#endif

@ -0,0 +1,28 @@
OpenCV Manager selection
========================
Since version 1.7 several packages of OpenCV Manager is built. Every package includes OpenCV library
for package target platform. The internal library is used for most cases, except the rare one, when
arm-v7a without NEON instruction set processor is detected. In this case additional binary package
for arm-v7a is used. The new package selection logic in most cases simplifies OpenCV installation
on end user devices. In most cases OpenCV Manager may be installed automatically from Google Play.
For such case, when Google Play is not available, i.e. emulator, developer board, etc, you can
install it manually using adb tool:
adb install OpenCV-2.4.3-android-sdk/apk/OpenCV_2.4.3.2_Manager_2.4_<platform_name>.apk
Use table to determine right OpenCV Manager package:
+----------------------------+-----------------+-----------------------------------------------------+
| Hardware Platform | Android version | Package name |
+============================+=================+=====================================================+
| Intel x86 | >= 2.3 | OpenCV_2.4.3.2_Manager_2.4_x86.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| MIPS | >= 2.3 | OpenCV_2.4.3.2_Manager_2.4_mips.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi (arm-v5, arm-v6) | >= 2.3 | OpenCV_2.4.3.2_Manager_2.4_armeabi.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi-v7a (arm-v7a-NEON) | >= 2.3 | OpenCV_2.4.3.2_Manager_2.4_armv7a-neon.apk |
+----------------------------+-----------------+-----------------------------------------------------+
| armeabi-v7a (arm-v7a-NEON) | 2.2 | OpenCV_2.4.3.2_Manager_2.4_armv7a-neon-android8.apk |
+----------------------------+-----------------+-----------------------------------------------------+

@ -19,7 +19,7 @@ IF(CMAKE_COMPILER_IS_GNUCXX)
ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion
OUTPUT_VARIABLE gcc_compiler_version)
#MESSAGE("GCC Version: ${gcc_compiler_version}")
IF(gcc_compiler_version MATCHES "4\\.[0,2-9]\\.[0-9x]")
IF(gcc_compiler_version VERSION_GREATER "4.2.-1")
SET(PCHSupport_FOUND TRUE)
ENDIF()

@ -85,7 +85,7 @@ This tutorial code's is shown lines below. You can also download it from `here <
for( int i = 0; i < contours.size(); i++ )
{ approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( Mat(contours_poly[i]) );
minEnclosingCircle( contours_poly[i], center[i], radius[i] );
minEnclosingCircle( (Mat)contours_poly[i], center[i], radius[i] );
}

@ -50,8 +50,8 @@ The structure of package contents looks as follows:
OpenCV-2.4.3-android-sdk
|_ apk
| |_ OpenCV_2.4.3_binary_pack_XXX.apk
| |_ OpenCV_2.4.3_Manager.apk
| |_ OpenCV_2.4.3_binary_pack_armv7a.apk
| |_ OpenCV_2.4.3_Manager_2.0_XXX.apk
|
|_ doc
|_ samples
@ -85,8 +85,8 @@ The structure of package contents looks as follows:
On production devices that have access to Google Play Market (and Internet) these packages will be
installed from Market on the first start of an application using OpenCV Manager API.
But devkits without Market or Internet connection require this packages to be installed manually.
Install the `Manager.apk` and the corresponding `binary_pack.apk` depending on the device CPU,
the Manager GUI provides this info. Below you'll see exact commands on how to do this.
Install the `Manager.apk` and optional `binary_pack.apk` if it needed.
See :ref:`manager_selection` for details.
.. note:: Installation from Internet is the preferable way since OpenCV team may publish updated
versions of this packages on the Market.
@ -280,21 +280,7 @@ Well, running samples from Eclipse is very simple:
To get rid of the message you will need to install `OpenCV Manager` and the appropriate `OpenCV binary pack`.
Simply tap :menuselection:`Yes` if you have *Google Play Market* installed on your device/emulator. It will redirect you to the corresponding page on *Google Play Market*.
If you have no access to the *Market*, which is often the case with emulators - you will need to install the packages from OpenCV4Android SDK folder manually. Open the console/terminal and type in the following two commands:
.. code-block:: sh
:linenos:
<Android SDK path>/platform-tools/adb install <OpenCV4Android SDK path>/apk/OpenCV_2.4.3_Manager.apk
<Android SDK path>/platform-tools/adb install <OpenCV4Android SDK path>/apk/OpenCV_2.4.3_binary_pack_armv7a.apk
If you're running Windows, that will probably look like this:
.. image:: images/install_opencv_manager_with_adb.png
:alt: Run these commands in the console to install OpenCV Manager
:align: center
When done, you will be able to run OpenCV samples on your device/emulator seamlessly.
If you have no access to the *Market*, which is often the case with emulators - you will need to install the packages from OpenCV4Android SDK folder manually. See :ref:`manager_selection` for details.
* Here is ``Tutorial 2 - Use OpenCV Camera`` sample, running on top of stock camera-preview of the emulator.

@ -54,20 +54,8 @@ Using async initialization is a **recommended** way for application development.
:alt: Add dependency from OpenCV library
:align: center
To run OpenCV Manager-based application for the first time you need to install package with the `OpenCV Manager` for your platform. Armeabi, Armeabi-v7a with NEON, x86 and MIPS achitectures supported.
You can do it using Google Play Market or manually with ``adb`` tool:
.. code-block:: sh
:linenos:
<Android SDK path>/platform-tools/adb install <OpenCV4Android SDK path>/apk/OpenCV_2.4.3_Manager.apk
For rare cases if NEON instruction set is not supported you need to install aditional OpenCV Library package:
.. code-block:: sh
:linenos:
<Android SDK path>/platform-tools/adb install <OpenCV4Android SDK path>/apk/OpenCV_2.4.3_binary_pack_armv7a.apk
In most cases OpenCV Manager may be installed automatically from Google Play. For such case, when Google Play is not available, i.e. emulator, developer board, etc, you can
install it manually using adb tool. See :ref:`manager_selection` for details.
There is a very base code snippet implementing the async initialization. It shows basic principles. See the "15-puzzle" OpenCV sample for details.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@ -71,7 +71,9 @@ There are functions in OpenCV, especially from calib3d module, such as ``project
//... fill the array
Mat pointsMat = Mat(points);
One can access a point in this matrix using the same method \texttt{Mat::at}: ::
One can access a point in this matrix using the same method ``Mat::at`` :
::
Point2f point = pointsMat.at<Point2f>(i, 0);
@ -109,7 +111,7 @@ Selecting a region of interest: ::
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
A convertion from \texttt{Mat} to C API data structures: ::
A convertion from ``Mat`` to C API data structures: ::
Mat img = imread("image.jpg");
IplImage img1 = img;
@ -150,7 +152,7 @@ A call to ``waitKey()`` starts a message passing cycle that waits for a key stro
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal);
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", CV_WINDOW_AUTOSIZE);
imshow("image", draw);

@ -64,7 +64,7 @@
#endif
#elif __GNUC__*10 + __GNUC_MINOR__ >= 42
#if !defined WIN32 && (defined __i486__ || defined __i586__ || \
#if !(defined WIN32 || defined _WIN32) && (defined __i486__ || defined __i586__ || \
defined __i686__ || defined __MMX__ || defined __SSE__ || defined __ppc__)
#define CV_XADD __sync_fetch_and_add
#else

@ -5170,6 +5170,7 @@ void FileStorage::release()
string FileStorage::releaseAndGetString()
{
string buf;
buf.reserve(16); // HACK: Work around for compiler bug
if( fs.obj && fs.obj->outbuf )
icvClose(fs.obj, &buf);

@ -399,12 +399,12 @@ bool CvCapture_GStreamer::open( int type, const char* filename )
gst_app_sink_set_max_buffers (GST_APP_SINK(sink), 1);
gst_app_sink_set_drop (GST_APP_SINK(sink), stream);
gst_app_sink_set_caps(GST_APP_SINK(sink), gst_caps_new_simple("video/x-raw-rgb",
caps = gst_caps_new_simple("video/x-raw-rgb",
"red_mask", G_TYPE_INT, 0x0000FF,
"green_mask", G_TYPE_INT, 0x00FF00,
"blue_mask", G_TYPE_INT, 0xFF0000,
NULL));
NULL);
gst_app_sink_set_caps(GST_APP_SINK(sink), caps);
gst_caps_unref(caps);
if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_READY) ==

@ -661,7 +661,7 @@ Applies a fixed-level threshold to each array element.
:param dst: output array of the same size and type as ``src``.
:param thresh: treshold value.
:param thresh: threshold value.
:param maxval: maximum value to use with the ``THRESH_BINARY`` and ``THRESH_BINARY_INV`` thresholding types.

@ -137,7 +137,7 @@ Finds contours in a binary image.
:param contours: Detected contours. Each contour is stored as a vector of points.
:param hierarchy: Optional output vector containing information about the image topology. It has as many elements as the number of contours. For each contour ``contours[i]`` , the elements ``hierarchy[i][0]`` , ``hiearchy[i][1]`` , ``hiearchy[i][2]`` , and ``hiearchy[i][3]`` are set to 0-based indices in ``contours`` of the next and previous contours at the same hierarchical level: the first child contour and the parent contour, respectively. If for a contour ``i`` there are no next, previous, parent, or nested contours, the corresponding elements of ``hierarchy[i]`` will be negative.
:param hierarchy: Optional output vector, containing information about the image topology. It has as many elements as the number of contours. For each i-th contour ``contours[i]`` , the elements ``hierarchy[i][0]`` , ``hiearchy[i][1]`` , ``hiearchy[i][2]`` , and ``hiearchy[i][3]`` are set to 0-based indices in ``contours`` of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively. If for the contour ``i`` there are no next, previous, parent, or nested contours, the corresponding elements of ``hierarchy[i]`` will be negative.
:param mode: Contour retrieval mode (if you use Python see also a note below).

@ -9,14 +9,14 @@ using std::tr1::get;
typedef tr1::tuple<Size, MatType> Size_Source_t;
typedef TestBaseWithParam<Size_Source_t> Size_Source;
typedef TestBaseWithParam<Size> MatSize;
static const float rangeHight = 256.0f;
static const float rangeLow = 0.0f;
PERF_TEST_P(Size_Source, calcHist,
testing::Combine(testing::Values(TYPICAL_MAT_SIZES),
testing::Values(CV_8U, CV_32F)
)
PERF_TEST_P(Size_Source, calcHist1d,
testing::Combine(testing::Values(sz3MP, sz5MP),
testing::Values(CV_8U, CV_16U, CV_32F) )
)
{
Size size = get<0>(GetParam());
@ -28,10 +28,69 @@ PERF_TEST_P(Size_Source, calcHist,
int dims = 1;
int numberOfImages = 1;
const float r[] = {0.0f, 256.0f};
const float r[] = {rangeLow, rangeHight};
const float* ranges[] = {r};
declare.in(source, WARMUP_RNG).time(20).iterations(1000);
randu(source, rangeLow, rangeHight);
declare.in(source);
TEST_CYCLE()
{
calcHist(&source, numberOfImages, channels, Mat(), hist, dims, histSize, ranges);
}
SANITY_CHECK(hist);
}
PERF_TEST_P(Size_Source, calcHist2d,
testing::Combine(testing::Values(sz3MP, sz5MP),
testing::Values(CV_8UC2, CV_16UC2, CV_32FC2) )
)
{
Size size = get<0>(GetParam());
MatType type = get<1>(GetParam());
Mat source(size.height, size.width, type);
Mat hist;
int channels [] = {0, 1};
int histSize [] = {256, 256};
int dims = 2;
int numberOfImages = 1;
const float r[] = {rangeLow, rangeHight};
const float* ranges[] = {r, r};
randu(source, rangeLow, rangeHight);
declare.in(source);
TEST_CYCLE()
{
calcHist(&source, numberOfImages, channels, Mat(), hist, dims, histSize, ranges);
}
SANITY_CHECK(hist);
}
PERF_TEST_P(Size_Source, calcHist3d,
testing::Combine(testing::Values(sz3MP, sz5MP),
testing::Values(CV_8UC3, CV_16UC3, CV_32FC3) )
)
{
Size size = get<0>(GetParam());
MatType type = get<1>(GetParam());
Mat hist;
int channels [] = {0, 1, 2};
int histSize [] = {32, 32, 32};
int dims = 3;
int numberOfImages = 1;
Mat source(size.height, size.width, type);
const float r[] = {rangeLow, rangeHight};
const float* ranges[] = {r, r, r};
randu(source, rangeLow, rangeHight);
declare.in(source);
TEST_CYCLE()
{
calcHist(&source, numberOfImages, channels, Mat(), hist, dims, histSize, ranges);

@ -165,11 +165,13 @@ static void histPrepareImages( const Mat* images, int nimages, const int* channe
deltas[dims*2 + 1] = (int)(mask.step/mask.elemSize1());
}
#ifndef HAVE_TBB
if( isContinuous )
{
imsize.width *= imsize.height;
imsize.height = 1;
}
#endif
if( !ranges )
{
@ -207,6 +209,538 @@ static void histPrepareImages( const Mat* images, int nimages, const int* channe
////////////////////////////////// C A L C U L A T E H I S T O G R A M ////////////////////////////////////
#ifdef HAVE_TBB
enum {one = 1, two, three}; // array elements number
template<typename T>
class calcHist1D_Invoker
{
public:
calcHist1D_Invoker( const vector<uchar*>& _ptrs, const vector<int>& _deltas,
Mat& hist, const double* _uniranges, int sz, int dims,
Size& imageSize )
: mask_(_ptrs[dims]),
mstep_(_deltas[dims*2 + 1]),
imageWidth_(imageSize.width),
histogramSize_(hist.size()), histogramType_(hist.type()),
globalHistogram_((tbb::atomic<int>*)hist.data)
{
p_[0] = ((T**)&_ptrs[0])[0];
step_[0] = (&_deltas[0])[1];
d_[0] = (&_deltas[0])[0];
a_[0] = (&_uniranges[0])[0];
b_[0] = (&_uniranges[0])[1];
size_[0] = sz;
}
void operator()( const BlockedRange& range ) const
{
T* p0 = p_[0] + range.begin() * (step_[0] + imageWidth_*d_[0]);
uchar* mask = mask_ + range.begin()*mstep_;
for( int row = range.begin(); row < range.end(); row++, p0 += step_[0] )
{
if( !mask_ )
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0] )
{
int idx = cvFloor(*p0*a_[0] + b_[0]);
if( (unsigned)idx < (unsigned)size_[0] )
{
globalHistogram_[idx].fetch_and_add(1);
}
}
}
else
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0] )
{
if( mask[x] )
{
int idx = cvFloor(*p0*a_[0] + b_[0]);
if( (unsigned)idx < (unsigned)size_[0] )
{
globalHistogram_[idx].fetch_and_add(1);
}
}
}
mask += mstep_;
}
}
}
private:
T* p_[one];
uchar* mask_;
int step_[one];
int d_[one];
int mstep_;
double a_[one];
double b_[one];
int size_[one];
int imageWidth_;
Size histogramSize_;
int histogramType_;
tbb::atomic<int>* globalHistogram_;
};
template<typename T>
class calcHist2D_Invoker
{
public:
calcHist2D_Invoker( const vector<uchar*>& _ptrs, const vector<int>& _deltas,
Mat& hist, const double* _uniranges, const int* size,
int dims, Size& imageSize, size_t* hstep )
: mask_(_ptrs[dims]),
mstep_(_deltas[dims*2 + 1]),
imageWidth_(imageSize.width),
histogramSize_(hist.size()), histogramType_(hist.type()),
globalHistogram_(hist.data)
{
p_[0] = ((T**)&_ptrs[0])[0]; p_[1] = ((T**)&_ptrs[0])[1];
step_[0] = (&_deltas[0])[1]; step_[1] = (&_deltas[0])[3];
d_[0] = (&_deltas[0])[0]; d_[1] = (&_deltas[0])[2];
a_[0] = (&_uniranges[0])[0]; a_[1] = (&_uniranges[0])[2];
b_[0] = (&_uniranges[0])[1]; b_[1] = (&_uniranges[0])[3];
size_[0] = size[0]; size_[1] = size[1];
hstep_[0] = hstep[0];
}
void operator()(const BlockedRange& range) const
{
T* p0 = p_[0] + range.begin()*(step_[0] + imageWidth_*d_[0]);
T* p1 = p_[1] + range.begin()*(step_[1] + imageWidth_*d_[1]);
uchar* mask = mask_ + range.begin()*mstep_;
for( int row = range.begin(); row < range.end(); row++, p0 += step_[0], p1 += step_[1] )
{
if( !mask_ )
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1] )
{
int idx0 = cvFloor(*p0*a_[0] + b_[0]);
int idx1 = cvFloor(*p1*a_[1] + b_[1]);
if( (unsigned)idx0 < (unsigned)size_[0] && (unsigned)idx1 < (unsigned)size_[1] )
( (tbb::atomic<int>*)(globalHistogram_ + hstep_[0]*idx0) )[idx1].fetch_and_add(1);
}
}
else
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1] )
{
if( mask[x] )
{
int idx0 = cvFloor(*p0*a_[0] + b_[0]);
int idx1 = cvFloor(*p1*a_[1] + b_[1]);
if( (unsigned)idx0 < (unsigned)size_[0] && (unsigned)idx1 < (unsigned)size_[1] )
((tbb::atomic<int>*)(globalHistogram_ + hstep_[0]*idx0))[idx1].fetch_and_add(1);
}
}
mask += mstep_;
}
}
}
private:
T* p_[two];
uchar* mask_;
int step_[two];
int d_[two];
int mstep_;
double a_[two];
double b_[two];
int size_[two];
const int imageWidth_;
size_t hstep_[one];
Size histogramSize_;
int histogramType_;
uchar* globalHistogram_;
};
template<typename T>
class calcHist3D_Invoker
{
public:
calcHist3D_Invoker( const vector<uchar*>& _ptrs, const vector<int>& _deltas,
Size imsize, Mat& hist, const double* uniranges, int _dims,
size_t* hstep, int* size )
: mask_(_ptrs[_dims]),
mstep_(_deltas[_dims*2 + 1]),
imageWidth_(imsize.width),
globalHistogram_(hist.data)
{
p_[0] = ((T**)&_ptrs[0])[0]; p_[1] = ((T**)&_ptrs[0])[1]; p_[2] = ((T**)&_ptrs[0])[2];
step_[0] = (&_deltas[0])[1]; step_[1] = (&_deltas[0])[3]; step_[2] = (&_deltas[0])[5];
d_[0] = (&_deltas[0])[0]; d_[1] = (&_deltas[0])[2]; d_[2] = (&_deltas[0])[4];
a_[0] = uniranges[0]; a_[1] = uniranges[2]; a_[2] = uniranges[4];
b_[0] = uniranges[1]; b_[1] = uniranges[3]; b_[2] = uniranges[5];
size_[0] = size[0]; size_[1] = size[1]; size_[2] = size[2];
hstep_[0] = hstep[0]; hstep_[1] = hstep[1];
}
void operator()( const BlockedRange& range ) const
{
T* p0 = p_[0] + range.begin()*(imageWidth_*d_[0] + step_[0]);
T* p1 = p_[1] + range.begin()*(imageWidth_*d_[1] + step_[1]);
T* p2 = p_[2] + range.begin()*(imageWidth_*d_[2] + step_[2]);
uchar* mask = mask_ + range.begin()*mstep_;
for( int i = range.begin(); i < range.end(); i++, p0 += step_[0], p1 += step_[1], p2 += step_[2] )
{
if( !mask_ )
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1], p2 += d_[2] )
{
int idx0 = cvFloor(*p0*a_[0] + b_[0]);
int idx1 = cvFloor(*p1*a_[1] + b_[1]);
int idx2 = cvFloor(*p2*a_[2] + b_[2]);
if( (unsigned)idx0 < (unsigned)size_[0] &&
(unsigned)idx1 < (unsigned)size_[1] &&
(unsigned)idx2 < (unsigned)size_[2] )
{
( (tbb::atomic<int>*)(globalHistogram_ + hstep_[0]*idx0 + hstep_[1]*idx1) )[idx2].fetch_and_add(1);
}
}
}
else
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1], p2 += d_[2] )
{
if( mask[x] )
{
int idx0 = cvFloor(*p0*a_[0] + b_[0]);
int idx1 = cvFloor(*p1*a_[1] + b_[1]);
int idx2 = cvFloor(*p2*a_[2] + b_[2]);
if( (unsigned)idx0 < (unsigned)size_[0] &&
(unsigned)idx1 < (unsigned)size_[1] &&
(unsigned)idx2 < (unsigned)size_[2] )
{
( (tbb::atomic<int>*)(globalHistogram_ + hstep_[0]*idx0 + hstep_[1]*idx1) )[idx2].fetch_and_add(1);
}
}
}
mask += mstep_;
}
}
}
static bool isFit( const Mat& histogram, const Size imageSize )
{
return ( imageSize.width * imageSize.height >= 320*240
&& histogram.total() >= 8*8*8 );
}
private:
T* p_[three];
uchar* mask_;
int step_[three];
int d_[three];
const int mstep_;
double a_[three];
double b_[three];
int size_[three];
int imageWidth_;
size_t hstep_[two];
uchar* globalHistogram_;
};
class CalcHist1D_8uInvoker
{
public:
CalcHist1D_8uInvoker( const vector<uchar*>& ptrs, const vector<int>& deltas,
Size imsize, Mat& hist, int dims, const vector<size_t>& tab,
tbb::mutex* lock )
: mask_(ptrs[dims]),
mstep_(deltas[dims*2 + 1]),
imageWidth_(imsize.width),
imageSize_(imsize),
histSize_(hist.size()), histType_(hist.type()),
tab_((size_t*)&tab[0]),
histogramWriteLock_(lock),
globalHistogram_(hist.data)
{
p_[0] = (&ptrs[0])[0];
step_[0] = (&deltas[0])[1];
d_[0] = (&deltas[0])[0];
}
void operator()( const BlockedRange& range ) const
{
int localHistogram[256] = { 0, };
uchar* mask = mask_;
uchar* p0 = p_[0];
int x;
tbb::mutex::scoped_lock lock;
if( !mask_ )
{
int n = (imageWidth_ - 4) / 4 + 1;
int tail = imageWidth_ - n*4;
int xN = 4*n;
p0 += (xN*d_[0] + tail*d_[0] + step_[0]) * range.begin();
}
else
{
p0 += (imageWidth_*d_[0] + step_[0]) * range.begin();
mask += mstep_*range.begin();
}
for( int i = range.begin(); i < range.end(); i++, p0 += step_[0] )
{
if( !mask_ )
{
if( d_[0] == 1 )
{
for( x = 0; x <= imageWidth_ - 4; x += 4 )
{
int t0 = p0[x], t1 = p0[x+1];
localHistogram[t0]++; localHistogram[t1]++;
t0 = p0[x+2]; t1 = p0[x+3];
localHistogram[t0]++; localHistogram[t1]++;
}
p0 += x;
}
else
{
for( x = 0; x <= imageWidth_ - 4; x += 4 )
{
int t0 = p0[0], t1 = p0[d_[0]];
localHistogram[t0]++; localHistogram[t1]++;
p0 += d_[0]*2;
t0 = p0[0]; t1 = p0[d_[0]];
localHistogram[t0]++; localHistogram[t1]++;
p0 += d_[0]*2;
}
}
for( ; x < imageWidth_; x++, p0 += d_[0] )
{
localHistogram[*p0]++;
}
}
else
{
for( x = 0; x < imageWidth_; x++, p0 += d_[0] )
{
if( mask[x] )
{
localHistogram[*p0]++;
}
}
mask += mstep_;
}
}
lock.acquire(*histogramWriteLock_);
for(int i = 0; i < 256; i++ )
{
size_t hidx = tab_[i];
if( hidx < OUT_OF_RANGE )
{
*(int*)((globalHistogram_ + hidx)) += localHistogram[i];
}
}
lock.release();
}
static bool isFit( const Mat& histogram, const Size imageSize )
{
return ( histogram.total() >= 8
&& imageSize.width * imageSize.height >= 160*120 );
}
private:
uchar* p_[one];
uchar* mask_;
int mstep_;
int step_[one];
int d_[one];
int imageWidth_;
Size imageSize_;
Size histSize_;
int histType_;
size_t* tab_;
tbb::mutex* histogramWriteLock_;
uchar* globalHistogram_;
};
class CalcHist2D_8uInvoker
{
public:
CalcHist2D_8uInvoker( const vector<uchar*>& _ptrs, const vector<int>& _deltas,
Size imsize, Mat& hist, int dims, const vector<size_t>& _tab,
tbb::mutex* lock )
: mask_(_ptrs[dims]),
mstep_(_deltas[dims*2 + 1]),
imageWidth_(imsize.width),
histSize_(hist.size()), histType_(hist.type()),
tab_((size_t*)&_tab[0]),
histogramWriteLock_(lock),
globalHistogram_(hist.data)
{
p_[0] = (uchar*)(&_ptrs[0])[0]; p_[1] = (uchar*)(&_ptrs[0])[1];
step_[0] = (&_deltas[0])[1]; step_[1] = (&_deltas[0])[3];
d_[0] = (&_deltas[0])[0]; d_[1] = (&_deltas[0])[2];
}
void operator()( const BlockedRange& range ) const
{
uchar* p0 = p_[0] + range.begin()*(step_[0] + imageWidth_*d_[0]);
uchar* p1 = p_[1] + range.begin()*(step_[1] + imageWidth_*d_[1]);
uchar* mask = mask_ + range.begin()*mstep_;
Mat localHist = Mat::zeros(histSize_, histType_);
uchar* localHistData = localHist.data;
tbb::mutex::scoped_lock lock;
for(int i = range.begin(); i < range.end(); i++, p0 += step_[0], p1 += step_[1])
{
if( !mask_ )
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1] )
{
size_t idx = tab_[*p0] + tab_[*p1 + 256];
if( idx < OUT_OF_RANGE )
{
++*(int*)(localHistData + idx);
}
}
}
else
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1] )
{
size_t idx;
if( mask[x] && (idx = tab_[*p0] + tab_[*p1 + 256]) < OUT_OF_RANGE )
{
++*(int*)(localHistData + idx);
}
}
mask += mstep_;
}
}
lock.acquire(*histogramWriteLock_);
for(int i = 0; i < histSize_.width*histSize_.height; i++)
{
((int*)globalHistogram_)[i] += ((int*)localHistData)[i];
}
lock.release();
}
static bool isFit( const Mat& histogram, const Size imageSize )
{
return ( (histogram.total() > 4*4 && histogram.total() <= 116*116
&& imageSize.width * imageSize.height >= 320*240)
|| (histogram.total() > 116*116 && imageSize.width * imageSize.height >= 1280*720) );
}
private:
uchar* p_[two];
uchar* mask_;
int step_[two];
int d_[two];
int mstep_;
int imageWidth_;
Size histSize_;
int histType_;
size_t* tab_;
tbb::mutex* histogramWriteLock_;
uchar* globalHistogram_;
};
class CalcHist3D_8uInvoker
{
public:
CalcHist3D_8uInvoker( const vector<uchar*>& _ptrs, const vector<int>& _deltas,
Size imsize, Mat& hist, int dims, const vector<size_t>& tab )
: mask_(_ptrs[dims]),
mstep_(_deltas[dims*2 + 1]),
histogramSize_(hist.size.p), histogramType_(hist.type()),
imageWidth_(imsize.width),
tab_((size_t*)&tab[0]),
globalHistogram_(hist.data)
{
p_[0] = (uchar*)(&_ptrs[0])[0]; p_[1] = (uchar*)(&_ptrs[0])[1]; p_[2] = (uchar*)(&_ptrs[0])[2];
step_[0] = (&_deltas[0])[1]; step_[1] = (&_deltas[0])[3]; step_[2] = (&_deltas[0])[5];
d_[0] = (&_deltas[0])[0]; d_[1] = (&_deltas[0])[2]; d_[2] = (&_deltas[0])[4];
}
void operator()( const BlockedRange& range ) const
{
uchar* p0 = p_[0] + range.begin()*(step_[0] + imageWidth_*d_[0]);
uchar* p1 = p_[1] + range.begin()*(step_[1] + imageWidth_*d_[1]);
uchar* p2 = p_[2] + range.begin()*(step_[2] + imageWidth_*d_[2]);
uchar* mask = mask_ + range.begin()*mstep_;
for(int i = range.begin(); i < range.end(); i++, p0 += step_[0], p1 += step_[1], p2 += step_[2] )
{
if( !mask_ )
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1], p2 += d_[2] )
{
size_t idx = tab_[*p0] + tab_[*p1 + 256] + tab_[*p2 + 512];
if( idx < OUT_OF_RANGE )
{
( *(tbb::atomic<int>*)(globalHistogram_ + idx) ).fetch_and_add(1);
}
}
}
else
{
for( int x = 0; x < imageWidth_; x++, p0 += d_[0], p1 += d_[1], p2 += d_[2] )
{
size_t idx;
if( mask[x] && (idx = tab_[*p0] + tab_[*p1 + 256] + tab_[*p2 + 512]) < OUT_OF_RANGE )
{
(*(tbb::atomic<int>*)(globalHistogram_ + idx)).fetch_and_add(1);
}
}
mask += mstep_;
}
}
}
static bool isFit( const Mat& histogram, const Size imageSize )
{
return ( histogram.total() >= 128*128*128
&& imageSize.width * imageSize.width >= 320*240 );
}
private:
uchar* p_[three];
uchar* mask_;
int mstep_;
int step_[three];
int d_[three];
int* histogramSize_;
int histogramType_;
int imageWidth_;
size_t* tab_;
uchar* globalHistogram_;
};
static void
callCalcHist2D_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas,
Size imsize, Mat& hist, int dims, vector<size_t>& _tab )
{
int grainSize = imsize.height / tbb::task_scheduler_init::default_num_threads();
tbb::mutex histogramWriteLock;
CalcHist2D_8uInvoker body(_ptrs, _deltas, imsize, hist, dims, _tab, &histogramWriteLock);
parallel_for(BlockedRange(0, imsize.height, grainSize), body);
}
static void
callCalcHist3D_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas,
Size imsize, Mat& hist, int dims, vector<size_t>& _tab )
{
CalcHist3D_8uInvoker body(_ptrs, _deltas, imsize, hist, dims, _tab);
parallel_for(BlockedRange(0, imsize.height), body);
}
#endif
template<typename T> static void
calcHist_( vector<uchar*>& _ptrs, const vector<int>& _deltas,
@ -234,6 +768,11 @@ calcHist_( vector<uchar*>& _ptrs, const vector<int>& _deltas,
if( dims == 1 )
{
#ifdef HAVE_TBB
calcHist1D_Invoker<T> body(_ptrs, _deltas, hist, _uniranges, size[0], dims, imsize);
parallel_for(BlockedRange(0, imsize.height), body);
return;
#endif
double a = uniranges[0], b = uniranges[1];
int sz = size[0], d0 = deltas[0], step0 = deltas[1];
const T* p0 = (const T*)ptrs[0];
@ -259,6 +798,11 @@ calcHist_( vector<uchar*>& _ptrs, const vector<int>& _deltas,
}
else if( dims == 2 )
{
#ifdef HAVE_TBB
calcHist2D_Invoker<T> body(_ptrs, _deltas, hist, _uniranges, size, dims, imsize, hstep);
parallel_for(BlockedRange(0, imsize.height), body);
return;
#endif
double a0 = uniranges[0], b0 = uniranges[1], a1 = uniranges[2], b1 = uniranges[3];
int sz0 = size[0], sz1 = size[1];
int d0 = deltas[0], step0 = deltas[1],
@ -290,6 +834,14 @@ calcHist_( vector<uchar*>& _ptrs, const vector<int>& _deltas,
}
else if( dims == 3 )
{
#ifdef HAVE_TBB
if( calcHist3D_Invoker<T>::isFit(hist, imsize) )
{
calcHist3D_Invoker<T> body(_ptrs, _deltas, imsize, hist, uniranges, dims, hstep, size);
parallel_for(BlockedRange(0, imsize.height), body);
return;
}
#endif
double a0 = uniranges[0], b0 = uniranges[1],
a1 = uniranges[2], b1 = uniranges[3],
a2 = uniranges[4], b2 = uniranges[5];
@ -441,8 +993,20 @@ calcHist_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas,
if( dims == 1 )
{
#ifdef HAVE_TBB
if( CalcHist1D_8uInvoker::isFit(hist, imsize) )
{
int treadsNumber = tbb::task_scheduler_init::default_num_threads();
int grainSize = imsize.height/treadsNumber;
tbb::mutex histogramWriteLock;
CalcHist1D_8uInvoker body(_ptrs, _deltas, imsize, hist, dims, _tab, &histogramWriteLock);
parallel_for(BlockedRange(0, imsize.height, grainSize), body);
return;
}
#endif
int d0 = deltas[0], step0 = deltas[1];
int matH[256] = {0};
int matH[256] = { 0, };
const uchar* p0 = (const uchar*)ptrs[0];
for( ; imsize.height--; p0 += step0, mask += mstep )
@ -489,6 +1053,13 @@ calcHist_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas,
}
else if( dims == 2 )
{
#ifdef HAVE_TBB
if( CalcHist2D_8uInvoker::isFit(hist, imsize) )
{
callCalcHist2D_8u(_ptrs, _deltas, imsize, hist, dims, _tab);
return;
}
#endif
int d0 = deltas[0], step0 = deltas[1],
d1 = deltas[2], step1 = deltas[3];
const uchar* p0 = (const uchar*)ptrs[0];
@ -514,6 +1085,13 @@ calcHist_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas,
}
else if( dims == 3 )
{
#ifdef HAVE_TBB
if( CalcHist3D_8uInvoker::isFit(hist, imsize) )
{
callCalcHist3D_8u(_ptrs, _deltas, imsize, hist, dims, _tab);
return;
}
#endif
int d0 = deltas[0], step0 = deltas[1],
d1 = deltas[2], step1 = deltas[3],
d2 = deltas[4], step2 = deltas[5];
@ -2404,61 +2982,206 @@ cvCalcProbDensity( const CvHistogram* hist, const CvHistogram* hist_mask,
}
}
class EqualizeHistCalcHist_Invoker
{
public:
enum {HIST_SZ = 256};
#ifdef HAVE_TBB
typedef tbb::mutex* MutextPtr;
#else
typedef void* MutextPtr;
#endif
CV_IMPL void cvEqualizeHist( const CvArr* srcarr, CvArr* dstarr )
EqualizeHistCalcHist_Invoker(cv::Mat& src, int* histogram, MutextPtr histogramLock)
: src_(src), globalHistogram_(histogram), histogramLock_(histogramLock)
{ }
void operator()( const cv::BlockedRange& rowRange ) const
{
CvMat sstub, *src = cvGetMat(srcarr, &sstub);
CvMat dstub, *dst = cvGetMat(dstarr, &dstub);
int localHistogram[HIST_SZ] = {0, };
const size_t sstep = src_.step;
int width = src_.cols;
int height = rowRange.end() - rowRange.begin();
CV_Assert( CV_ARE_SIZES_EQ(src, dst) && CV_ARE_TYPES_EQ(src, dst) &&
CV_MAT_TYPE(src->type) == CV_8UC1 );
CvSize size = cvGetMatSize(src);
if( CV_IS_MAT_CONT(src->type & dst->type) )
if (src_.isContinuous())
{
size.width *= size.height;
size.height = 1;
width *= height;
height = 1;
}
int x, y;
const int hist_sz = 256;
int hist[hist_sz];
memset(hist, 0, sizeof(hist));
for( y = 0; y < size.height; y++ )
for (const uchar* ptr = src_.ptr<uchar>(rowRange.begin()); height--; ptr += sstep)
{
const uchar* sptr = src->data.ptr + src->step*y;
for( x = 0; x < size.width; x++ )
hist[sptr[x]]++;
int x = 0;
for (; x <= width - 4; x += 4)
{
int t0 = ptr[x], t1 = ptr[x+1];
localHistogram[t0]++; localHistogram[t1]++;
t0 = ptr[x+2]; t1 = ptr[x+3];
localHistogram[t0]++; localHistogram[t1]++;
}
float scale = 255.f/(size.width*size.height);
int sum = 0;
uchar lut[hist_sz+1];
for (; x < width; ++x, ++ptr)
localHistogram[ptr[x]]++;
}
#ifdef HAVE_TBB
tbb::mutex::scoped_lock lock(*histogramLock_);
#endif
for( int i = 0; i < HIST_SZ; i++ )
globalHistogram_[i] += localHistogram[i];
}
for( int i = 0; i < hist_sz; i++ )
static bool isWorthParallel( const cv::Mat& src )
{
sum += hist[i];
int val = cvRound(sum*scale);
lut[i] = CV_CAST_8U(val);
#ifdef HAVE_TBB
return ( src.total() >= 640*480 );
#else
(void)src;
return false;
#endif
}
lut[0] = 0;
for( y = 0; y < size.height; y++ )
private:
EqualizeHistCalcHist_Invoker& operator=(const EqualizeHistCalcHist_Invoker&);
cv::Mat& src_;
int* globalHistogram_;
MutextPtr histogramLock_;
};
class EqualizeHistLut_Invoker
{
const uchar* sptr = src->data.ptr + src->step*y;
uchar* dptr = dst->data.ptr + dst->step*y;
for( x = 0; x < size.width; x++ )
dptr[x] = lut[sptr[x]];
public:
EqualizeHistLut_Invoker( cv::Mat& src, cv::Mat& dst, int* lut )
: src_(src),
dst_(dst),
lut_(lut)
{ }
void operator()( const cv::BlockedRange& rowRange ) const
{
const size_t sstep = src_.step;
const size_t dstep = dst_.step;
int width = src_.cols;
int height = rowRange.end() - rowRange.begin();
int* lut = lut_;
if (src_.isContinuous() && dst_.isContinuous())
{
width *= height;
height = 1;
}
const uchar* sptr = src_.ptr<uchar>(rowRange.begin());
uchar* dptr = dst_.ptr<uchar>(rowRange.begin());
for (; height--; sptr += sstep, dptr += dstep)
{
int x = 0;
for (; x <= width - 4; x += 4)
{
int v0 = sptr[x];
int v1 = sptr[x+1];
int x0 = lut[v0];
int x1 = lut[v1];
dptr[x] = (uchar)x0;
dptr[x+1] = (uchar)x1;
v0 = sptr[x+2];
v1 = sptr[x+3];
x0 = lut[v0];
x1 = lut[v1];
dptr[x+2] = (uchar)x0;
dptr[x+3] = (uchar)x1;
}
for (; x < width; ++x)
dptr[x] = (uchar)lut[sptr[x]];
}
}
static bool isWorthParallel( const cv::Mat& src )
{
#ifdef HAVE_TBB
return ( src.total() >= 640*480 );
#else
(void)src;
return false;
#endif
}
private:
EqualizeHistLut_Invoker& operator=(const EqualizeHistLut_Invoker&);
cv::Mat& src_;
cv::Mat& dst_;
int* lut_;
};
CV_IMPL void cvEqualizeHist( const CvArr* srcarr, CvArr* dstarr )
{
cv::equalizeHist(cv::cvarrToMat(srcarr), cv::cvarrToMat(dstarr));
}
void cv::equalizeHist( InputArray _src, OutputArray _dst )
{
Mat src = _src.getMat();
CV_Assert( src.type() == CV_8UC1 );
_dst.create( src.size(), src.type() );
Mat dst = _dst.getMat();
CvMat _csrc = src, _cdst = dst;
cvEqualizeHist( &_csrc, &_cdst );
if(src.empty())
return;
#ifdef HAVE_TBB
tbb::mutex histogramLockInstance;
EqualizeHistCalcHist_Invoker::MutextPtr histogramLock = &histogramLockInstance;
#else
EqualizeHistCalcHist_Invoker::MutextPtr histogramLock = 0;
#endif
const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ;
int hist[hist_sz] = {0,};
int lut[hist_sz];
EqualizeHistCalcHist_Invoker calcBody(src, hist, histogramLock);
EqualizeHistLut_Invoker lutBody(src, dst, lut);
cv::BlockedRange heightRange(0, src.rows);
if(EqualizeHistCalcHist_Invoker::isWorthParallel(src))
parallel_for(heightRange, calcBody);
else
calcBody(heightRange);
int i = 0;
while (!hist[i]) ++i;
int total = (int)src.total();
if (hist[i] == total)
{
dst.setTo(i);
return;
}
float scale = (hist_sz - 1.f)/(total - hist[i]);
int sum = 0;
for (lut[i++] = 0; i < hist_sz; ++i)
{
sum += hist[i];
lut[i] = saturate_cast<uchar>(sum * scale);
}
if(EqualizeHistLut_Invoker::isWorthParallel(src))
parallel_for(heightRange, lutBody);
else
lutBody(heightRange);
}
/* Implementation of RTTI and Generic Functions for CvHistogram */

@ -3,6 +3,19 @@
import os, sys, re, string, glob
from optparse import OptionParser
# Black list for classes and methods that does not implemented in Java API
# Created to exclude referencies to them in @see tag
JAVADOC_ENTITY_BLACK_LIST = set(["org.opencv.core.Core#abs", \
"org.opencv.core.Core#theRNG", \
"org.opencv.core.Core#extractImageCOI", \
"org.opencv.core.PCA", \
"org.opencv.core.SVD", \
"org.opencv.core.RNG", \
"org.opencv.imgproc.Imgproc#createMorphologyFilter", \
"org.opencv.imgproc.Imgproc#createLinearFilter", \
"org.opencv.imgproc.Imgproc#createSeparableLinearFilter", \
"org.opencv.imgproc.FilterEngine"])
class JavadocGenerator(object):
def __init__(self, definitions = {}, modules= [], javadoc_marker = "//javadoc:"):
self.definitions = definitions
@ -214,9 +227,9 @@ class JavadocGenerator(object):
for see in decl["seealso"]:
seedecl = self.definitions.get(see,None)
if seedecl:
doc += prefix + " * @see " + self.getJavaName(seedecl, "#") + "\n"
else:
doc += prefix + " * @see " + see.replace("::",".") + "\n"
javadoc_name = self.getJavaName(seedecl, "#")
if (javadoc_name not in JAVADOC_ENTITY_BLACK_LIST):
doc += prefix + " * @see " + javadoc_name + "\n"
prefix = " *\n"
#doc += prefix + " * File: " + decl["file"] + " (line " + str(decl["line"]) + ")\n"

@ -344,7 +344,7 @@ public abstract class CameraBridgeViewBase extends SurfaceView implements Surfac
* @param supportedSizes
* @param surfaceWidth
* @param surfaceHeight
* @return
* @return optimal frame size
*/
protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;

@ -31,7 +31,7 @@ public class OpenCVLoader
* @param Version OpenCV library version.
* @param AppContext application context for connecting to the service.
* @param Callback object, that implements LoaderCallbackInterface for handling the connection status.
* @return Returns true if initialization of OpenCV is successful.
* @return Returns true if initialization of OpenCV is successful.
*/
public static boolean initAsync(String Version, Context AppContext,
LoaderCallbackInterface Callback)

@ -64,11 +64,11 @@ int CV_SLMLTest::run_test_case( int testCaseIdx )
if( code == cvtest::TS::OK )
{
get_error( testCaseIdx, CV_TEST_ERROR, &test_resps1 );
fname1 = tempfile();
fname1 = tempfile(".yml.gz");
save( fname1.c_str() );
load( fname1.c_str() );
get_error( testCaseIdx, CV_TEST_ERROR, &test_resps2 );
fname2 = tempfile();
fname2 = tempfile(".yml.gz");
save( fname2.c_str() );
}
else

@ -14,11 +14,7 @@ PERF_TEST_P(ImageName_MinSize, CascadeClassifierLBPFrontalFace,
testing::Combine(testing::Values( std::string("cv/shared/lena.png"),
std::string("cv/shared/1_itseez-0000289.png"),
std::string("cv/shared/1_itseez-0000492.png"),
std::string("cv/shared/1_itseez-0000573.png"),
std::string("cv/shared/1_itseez-0000892.png"),
std::string("cv/shared/1_itseez-0001238.png"),
std::string("cv/shared/1_itseez-0001438.png"),
std::string("cv/shared/1_itseez-0002524.png")),
std::string("cv/shared/1_itseez-0000573.png")),
testing::Values(24, 30, 40, 50, 60, 70, 80, 90)
)
)

@ -433,8 +433,8 @@
// Defines this to true iff Google Test can use POSIX regular expressions.
#ifndef GTEST_HAS_POSIX_RE
# if GTEST_OS_LINUX_ANDROID
// On Android, <regex.h> is only available starting with Gingerbread.
# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9)
// On Android, <regex.h> is only available starting with Froyo.
# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 8)
# else
# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS)
#endif

@ -1340,6 +1340,7 @@ GTEST_API_ bool IsAsciiWhiteSpace(char ch);
GTEST_API_ bool IsAsciiWordChar(char ch);
GTEST_API_ bool IsValidEscape(char ch);
GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch);
GTEST_API_ std::string FormatRegexSyntaxError(const char* regex, int index);
GTEST_API_ bool ValidateRegex(const char* regex);
GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str);
GTEST_API_ bool MatchRepetitionAndRegexAtHead(

@ -305,23 +305,25 @@ double Regression::getElem(cv::Mat& m, int y, int x, int cn)
void Regression::write(cv::Mat m)
{
if (!m.empty() && m.dims < 2) return;
double min, max;
cv::minMaxLoc(m, &min, &max);
cv::minMaxIdx(m, &min, &max);
write() << "min" << min << "max" << max;
write() << "last" << "{" << "x" << m.cols-1 << "y" << m.rows-1
<< "val" << getElem(m, m.rows-1, m.cols-1, m.channels()-1) << "}";
write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
<< "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";
int x, y, cn;
x = regRNG.uniform(0, m.cols);
y = regRNG.uniform(0, m.rows);
x = regRNG.uniform(0, m.size.p[1]);
y = regRNG.uniform(0, m.size.p[0]);
cn = regRNG.uniform(0, m.channels());
write() << "rng1" << "{" << "x" << x << "y" << y;
if(cn > 0) write() << "cn" << cn;
write() << "val" << getElem(m, y, x, cn) << "}";
x = regRNG.uniform(0, m.cols);
y = regRNG.uniform(0, m.rows);
x = regRNG.uniform(0, m.size.p[1]);
y = regRNG.uniform(0, m.size.p[0]);
cn = regRNG.uniform(0, m.channels());
write() << "rng2" << "{" << "x" << x << "y" << y;
if (cn > 0) write() << "cn" << cn;
@ -339,8 +341,10 @@ static double evalEps(double expected, double actual, double _eps, ERROR_TYPE er
void Regression::verify(cv::FileNode node, cv::Mat actual, double _eps, std::string argname, ERROR_TYPE err)
{
if (!actual.empty() && actual.dims < 2) return;
double actual_min, actual_max;
cv::minMaxLoc(actual, &actual_min, &actual_max);
cv::minMaxIdx(actual, &actual_min, &actual_max);
double expect_min = (double)node["min"];
double eps = evalEps(expect_min, actual_min, _eps, err);
@ -353,12 +357,12 @@ void Regression::verify(cv::FileNode node, cv::Mat actual, double _eps, std::str
<< argname << " has unexpected maximal value" << std::endl;
cv::FileNode last = node["last"];
double actual_last = getElem(actual, actual.rows - 1, actual.cols - 1, actual.channels() - 1);
double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
int expect_cols = (int)last["x"] + 1;
int expect_rows = (int)last["y"] + 1;
ASSERT_EQ(expect_cols, actual.cols)
ASSERT_EQ(expect_cols, actual.size.p[1])
<< argname << " has unexpected number of columns" << std::endl;
ASSERT_EQ(expect_rows, actual.rows)
ASSERT_EQ(expect_rows, actual.size.p[0])
<< argname << " has unexpected number of rows" << std::endl;
double expect_last = (double)last["val"];
@ -372,6 +376,8 @@ void Regression::verify(cv::FileNode node, cv::Mat actual, double _eps, std::str
int cn1 = rng1["cn"];
double expect_rng1 = (double)rng1["val"];
// it is safe to use x1 and y1 without checks here because we have already
// verified that mat size is the same as recorded
double actual_rng1 = getElem(actual, y1, x1, cn1);
eps = evalEps(expect_rng1, actual_rng1, _eps, err);
@ -490,7 +496,7 @@ void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERR
std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
double max;
cv::minMaxLoc(diff.reshape(1), 0, &max);
cv::minMaxIdx(diff.reshape(1), 0, &max);
FAIL() << " Absolute difference (=" << max << ") between argument \""
<< node.name() << "[" << idx << "]\" and expected value is greater than " << eps;
@ -544,7 +550,7 @@ void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERR
std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
double max;
cv::minMaxLoc(diff.reshape(1), 0, &max);
cv::minMaxIdx(diff.reshape(1), 0, &max);
FAIL() << " Difference (=" << max << ") between argument1 \"" << node.name()
<< "\" and expected value is greater than " << eps;
@ -1153,12 +1159,17 @@ void TestBase::RunPerfTestBody()
catch(cv::Exception e)
{
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what();
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws cv::Exception:\n " << e.what();
}
catch(std::exception e)
{
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws std::exception:\n " << e.what();
}
catch(...)
{
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws.";
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws...";
}
}

@ -13,12 +13,14 @@ import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
@ -29,7 +31,11 @@ public class Sample5CameraControl extends Activity implements CvCameraViewListen
private static final String TAG = "OCVSample::Activity";
private SampleJavaCameraView mOpenCvCameraView;
private List<Size> mResolutionList;
private MenuItem[] mEffectMenuItems;
private SubMenu mColorEffectsMenu;
private MenuItem[] mResolutionMenuItems;
private SubMenu mResolutionMenu;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
@ -109,22 +115,50 @@ public class Sample5CameraControl extends Activity implements CvCameraViewListen
return true;
}
mColorEffectsMenu = menu.addSubMenu("Color Effect");
mEffectMenuItems = new MenuItem[effects.size()];
int idx = 0;
ListIterator<String> itr = effects.listIterator();
while(itr.hasNext()) {
String element = itr.next();
mEffectMenuItems[idx] = menu.add(element);
ListIterator<String> effectItr = effects.listIterator();
while(effectItr.hasNext()) {
String element = effectItr.next();
mEffectMenuItems[idx] = mColorEffectsMenu.add(1, idx, Menu.NONE, element);
idx++;
}
mResolutionMenu = menu.addSubMenu("Resolution");
mResolutionList = mOpenCvCameraView.getResolutionList();
mResolutionMenuItems = new MenuItem[mResolutionList.size()];
ListIterator<Size> resolutionItr = mResolutionList.listIterator();
idx = 0;
while(resolutionItr.hasNext()) {
Size element = resolutionItr.next();
mResolutionMenuItems[idx] = mResolutionMenu.add(2, idx, Menu.NONE,
Integer.valueOf(element.width).toString() + "x" + Integer.valueOf(element.height).toString());
idx++;
}
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
if (item.getGroupId() == 1)
{
mOpenCvCameraView.setEffect((String) item.getTitle());
Toast.makeText(this, mOpenCvCameraView.getEffect(), Toast.LENGTH_SHORT).show();
}
else if (item.getGroupId() == 2)
{
int id = item.getItemId();
Size resolution = mResolutionList.get(id);
mOpenCvCameraView.setResolution(resolution);
resolution = mOpenCvCameraView.getResolution();
String caption = Integer.valueOf(resolution.width).toString() + "x" + Integer.valueOf(resolution.height).toString();
Toast.makeText(this, caption, Toast.LENGTH_SHORT).show();
}
return true;
}

@ -10,6 +10,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
@ -39,6 +40,21 @@ public class SampleJavaCameraView extends JavaCameraView {
mCamera.setParameters(params);
}
public List<Size> getResolutionList() {
return mCamera.getParameters().getSupportedPreviewSizes();
}
public void setResolution(Size resolution) {
disconnectCamera();
mMaxHeight = resolution.height;
mMaxWidth = resolution.width;
connectCamera(getWidth(), getHeight());
}
public Size getResolution() {
return mCamera.getParameters().getPreviewSize();
}
public void takePicture(final String fileName) {
Log.i(TAG, "Tacking picture");
PictureCallback callback = new PictureCallback() {

@ -55,7 +55,7 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND)
endforeach()
endif()
if (NOT WIN32)
if (INSTALL_C_EXAMPLES AND NOT WIN32)
file(GLOB install_list *.c *.cpp *.jpg *.png *.data makefile.* build_all.sh *.dsp *.cmd )
install(FILES ${install_list}
DESTINATION share/opencv/samples/${project}

Loading…
Cancel
Save