Skip to content

Core Concepts#

Common Interfaces and Types#

Reference Counted Interface#

Everything in FaceEngine object system starts from here. The IRefCounted interface provides methods for reference counter access, increment, and decrement. All reference counted objects imply a custom memory management model. This way they support automated destruction when reference count drops to zero as well as more sophisticated strategies of partial destruction and weak referencing required for FaceEngine internal needs. The bare minimum of such functions is exposed to a user allowing:

  • to notify the object that it is required by a client via retaining a reference to it;

  • to notify the object that it is no longer required by releasing a reference to it;

  • to get actual reference counter value.

Reference counted objects expect some special treatment as well. Be sure never to call delete on any pointer to object derived from IRefCounted! Doing so leads to heap corruption. Simply calling release notifies the system when the object should be destroyed and it does this properly for you.

However, it is not recommended to interact with the reference counting mechanism manually as doing so may be error-prone. Instead, you are strongly advised to use smart pointers that are specially designed to handle such objects and provided by FaceEngine. See section "Automatic reference counting" for details.

Automatic reference counting#

For your convenience, a special smart pointer class Ref is provided. It is capable of automatic reference counter incrementing upon its creation and automatic decrementing upon its destruction. It also does an assertion of the inner raw pointer being non-null, thus preventing errors.

Two ways of working with Ref are possible:

Referencing - without acquiring ownership of object lifetime#

ISomeObject* createSomeObject();
{
/* Here createSomeObject returns an object with initial reference count of 1 (otherwise, it would be dead). Then Ref adds another one for itself making a total reference count of 2!
*/
Ref<ISomeObject> objref = make_ref(createSomeObject());
/* Here we use the object in any way we want expecting it to be properly destroyed when control will leave this scope.
*/

}
/* Here we have left the scope and Ref was automatically destroyed like any other object created on the stack. At the same time, it decreased reference count of its internal object by 1 making it 1 again.
*/

However, the object is not destroyed automatically! For this to happen, it should have precisely 0 references. Moreover, in this example, the raw pointer to the object is lost, so it is impossible to fix it in any way; thus a memory leak is introduced.

Acquiring - own object lifetime#

So keeping that in mind we introduce a concept of ownership acquiring. By acquiring an object, you mean that its raw pointer is not going to be used and only a valid Ref to it is required. To acquire ownership, use a special ::acquire() function. The fixed version of the above example would look like this:

ISomeObject* createSomeObject();
{
/* Here createSomeObject returns an object with initial reference count of 1 (otherwise, it would be dead). Then we acquire it leaving a total reference count of 1.
*/
Ref<ISomeObject> objref = acquire(createSomeObject());
/* Here we use the object in any way we want.
*/
}

/* Here we have left the scope and Ref was automatically destroyed like any other object created on the stack. At the same time, it decreased reference count of its internal object by 1 making it 0. The object is destroyed properly by the object system.
*/

Do not store or use raw pointers to the object when using the ::acquire() function, as ownership acquiring invalidates them.

Acquiring way of working with Ref is pretty like standard library shared_ptr own lifetime of the object after it returned by std::make_shared().

You can statically cast object type during acquiring or referencing. To achieve this, use special versions of the ::make_ref_as() and ::acquire_as() functions. It is your responsibility to ensure that such a cast is possible.

Please refer to FaceEngine Reference Manual for more details on available convenience methods and functions.

As a side note, be informed that typedefs for Ref's to all reference counted types are declared. All of them match the following naming convention: InterfaceNamePtr. So, for example, Ref<IDetector> is equivalent to IDetectorPtr.

Serializable object interface#

This interface represents an object. Object's contents may be serialized to some data stream and then read back. Think of this as loading and saving.

To interact with the aforementioned data stream, the serializable object needs a user-provided adapter. Such adapter is called the archive. See a detailed explanation of it in section "Archive interface" in chapter "Core facility".

Serializable interfaces: IDescriptor, IDescriptorBatch.

Auxiliary types#

Image type#

Since FaceEngine is a computer vision library, it is natural for it to implement some image concept. Therefore, an Image class exists. It is designed as a reference counted container for raw pixel color data. Reference counting allows a single image to be shared by several objects. However, one should understand, that each Image object is holding a reference to some data, so if the data is modified in any way, this affects all other objects holding the same reference. To make a deep copy of an Image, one should use the clone() method, since assignment operators just make a reference. It is also possible to clip a part of an image into a new image by means of extract() method.

Pixel data may be characterized by color channel layout, i.e., a number of color channels and their order. The engine defines a Format structure for that. The Format determines:

  • Number of color channels (e.g., RGB or grayscale);

  • Order of color channel (e.g., RGB vs. BGR).

FaceEngine assumes 8 bits (i.e., 1 byte) per color channel and implements 8 BPP grayscale, 24 BPP RGB/BGR and padded 32 BPP formats. Format conversion functions are also provided for convenience; see the convert() function family.

The Image class supports data range mapping. It is possible to map a subset of bytes in a rectangular area for reading or writing. The mapped pixels are represented by the SubImage structure. In contrast to Image, SubImage is just a data view and is not reference counted. You are not supposed to store SubImages longer that it is necessary to complete data modification. See the documentation of the map() function family for details.

The supports IO roitines to read/write OOM, JPEG, PNG and TIFF formats via FreeImage library.

The absence of image IO is dictated by the fact that FaceEngine focuses on being lightweight and with the minimum possible number of external dependencies. It is not designed solely with image processing purpose in mind. I.e., one may treat video frames as Images and process them one by one. In this case, an external (possibly proprietary) video codec is required.

Beta Mode#

Some features in LUNA SDK are available just in Beta mode. This is experimental features which may be unstable. If you want use them, you have to activate betaMode param in config (faceengine.conf).