From 33b349d21950108fbf6405028c5285c3e02d2296 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Wed, 19 Feb 2014 23:56:48 -0600 Subject: [PATCH 01/11] Added tinythread x-platform multithreading library --- src/openalpr/tinythread/fast_mutex.h | 248 +++++++++ src/openalpr/tinythread/tinythread.cpp | 303 +++++++++++ src/openalpr/tinythread/tinythread.h | 714 +++++++++++++++++++++++++ 3 files changed, 1265 insertions(+) create mode 100644 src/openalpr/tinythread/fast_mutex.h create mode 100644 src/openalpr/tinythread/tinythread.cpp create mode 100644 src/openalpr/tinythread/tinythread.h diff --git a/src/openalpr/tinythread/fast_mutex.h b/src/openalpr/tinythread/fast_mutex.h new file mode 100644 index 0000000..4d4b7cc --- /dev/null +++ b/src/openalpr/tinythread/fast_mutex.h @@ -0,0 +1,248 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _FAST_MUTEX_H_ +#define _FAST_MUTEX_H_ + +/// @file + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Check if we can support the assembly language level implementation (otherwise +// revert to the system API) +#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \ + (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \ + (defined(__GNUC__) && (defined(__ppc__))) + #define _FAST_MUTEX_ASM_ +#else + #define _FAST_MUTEX_SYS_ +#endif + +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #ifdef _FAST_MUTEX_ASM_ + #include + #else + #include + #endif +#endif + +namespace tthread { + +/// Fast mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. It is similar to the tthread::mutex class, +/// but instead of using system level functions, it is implemented as an atomic +/// spin lock with very low CPU overhead. +/// +/// The \c fast_mutex class is NOT compatible with the \c condition_variable +/// class (however, it IS compatible with the \c lock_guard class). It should +/// also be noted that the \c fast_mutex class typically does not provide +/// as accurate thread scheduling as a the standard \c mutex class does. +/// +/// Because of the limitations of the class, it should only be used in +/// situations where the mutex needs to be locked/unlocked very frequently. +/// +/// @note The "fast" version of this class relies on inline assembler language, +/// which is currently only supported for 32/64-bit Intel x86/AMD64 and +/// PowerPC architectures on a limited number of compilers (GNU g++ and MS +/// Visual C++). +/// For other architectures/compilers, system functions are used instead. +class fast_mutex { + public: + /// Constructor. +#if defined(_FAST_MUTEX_ASM_) + fast_mutex() : mLock(0) {} +#else + fast_mutex() + { + #if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_init(&mHandle, NULL); + #endif + } +#endif + +#if !defined(_FAST_MUTEX_ASM_) + /// Destructor. + ~fast_mutex() + { + #if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_destroy(&mHandle); + #endif + } +#endif + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until \c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_FAST_MUTEX_ASM_) + bool gotLock; + do { + gotLock = try_lock(); + if(!gotLock) + { + #if defined(_TTHREAD_WIN32_) + Sleep(0); + #elif defined(_TTHREAD_POSIX_) + sched_yield(); + #endif + } + } while(!gotLock); +#else + #if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_lock(&mHandle); + #endif +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return \c true if the lock was acquired, or \c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_FAST_MUTEX_ASM_) + int oldLock; + #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm volatile ( + "movl $1,%%eax\n\t" + "xchg %%eax,%0\n\t" + "movl %%eax,%1\n\t" + : "=m" (mLock), "=m" (oldLock) + : + : "%eax", "memory" + ); + #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + int *ptrLock = &mLock; + __asm { + mov eax,1 + mov ecx,ptrLock + xchg eax,[ecx] + mov oldLock,eax + } + #elif defined(__GNUC__) && (defined(__ppc__)) + int newLock = 1; + asm volatile ( + "\n1:\n\t" + "lwarx %0,0,%1\n\t" + "cmpwi 0,%0,0\n\t" + "bne- 2f\n\t" + "stwcx. %2,0,%1\n\t" + "bne- 1b\n\t" + "isync\n" + "2:\n\t" + : "=&r" (oldLock) + : "r" (&mLock), "r" (newLock) + : "cr0", "memory" + ); + #endif + return (oldLock == 0); +#else + #if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; + #elif defined(_TTHREAD_POSIX_) + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; + #endif +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_FAST_MUTEX_ASM_) + #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm volatile ( + "movl $0,%%eax\n\t" + "xchg %%eax,%0\n\t" + : "=m" (mLock) + : + : "%eax", "memory" + ); + #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + int *ptrLock = &mLock; + __asm { + mov eax,0 + mov ecx,ptrLock + xchg eax,[ecx] + } + #elif defined(__GNUC__) && (defined(__ppc__)) + asm volatile ( + "sync\n\t" // Replace with lwsync where possible? + : : : "memory" + ); + mLock = 0; + #endif +#else + #if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_unlock(&mHandle); + #endif +#endif + } + + private: +#if defined(_FAST_MUTEX_ASM_) + int mLock; +#else + #if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_t mHandle; + #endif +#endif +}; + +} + +#endif // _FAST_MUTEX_H_ + diff --git a/src/openalpr/tinythread/tinythread.cpp b/src/openalpr/tinythread/tinythread.cpp new file mode 100644 index 0000000..690ecee --- /dev/null +++ b/src/openalpr/tinythread/tinythread.cpp @@ -0,0 +1,303 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) + #include + #include +#elif defined(_TTHREAD_WIN32_) + #include +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) + #define _CONDITION_EVENT_ONE 0 + #define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::condition_variable() : mWaitersCount(0) +{ + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::~condition_variable() +{ + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::_wait() +{ + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if(lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_one() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_all() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) +static thread::id _pthread_t_to_ID(const pthread_t &aHandle) +{ + static mutex idMapLock; + static std::map idMap; + static unsigned long int idCount(1); + + lock_guard guard(idMapLock); + if(idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); +} +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). +struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. +}; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) +unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +void * thread::wrapper_function(void * aArg) +#endif +{ + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try + { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } + catch(...) + { + // Uncaught exceptions will terminate the application (default behavior + // according to C++11) + std::terminate(); + } + + // The thread is no longer executing + lock_guard guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; +} + +thread::thread(void (*aFunction)(void *), void * aArg) +{ + // Serialize access to this thread structure + lock_guard guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if(!mHandle) + { + mNotAThread = true; + delete ti; + } +} + +thread::~thread() +{ + if(joinable()) + std::terminate(); +} + +void thread::join() +{ + if(joinable()) + { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } +} + +bool thread::joinable() const +{ + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; +} + +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + +thread::id thread::get_id() const +{ + if(!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif +} + +unsigned thread::hardware_concurrency() +{ +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif +} + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + +thread::id this_thread::get_id() +{ +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif +} + +} diff --git a/src/openalpr/tinythread/tinythread.h b/src/openalpr/tinythread/tinythread.h new file mode 100644 index 0000000..aed7b58 --- /dev/null +++ b/src/openalpr/tinythread/tinythread.h @@ -0,0 +1,714 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++11 standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++11 standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #include + #include + #include + #include +#endif + +// Generic includes +#include + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 1 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++11 compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) + #define _TTHREAD_CPP11_ +#endif + +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP11_PARTIAL_ + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the @c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define thread_local __thread + #else + #define thread_local __declspec(thread) + #endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex +class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while(mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if(ret && mAlreadyLocked) + { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex +class recursive_mutex { + public: + /// Constructor. + recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ counter; +/// } +/// @endcode + +template +class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type &aMutex) + { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() + { + if(mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; +}; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode +class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() + { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() + { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template + inline void wait(_mutexT &aMutex) + { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() + { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() + { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif +}; + + +/// Thread class. +class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a @c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a @c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// void fun(void * arg) + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, @c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// @c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. + inline native_handle_type native_handle() + { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif +}; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() +class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id& aId) : mId(aId.mId) {}; + + inline id & operator=(const id &aId) + { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id &aId1, const id &aId2) + { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id &aId1, const id &aId2) + { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id &aId1, const id &aId2) + { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id &aId1, const id &aId2) + { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id &aId1, const id &aId2) + { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id &aId1, const id &aId2) + { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream& operator <<(std::ostream &os, const id &obj) + { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; +}; + + +// Related to - minimal to be able to support chrono. +typedef long long __intmax_t; + +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. +template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { return double(N) / double(D); } +}; + +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. +namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement @c this_thread::sleep_for(). + template > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template + explicit duration(const _Rep2& r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const + { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. +} + +/// The namespace @c this_thread provides methods for dealing with the +/// calling thread. +namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() + { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template void sleep_for(const chrono::duration<_Rep, _Period>& aTime) + { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } +} + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_ From cb6e2667544ee2ab804c1afc75030bee48f489cb Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 00:02:02 -0600 Subject: [PATCH 02/11] Added cmake config for tinythread --- src/openalpr/CMakeLists.txt | 3 ++- src/openalpr/tinythread/CMakeLists.txt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/openalpr/tinythread/CMakeLists.txt diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index 79097cc..06d7650 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -31,4 +31,5 @@ add_library(openalpr ${lpr_source_files}) add_subdirectory(simpleini) -add_subdirectory(support) \ No newline at end of file +add_subdirectory(support) +add_subdirectory(tinythread) \ No newline at end of file diff --git a/src/openalpr/tinythread/CMakeLists.txt b/src/openalpr/tinythread/CMakeLists.txt new file mode 100644 index 0000000..435c2de --- /dev/null +++ b/src/openalpr/tinythread/CMakeLists.txt @@ -0,0 +1,6 @@ +set(tinythread_source_files + tinythread.cpp +) + + +add_library(tinythread ${tinythread_source_files}) \ No newline at end of file From 35820e3b0f419340ec87943764156b4eea22a033 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 11:53:42 -0600 Subject: [PATCH 03/11] Added preference for multithreading_cores (controls number of threads spawned during recognition) --- runtime_data/openalpr.conf | 2 ++ src/openalpr/config.cpp | 2 ++ src/openalpr/config.h | 1 + 3 files changed, 5 insertions(+) diff --git a/runtime_data/openalpr.conf b/runtime_data/openalpr.conf index dd9a616..aaeaed6 100644 --- a/runtime_data/openalpr.conf +++ b/runtime_data/openalpr.conf @@ -8,6 +8,8 @@ max_plate_width_percent = 100 max_plate_height_percent = 100 opencl_enabled = 0 +multithreading_cores = 1 + ocr_min_font_point = 6 diff --git a/src/openalpr/config.cpp b/src/openalpr/config.cpp index 282719e..31072db 100644 --- a/src/openalpr/config.cpp +++ b/src/openalpr/config.cpp @@ -72,6 +72,8 @@ void Config::loadValues(string country) { opencl_enabled = getBoolean("common", "opencl_enabled", false); + multithreading_cores = getInt("common", "multhreading_cores", 1); + maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100); maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100); diff --git a/src/openalpr/config.h b/src/openalpr/config.h index 34a5229..95841a0 100644 --- a/src/openalpr/config.h +++ b/src/openalpr/config.h @@ -44,6 +44,7 @@ class Config string country; bool opencl_enabled; + int multithreading_cores; float maxPlateWidthPercent; float maxPlateHeightPercent; From ce907f5c34c8627c07f73d9a470de11127a0a116 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 13:20:11 -0600 Subject: [PATCH 04/11] Fixed typo in "multithreading_cores" preference --- src/openalpr/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openalpr/config.cpp b/src/openalpr/config.cpp index 31072db..ecfa6b6 100644 --- a/src/openalpr/config.cpp +++ b/src/openalpr/config.cpp @@ -72,7 +72,7 @@ void Config::loadValues(string country) { opencl_enabled = getBoolean("common", "opencl_enabled", false); - multithreading_cores = getInt("common", "multhreading_cores", 1); + multithreading_cores = getInt("common", "multithreading_cores", 1); maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100); maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100); From 0386bba2993160e302a261b03cd1906dff9015e7 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 13:48:26 -0600 Subject: [PATCH 05/11] Fixed multithreading library in CMakeLists --- src/misc_utilities/CMakeLists.txt | 12 +++++++++++- src/openalpr/CMakeLists.txt | 8 ++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/misc_utilities/CMakeLists.txt b/src/misc_utilities/CMakeLists.txt index f2786b9..fd675f9 100644 --- a/src/misc_utilities/CMakeLists.txt +++ b/src/misc_utilities/CMakeLists.txt @@ -24,6 +24,7 @@ ADD_EXECUTABLE( benchmark benchmark.cpp ) TARGET_LINK_LIBRARIES(benchmark openalpr support + tinythread ${OpenCV_LIBS} tesseract ) @@ -35,4 +36,13 @@ TARGET_LINK_LIBRARIES(prepcharsfortraining ${OpenCV_LIBS} ) - \ No newline at end of file + + +ADD_EXECUTABLE( findnegatives findnegatives.cpp ) +TARGET_LINK_LIBRARIES(findnegatives + openalpr + support + tinythread + ${OpenCV_LIBS} + tesseract + ) \ No newline at end of file diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index 06d7650..6a27d58 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -26,10 +26,10 @@ set(lpr_source_files ) -add_library(openalpr ${lpr_source_files}) - - add_subdirectory(simpleini) add_subdirectory(support) -add_subdirectory(tinythread) \ No newline at end of file +add_subdirectory(tinythread) + + +add_library(openalpr ${lpr_source_files}) \ No newline at end of file From b5db3ea78aa2fcf75a7cc8957821709b92ec1481 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 13:48:47 -0600 Subject: [PATCH 06/11] Added multithreading functions to detection --- src/openalpr/alpr_impl.cpp | 147 ++++++++++++++++++++++++++----------- src/openalpr/alpr_impl.h | 83 ++++++++++++++++++++- 2 files changed, 187 insertions(+), 43 deletions(-) diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index 2eee2d8..6866211 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -19,6 +19,8 @@ #include "alpr_impl.h" +void plateAnalysisThread(void* arg); + AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) { @@ -73,21 +75,96 @@ std::vector AlprImpl::recognize(cv::Mat img) { timespec startTime; getTime(&startTime); - - vector response; - + + // Find all the candidate regions vector plateRegions = plateDetector->detect(img); + // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) + int numThreads = config->multithreading_cores; + if (numThreads > tthread::thread::hardware_concurrency()) + numThreads = tthread::thread::hardware_concurrency(); + if (numThreads <= 0) + numThreads = 1; - // Recognize. - - for (int i = 0; i < plateRegions.size(); i++) + + PlateDispatcher dispatcher(plateRegions, &img, + config, stateIdentifier, ocr, + topN, detectRegion, defaultRegion); + + // Spawn n threads to process all of the candidate regions and recognize + list threads; + for (int i = 0; i < numThreads; i++) { + tthread::thread * t = new tthread::thread(plateAnalysisThread, (void *) &dispatcher); + threads.push_back(t); + } + + // Wait for all threads to finish + for(list::iterator i = threads.begin(); i != threads.end(); ++ i) + { + tthread::thread* t = *i; + t->join(); + delete t; + } + + if (config->debugTiming) + { + timespec endTime; + getTime(&endTime); + cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl; + } + + if (config->debugGeneral && config->debugShowImages) + { + displayImage(config, "Main Image", img); + // Pause indefinitely until they press a key + while ((char) cv::waitKey(50) == -1) + {} + } + + // if (config->debugGeneral) +// { +// rectangle(img, plateRegion, Scalar(0, 255, 0), 2); +// for (int z = 0; z < 4; z++) +// line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2); +// } + +// if (config->debugGeneral) +// rectangle(img, plateRegion, Scalar(0, 0, 255), 2); + + return dispatcher.getRecognitionResults(); +} + +void plateAnalysisThread(void* arg) +{ + PlateDispatcher* dispatcher = (PlateDispatcher*) arg; + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " Initialized" << endl; + + int loop_count = 0; + while (true) + { + + if (dispatcher->hasPlate() == false) + break; + + // Synchronized section + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " loop " << ++loop_count << endl; + + // Get a single plate region from the queue + Rect plateRegion = dispatcher->nextPlate(); + + Mat img = dispatcher->getImageCopy(); + + + // Parallel section + timespec platestarttime; getTime(&platestarttime); - LicensePlateCandidate lp(img, plateRegions[i], config); + LicensePlateCandidate lp(img, plateRegion, dispatcher->config); lp.recognize(); @@ -95,7 +172,7 @@ std::vector AlprImpl::recognize(cv::Mat img) if (lp.confidence > 10) { AlprResult plateResult; - plateResult.region = defaultRegion; + plateResult.region = dispatcher->defaultRegion; plateResult.regionConfidence = 0; for (int pointidx = 0; pointidx < 4; pointidx++) @@ -104,37 +181,37 @@ std::vector AlprImpl::recognize(cv::Mat img) plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y; } - if (detectRegion) + if (dispatcher->detectRegion) { char statecode[4]; - plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegions[i], statecode); + plateResult.regionConfidence = dispatcher->stateIdentifier->recognize(img, plateRegion, statecode); if (plateResult.regionConfidence > 0) { plateResult.region = statecode; } - } + } - ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); + dispatcher->ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); - ocr->postProcessor->analyze(plateResult.region, topN); + dispatcher->ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); //plateResult.characters = ocr->postProcessor->bestChars; - const vector ppResults = ocr->postProcessor->getResults(); + const vector ppResults = dispatcher->ocr->postProcessor->getResults(); int bestPlateIndex = 0; for (int pp = 0; pp < ppResults.size(); pp++) { - if (pp >= topN) + if (pp >= dispatcher->topN) break; // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate) bestPlateIndex = pp; - if (ppResults[pp].letters.size() >= config->postProcessMinCharacters && - ppResults[pp].letters.size() <= config->postProcessMaxCharacters) + if (ppResults[pp].letters.size() >= dispatcher->config->postProcessMinCharacters && + ppResults[pp].letters.size() <= dispatcher->config->postProcessMaxCharacters) { AlprPlate aplate; aplate.characters = ppResults[pp].letters; @@ -153,40 +230,26 @@ std::vector AlprImpl::recognize(cv::Mat img) plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime); if (plateResult.result_count > 0) - response.push_back(plateResult); - - if (config->debugGeneral) { - rectangle(img, plateRegions[i], Scalar(0, 255, 0), 2); - for (int z = 0; z < 4; z++) - line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2); + // Synchronized section + dispatcher->addResult(plateResult); + } } - else - { - if (config->debugGeneral) - rectangle(img, plateRegions[i], Scalar(0, 0, 255), 2); - } - } + if (dispatcher->config->debugTiming) + { + timespec plateEndTime; + getTime(&plateEndTime); + cout << "Thread: " << tthread::this_thread::get_id() << " Finished loop " << loop_count << " in " << diffclock(platestarttime, plateEndTime) << "ms." << endl; + } - if (config->debugTiming) - { - timespec endTime; - getTime(&endTime); - cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl; } - if (config->debugGeneral && config->debugShowImages) - { - displayImage(config, "Main Image", img); - // Pause indefinitely until they press a key - while ((char) cv::waitKey(50) == -1) - {} - } - return response; + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " Complete" << endl; } string AlprImpl::toJson(const vector< AlprResult > results) diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h index 9960901..8fed548 100644 --- a/src/openalpr/alpr_impl.h +++ b/src/openalpr/alpr_impl.h @@ -21,6 +21,8 @@ #ifndef ALPRIMPL_H #define ALPRIMPL_H +#include + #include "alpr.h" #include "config.h" @@ -34,8 +36,8 @@ #include #include "opencv2/ocl/ocl.hpp" - +#include "tinythread/tinythread.h" #define DEFAULT_TOPN 25 #define DEFAULT_DETECT_REGION false @@ -70,6 +72,85 @@ class AlprImpl std::string defaultRegion; cJSON* createJsonObj(const AlprResult* result); + + +}; + +class PlateDispatcher +{ + public: + PlateDispatcher(vector plateRegions, cv::Mat* image, + Config* config, + StateIdentifier* stateIdentifier, + OCR* ocr, + int topN, bool detectRegion, std::string defaultRegion) + { + this->plateRegions = plateRegions; + this->frame = image; + + this->config = config; + this->stateIdentifier = stateIdentifier; + this->ocr = ocr; + this->topN = topN; + this->detectRegion = detectRegion; + this->defaultRegion = defaultRegion; + } + + cv::Mat getImageCopy() + { + tthread::lock_guard guard(mMutex); + + Mat img(this->frame->size(), this->frame->type()); + this->frame->copyTo(img); + + return img; + } + + bool hasPlate() + { + bool plateAvailable; + mMutex.lock(); + plateAvailable = plateRegions.size() > 0; + mMutex.unlock(); + return plateAvailable; + } + Rect nextPlate() + { + tthread::lock_guard guard(mMutex); + + Rect plateRegion = plateRegions[plateRegions.size() - 1]; + plateRegions.pop_back(); + + return plateRegion; + } + + void addResult(AlprResult recognitionResult) + { + tthread::lock_guard guard(mMutex); + recognitionResults.push_back(recognitionResult); + } + + vector getRecognitionResults() + { + return recognitionResults; + } + + + StateIdentifier* stateIdentifier; + OCR* ocr; + Config* config; + + int topN; + bool detectRegion; + std::string defaultRegion; + + private: + + tthread::mutex mMutex; + cv::Mat* frame; + vector plateRegions; + vector recognitionResults; + }; #endif // ALPRIMPL_H \ No newline at end of file From bb39631acbaf941eb0810428b279213ed3209bef Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 15:59:35 -0600 Subject: [PATCH 07/11] Explicitly set OpenCV functions to single threading --- src/openalpr/alpr_impl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index 6866211..5c405a7 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -29,6 +29,8 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) stateIdentifier = new StateIdentifier(config); ocr = new OCR(config); + setNumThreads(0); + this->detectRegion = DEFAULT_DETECT_REGION; this->topN = DEFAULT_TOPN; this->defaultRegion = ""; From 21acb7084a29a9f542b632d1d5d9f5f0c94acf2d Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 20 Feb 2014 17:32:50 -0600 Subject: [PATCH 08/11] Compiling tinythread as part of the openalpr source (rather than a separate library) --- src/misc_utilities/CMakeLists.txt | 2 -- src/openalpr/CMakeLists.txt | 2 +- src/openalpr/tinythread/CMakeLists.txt | 6 ------ 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 src/openalpr/tinythread/CMakeLists.txt diff --git a/src/misc_utilities/CMakeLists.txt b/src/misc_utilities/CMakeLists.txt index fd675f9..d9ac3a7 100644 --- a/src/misc_utilities/CMakeLists.txt +++ b/src/misc_utilities/CMakeLists.txt @@ -24,7 +24,6 @@ ADD_EXECUTABLE( benchmark benchmark.cpp ) TARGET_LINK_LIBRARIES(benchmark openalpr support - tinythread ${OpenCV_LIBS} tesseract ) @@ -42,7 +41,6 @@ ADD_EXECUTABLE( findnegatives findnegatives.cpp ) TARGET_LINK_LIBRARIES(findnegatives openalpr support - tinythread ${OpenCV_LIBS} tesseract ) \ No newline at end of file diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index 6a27d58..3cc4923 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -23,13 +23,13 @@ set(lpr_source_files verticalhistogram.cpp trex.c cjson.c + tinythread/tinythread.cpp ) add_subdirectory(simpleini) add_subdirectory(support) -add_subdirectory(tinythread) add_library(openalpr ${lpr_source_files}) \ No newline at end of file diff --git a/src/openalpr/tinythread/CMakeLists.txt b/src/openalpr/tinythread/CMakeLists.txt deleted file mode 100644 index 435c2de..0000000 --- a/src/openalpr/tinythread/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(tinythread_source_files - tinythread.cpp -) - - -add_library(tinythread ${tinythread_source_files}) \ No newline at end of file From 29cb8fab213bfbc16b6a9674f6244d780f425533 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 21 Feb 2014 04:04:18 -0600 Subject: [PATCH 09/11] Using multiple instances of OCR and state recognizer for multiple threads --- src/openalpr/alpr_impl.cpp | 71 ++++++++++++++++++++++++++++++-------- src/openalpr/alpr_impl.h | 30 +++++++++++----- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index 5c405a7..51d5dfa 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -26,8 +26,17 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) { config = new Config(country, runtimeDir); plateDetector = new RegionDetector(config); - stateIdentifier = new StateIdentifier(config); - ocr = new OCR(config); + + int numThreads = getNumThreads(); + + for (int i = 0; i < numThreads; i++) + { + StateIdentifier* stateIdentifier = new StateIdentifier(config); + OCR* ocr = new OCR(config); + + stateIdentifiers.push_back(stateIdentifier); + ocrs.push_back(ocr); + } setNumThreads(0); @@ -68,10 +77,28 @@ AlprImpl::~AlprImpl() { delete config; delete plateDetector; - delete stateIdentifier; - delete ocr; + + for (int i = 0; i < stateIdentifiers.size(); i++) + { + delete stateIdentifiers[i]; + delete ocrs[i]; + } + + stateIdentifiers.clear(); + ocrs.clear(); } +int AlprImpl::getNumThreads() +{ + // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) + int numThreads = config->multithreading_cores; + if (numThreads > tthread::thread::hardware_concurrency()) + numThreads = tthread::thread::hardware_concurrency(); + if (numThreads <= 0) + numThreads = 1; + + return numThreads; +} std::vector AlprImpl::recognize(cv::Mat img) { @@ -83,15 +110,11 @@ std::vector AlprImpl::recognize(cv::Mat img) vector plateRegions = plateDetector->detect(img); // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) - int numThreads = config->multithreading_cores; - if (numThreads > tthread::thread::hardware_concurrency()) - numThreads = tthread::thread::hardware_concurrency(); - if (numThreads <= 0) - numThreads = 1; + int numThreads = getNumThreads(); PlateDispatcher dispatcher(plateRegions, &img, - config, stateIdentifier, ocr, + config, stateIdentifiers, ocrs, topN, detectRegion, defaultRegion); // Spawn n threads to process all of the candidate regions and recognize @@ -141,13 +164,28 @@ std::vector AlprImpl::recognize(cv::Mat img) void plateAnalysisThread(void* arg) { PlateDispatcher* dispatcher = (PlateDispatcher*) arg; + + timespec syncstarttime; + getTime(&syncstarttime); + + int threadID = dispatcher->getUniqueThreadId(); + Mat img = dispatcher->getImageCopy(); + if (dispatcher->config->debugGeneral) cout << "Thread: " << tthread::this_thread::get_id() << " Initialized" << endl; + + if (dispatcher->config->debugTiming) + { + timespec syncendtime; + getTime(&syncendtime); + cout << "Thread: " << tthread::this_thread::get_id() << " Synchronized initialization time in " << diffclock(syncstarttime, syncendtime) << "ms." << endl; + } int loop_count = 0; while (true) { + if (dispatcher->hasPlate() == false) break; @@ -158,7 +196,7 @@ void plateAnalysisThread(void* arg) // Get a single plate region from the queue Rect plateRegion = dispatcher->nextPlate(); - Mat img = dispatcher->getImageCopy(); + // Parallel section @@ -166,6 +204,9 @@ void plateAnalysisThread(void* arg) timespec platestarttime; getTime(&platestarttime); + StateIdentifier* stateIdentifier = dispatcher->stateIdentifiers[threadID]; + OCR* ocr = dispatcher->ocrs[threadID]; + LicensePlateCandidate lp(img, plateRegion, dispatcher->config); lp.recognize(); @@ -186,7 +227,7 @@ void plateAnalysisThread(void* arg) if (dispatcher->detectRegion) { char statecode[4]; - plateResult.regionConfidence = dispatcher->stateIdentifier->recognize(img, plateRegion, statecode); + plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegion, statecode); if (plateResult.regionConfidence > 0) { plateResult.region = statecode; @@ -194,12 +235,12 @@ void plateAnalysisThread(void* arg) } - dispatcher->ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); + ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); - dispatcher->ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); + ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); //plateResult.characters = ocr->postProcessor->bestChars; - const vector ppResults = dispatcher->ocr->postProcessor->getResults(); + const vector ppResults = ocr->postProcessor->getResults(); int bestPlateIndex = 0; diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h index 8fed548..5915736 100644 --- a/src/openalpr/alpr_impl.h +++ b/src/openalpr/alpr_impl.h @@ -63,15 +63,17 @@ class AlprImpl private: + int getNumThreads(); + cJSON* createJsonObj(const AlprResult* result); + RegionDetector* plateDetector; - StateIdentifier* stateIdentifier; - OCR* ocr; + vector stateIdentifiers; + vector ocrs; int topN; bool detectRegion; std::string defaultRegion; - cJSON* createJsonObj(const AlprResult* result); }; @@ -81,19 +83,21 @@ class PlateDispatcher public: PlateDispatcher(vector plateRegions, cv::Mat* image, Config* config, - StateIdentifier* stateIdentifier, - OCR* ocr, + vector stateIdentifiers, + vector ocrs, int topN, bool detectRegion, std::string defaultRegion) { this->plateRegions = plateRegions; this->frame = image; this->config = config; - this->stateIdentifier = stateIdentifier; - this->ocr = ocr; + this->stateIdentifiers = stateIdentifiers; + this->ocrs = ocrs; this->topN = topN; this->detectRegion = detectRegion; this->defaultRegion = defaultRegion; + + this->threadCounter = 0; } cv::Mat getImageCopy() @@ -105,6 +109,13 @@ class PlateDispatcher return img; } + + int getUniqueThreadId() + { + tthread::lock_guard guard(mMutex); + + return threadCounter++; + } bool hasPlate() { @@ -136,8 +147,8 @@ class PlateDispatcher } - StateIdentifier* stateIdentifier; - OCR* ocr; + vector stateIdentifiers; + vector ocrs; Config* config; int topN; @@ -146,6 +157,7 @@ class PlateDispatcher private: + int threadCounter; tthread::mutex mMutex; cv::Mat* frame; vector plateRegions; From 749366741e73d9fa7db1bf85a6d8f534e4fa82dd Mon Sep 17 00:00:00 2001 From: Kristians Vebers Date: Tue, 25 Feb 2014 23:24:14 +0200 Subject: [PATCH 10/11] Removed findnegatves build rule from misc_utilities as source file is not found in repository --- src/misc_utilities/CMakeLists.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/misc_utilities/CMakeLists.txt b/src/misc_utilities/CMakeLists.txt index d9ac3a7..d0ea6bc 100644 --- a/src/misc_utilities/CMakeLists.txt +++ b/src/misc_utilities/CMakeLists.txt @@ -35,12 +35,3 @@ TARGET_LINK_LIBRARIES(prepcharsfortraining ${OpenCV_LIBS} ) - - -ADD_EXECUTABLE( findnegatives findnegatives.cpp ) -TARGET_LINK_LIBRARIES(findnegatives - openalpr - support - ${OpenCV_LIBS} - tesseract - ) \ No newline at end of file From ec8fdc8aade80f03fd2e89fc5727e4459ea2fcb4 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 4 Apr 2014 13:58:53 -0500 Subject: [PATCH 11/11] Revert "Using multiple instances of OCR and state recognizer for multiple threads" This reverts commit 29cb8fab213bfbc16b6a9674f6244d780f425533. --- src/openalpr/alpr_impl.cpp | 71 ++++++++------------------------------ src/openalpr/alpr_impl.h | 30 +++++----------- 2 files changed, 24 insertions(+), 77 deletions(-) diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index 51d5dfa..5c405a7 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -26,17 +26,8 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) { config = new Config(country, runtimeDir); plateDetector = new RegionDetector(config); - - int numThreads = getNumThreads(); - - for (int i = 0; i < numThreads; i++) - { - StateIdentifier* stateIdentifier = new StateIdentifier(config); - OCR* ocr = new OCR(config); - - stateIdentifiers.push_back(stateIdentifier); - ocrs.push_back(ocr); - } + stateIdentifier = new StateIdentifier(config); + ocr = new OCR(config); setNumThreads(0); @@ -77,28 +68,10 @@ AlprImpl::~AlprImpl() { delete config; delete plateDetector; - - for (int i = 0; i < stateIdentifiers.size(); i++) - { - delete stateIdentifiers[i]; - delete ocrs[i]; - } - - stateIdentifiers.clear(); - ocrs.clear(); + delete stateIdentifier; + delete ocr; } -int AlprImpl::getNumThreads() -{ - // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) - int numThreads = config->multithreading_cores; - if (numThreads > tthread::thread::hardware_concurrency()) - numThreads = tthread::thread::hardware_concurrency(); - if (numThreads <= 0) - numThreads = 1; - - return numThreads; -} std::vector AlprImpl::recognize(cv::Mat img) { @@ -110,11 +83,15 @@ std::vector AlprImpl::recognize(cv::Mat img) vector plateRegions = plateDetector->detect(img); // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) - int numThreads = getNumThreads(); + int numThreads = config->multithreading_cores; + if (numThreads > tthread::thread::hardware_concurrency()) + numThreads = tthread::thread::hardware_concurrency(); + if (numThreads <= 0) + numThreads = 1; PlateDispatcher dispatcher(plateRegions, &img, - config, stateIdentifiers, ocrs, + config, stateIdentifier, ocr, topN, detectRegion, defaultRegion); // Spawn n threads to process all of the candidate regions and recognize @@ -164,28 +141,13 @@ std::vector AlprImpl::recognize(cv::Mat img) void plateAnalysisThread(void* arg) { PlateDispatcher* dispatcher = (PlateDispatcher*) arg; - - timespec syncstarttime; - getTime(&syncstarttime); - - int threadID = dispatcher->getUniqueThreadId(); - Mat img = dispatcher->getImageCopy(); - if (dispatcher->config->debugGeneral) cout << "Thread: " << tthread::this_thread::get_id() << " Initialized" << endl; - - if (dispatcher->config->debugTiming) - { - timespec syncendtime; - getTime(&syncendtime); - cout << "Thread: " << tthread::this_thread::get_id() << " Synchronized initialization time in " << diffclock(syncstarttime, syncendtime) << "ms." << endl; - } int loop_count = 0; while (true) { - if (dispatcher->hasPlate() == false) break; @@ -196,7 +158,7 @@ void plateAnalysisThread(void* arg) // Get a single plate region from the queue Rect plateRegion = dispatcher->nextPlate(); - + Mat img = dispatcher->getImageCopy(); // Parallel section @@ -204,9 +166,6 @@ void plateAnalysisThread(void* arg) timespec platestarttime; getTime(&platestarttime); - StateIdentifier* stateIdentifier = dispatcher->stateIdentifiers[threadID]; - OCR* ocr = dispatcher->ocrs[threadID]; - LicensePlateCandidate lp(img, plateRegion, dispatcher->config); lp.recognize(); @@ -227,7 +186,7 @@ void plateAnalysisThread(void* arg) if (dispatcher->detectRegion) { char statecode[4]; - plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegion, statecode); + plateResult.regionConfidence = dispatcher->stateIdentifier->recognize(img, plateRegion, statecode); if (plateResult.regionConfidence > 0) { plateResult.region = statecode; @@ -235,12 +194,12 @@ void plateAnalysisThread(void* arg) } - ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); + dispatcher->ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); - ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); + dispatcher->ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); //plateResult.characters = ocr->postProcessor->bestChars; - const vector ppResults = ocr->postProcessor->getResults(); + const vector ppResults = dispatcher->ocr->postProcessor->getResults(); int bestPlateIndex = 0; diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h index 5915736..8fed548 100644 --- a/src/openalpr/alpr_impl.h +++ b/src/openalpr/alpr_impl.h @@ -63,17 +63,15 @@ class AlprImpl private: - int getNumThreads(); - cJSON* createJsonObj(const AlprResult* result); - RegionDetector* plateDetector; - vector stateIdentifiers; - vector ocrs; + StateIdentifier* stateIdentifier; + OCR* ocr; int topN; bool detectRegion; std::string defaultRegion; + cJSON* createJsonObj(const AlprResult* result); }; @@ -83,21 +81,19 @@ class PlateDispatcher public: PlateDispatcher(vector plateRegions, cv::Mat* image, Config* config, - vector stateIdentifiers, - vector ocrs, + StateIdentifier* stateIdentifier, + OCR* ocr, int topN, bool detectRegion, std::string defaultRegion) { this->plateRegions = plateRegions; this->frame = image; this->config = config; - this->stateIdentifiers = stateIdentifiers; - this->ocrs = ocrs; + this->stateIdentifier = stateIdentifier; + this->ocr = ocr; this->topN = topN; this->detectRegion = detectRegion; this->defaultRegion = defaultRegion; - - this->threadCounter = 0; } cv::Mat getImageCopy() @@ -109,13 +105,6 @@ class PlateDispatcher return img; } - - int getUniqueThreadId() - { - tthread::lock_guard guard(mMutex); - - return threadCounter++; - } bool hasPlate() { @@ -147,8 +136,8 @@ class PlateDispatcher } - vector stateIdentifiers; - vector ocrs; + StateIdentifier* stateIdentifier; + OCR* ocr; Config* config; int topN; @@ -157,7 +146,6 @@ class PlateDispatcher private: - int threadCounter; tthread::mutex mMutex; cv::Mat* frame; vector plateRegions;