Skip to content

Commit b7c4c1f

Browse files
committed
Server: use libjpeg-turbo when possible instead of relying on dlib's jpeg support
1 parent 64c7960 commit b7c4c1f

File tree

4 files changed

+171
-43
lines changed

4 files changed

+171
-43
lines changed

server/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ set(CMAKE_AUTORCC ON)
1717
set(CMAKE_CXX_STANDARD 17)
1818
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1919

20+
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
21+
2022
if (MSVC)
2123
add_definitions(/bigobj)
2224
add_definitions(/FS)
@@ -67,6 +69,22 @@ FetchContent_Declare(dlib
6769
)
6870
FetchContent_MakeAvailable(dlib)
6971

72+
if (NOT DLIB_JPEG_SUPPORT)
73+
message(FATAL_ERROR "dlib's jpeg support must be enabled!")
74+
endif()
75+
76+
find_package(TurboJPEG)
77+
if (TurboJPEG_FOUND OR DLIB_JPEG_SUPPORT)
78+
target_include_directories(iot-facerecognition-server PRIVATE ${TurboJPEG_INCLUDE_DIRS})
79+
target_link_libraries(iot-facerecognition-server PRIVATE ${TurboJPEG_LIBRARIES})
80+
target_compile_definitions(iot-facerecognition-server PRIVATE TURBOJPEG_AVAILABLE)
81+
82+
message(STATUS "libjpeg-turbo is found. It will be used for fast JPEG decoding.")
83+
elseif (DLIB_JPEG_SUPPORT)
84+
message(WARNING "libjpeg-turbo is NOT found. Real-time JPEG decoding may be slow.")
85+
endif()
86+
87+
7088
target_include_directories(iot-facerecognition-server
7189
PRIVATE
7290
source/ClientDialog

server/source/Client/Client.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ void Client::processBinaryMessage(const QByteArray& data)
125125
{
126126
if(dialog)
127127
{
128-
dlib::array2d<dlib::rgb_pixel> img(DLIBWorker::constructImgFromBuffer(data));
128+
dlib::array2d<dlib::rgb_pixel> img(DLIBWorker::decodeJPEG(data, nullptr));
129+
130+
if (img.size() <= 0)
131+
return;
129132

130133
QPixmap pixmap(QPixmap::fromImage(QImage((unsigned char*)dlib::image_data(img), img.nc(), img.nr(), QImage::Format_RGB888)));
131134
primaryDisplay->setPixmap(pixmap);

server/source/DLIBWorker/DLIBWorker.cpp

Lines changed: 142 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030

3131
#include <Client/Client.h>
3232

33-
#ifndef DLIB_JPEG_SUPPORT
34-
#error "DLIB must have built-in libjpeg support enabled!"
35-
#endif
36-
3733
#include <dlib/clustering.h>
3834
#include <dlib/image_io.h>
3935

36+
#ifdef TURBOJPEG_AVAILABLE
37+
#include <turbojpeg.h>
38+
#endif
39+
4040
#include "config.h"
4141

4242
using namespace dlib;
@@ -94,6 +94,104 @@ DLIBWorker::DLIBWorker(class QSettings* config, const Settings *settings)
9494
}
9595
}
9696
}
97+
98+
#ifdef TURBOJPEG_AVAILABLE
99+
QMetaObject::invokeMethod(this, [this]() {
100+
m_tjHandle = tjInitDecompress();
101+
}, Qt::QueuedConnection);
102+
#endif
103+
}
104+
105+
DLIBWorker::~DLIBWorker()
106+
{
107+
#ifdef TURBOJPEG_AVAILABLE
108+
if (m_tjHandle)
109+
tjDestroy(m_tjHandle);
110+
#endif
111+
}
112+
113+
dlib::array2d<rgb_pixel> DLIBWorker::decodeJPEG(const QByteArray &jpegBuffer, void *tjHandle)
114+
{
115+
array2d<rgb_pixel> img;
116+
117+
#ifdef TURBOJPEG_AVAILABLE
118+
bool destroyHandle = false;
119+
120+
if (tjHandle == nullptr)
121+
{
122+
tjHandle = tjInitDecompress();
123+
destroyHandle = true;
124+
}
125+
126+
assert(tjHandle);
127+
128+
int width, height;
129+
int jpegSubsamp, jpegColorspace;
130+
131+
int ret;
132+
133+
auto ptrBuffer = reinterpret_cast<const unsigned char*>(jpegBuffer.constData());
134+
135+
ret = tjDecompressHeader3(tjHandle,
136+
ptrBuffer,
137+
jpegBuffer.size(),
138+
&width, &height,
139+
&jpegSubsamp, &jpegColorspace);
140+
141+
if (ret || width <= 0 || height <= 0)
142+
{
143+
if (destroyHandle)
144+
tjDestroy(tjHandle);
145+
146+
return img;
147+
}
148+
149+
img.set_size(height, width);
150+
151+
tjDecompress2(tjHandle,
152+
ptrBuffer,
153+
jpegBuffer.size(),
154+
static_cast<unsigned char*>(image_data(img)),
155+
width, 0, height,
156+
TJPF_RGB,
157+
TJFLAG_FASTDCT);
158+
159+
if (destroyHandle)
160+
tjDestroy(tjHandle);
161+
#else
162+
Q_UNUSED(tjHandle);
163+
164+
try
165+
{
166+
load_jpeg(img, buffer.constData(), buffer.size());
167+
}
168+
catch(...)
169+
{
170+
171+
}
172+
#endif
173+
174+
return img;
175+
}
176+
177+
dlib::array2d<rgb_pixel> DLIBWorker::decodeJPEG(const QByteArray &jpegBuffer)
178+
{
179+
auto decoded = decodeJPEG(jpegBuffer,
180+
#ifdef TURBOJPEG_AVAILABLE
181+
m_tjHandle
182+
#else
183+
nullptr
184+
#endif
185+
);
186+
187+
if (decoded.size() <= 0)
188+
#ifdef TURBOJPEG_AVAILABLE
189+
emit log(QString("libjpeg-turbo error code: %1").arg(tjGetErrorStr2(m_tjHandle)));
190+
#else
191+
emit log("dlib::load_jpeg failed!");
192+
#endif
193+
194+
return decoded;
97195
}
98196

99197
std::vector<Face> DLIBWorker::findFaces(const array2d<rgb_pixel>& img)
@@ -260,26 +358,14 @@ void DLIBWorker::process(const QByteArray& buffer)
260358

261359
try
262360
{
263-
if(referenceFaces.size() == 0)
264-
{
265-
if(m_refPhotoFileList.size() == 0)
266-
{
267-
throwException(std::exception("Reference face list is empty!"));
268-
}
269-
else
270-
{
271-
detector = get_frontal_face_detector();
272-
string landmarkModel = m_faceLandmarkModelFile.toStdString();
273-
string recogModel = m_faceRecognitionModelFile.toStdString();
274-
deserialize(landmarkModel) >> sp;
275-
deserialize(recogModel) >> net;
361+
auto img = decodeJPEG(buffer);
276362

277-
setupReference(m_refPhotoFileList);
278-
}
363+
if (img.size() <= 0)
364+
{
365+
m_busy = false;
366+
return;
279367
}
280368

281-
auto img = constructImgFromBuffer(buffer);
282-
283369
if (m_settings->objectDetectionEnabled)
284370
{
285371
if (labels.empty())
@@ -312,30 +398,51 @@ void DLIBWorker::process(const QByteArray& buffer)
312398

313399
if (m_settings->faceRecognitionEnabled)
314400
{
401+
if(referenceFaces.size() == 0)
402+
{
403+
if(m_refPhotoFileList.size() == 0)
404+
{
405+
throwException(std::exception("Reference face list is empty!"));
406+
}
407+
else
408+
{
409+
detector = get_frontal_face_detector();
410+
string landmarkModel = m_faceLandmarkModelFile.toStdString();
411+
string recogModel = m_faceRecognitionModelFile.toStdString();
412+
deserialize(landmarkModel) >> sp;
413+
deserialize(recogModel) >> net;
414+
415+
setupReference(m_refPhotoFileList);
416+
}
417+
}
418+
315419
auto faces = findFaces(img);
316420

317-
rectangle rect;
318-
for (const auto& face : referenceFaces)
421+
if (faces.size() > 0)
319422
{
320-
faces.push_back(make_tuple(get<0>(face), rect, get<2>(face)));
321-
}
423+
rectangle rect;
424+
for (const auto& face : referenceFaces)
425+
{
426+
faces.push_back(make_tuple(get<0>(face), rect, get<2>(face)));
427+
}
322428

323-
auto graph = createGraph(faces, m_threshold);
429+
auto graph = createGraph(faces, m_threshold);
324430

325-
auto clusters = cluster(graph, faces);
431+
auto clusters = cluster(graph, faces);
326432

327-
QVector<QPair<QRect, QString>> linearFaces;
433+
QVector<QPair<QRect, QString>> linearFaces;
328434

329-
for (const auto& it : clusters)
330-
{
331-
QString str = it.first;
332-
for (const auto& it2 : it.second)
435+
for (const auto& it : clusters)
333436
{
334-
linearFaces.push_back(qMakePair(QRect(it2.left(), it2.top(), it2.width(), it2.height()), str));
437+
QString str = it.first;
438+
for (const auto& it2 : it.second)
439+
{
440+
linearFaces.push_back(qMakePair(QRect(it2.left(), it2.top(), it2.width(), it2.height()), str));
441+
}
335442
}
336-
}
337443

338-
emit doneFace(linearFaces);
444+
emit doneFace(linearFaces);
445+
}
339446
}
340447

341448
m_busy = false;
@@ -346,9 +453,3 @@ void DLIBWorker::process(const QByteArray& buffer)
346453
}
347454
}
348455

349-
dlib::array2d<dlib::rgb_pixel> DLIBWorker::constructImgFromBuffer(const QByteArray& buffer)
350-
{
351-
array2d<rgb_pixel> img;
352-
load_jpeg(img, buffer.constData(), buffer.size());
353-
return img;
354-
}

server/source/DLIBWorker/DLIBWorker.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ class DLIBWorker : public QObject
108108
QString m_imageNetClassifierFile;
109109
size_t m_numCrops;
110110

111+
#ifdef TURBOJPEG_AVAILABLE
112+
void* m_tjHandle = nullptr;
113+
#endif
114+
111115
const struct Settings* m_settings;
112116

113117
std::vector<Face> referenceFaces;
@@ -123,8 +127,10 @@ public slots:
123127

124128
public:
125129
explicit DLIBWorker(class QSettings* config, const struct Settings* settings);
130+
~DLIBWorker();
126131

127-
static dlib::array2d<dlib::rgb_pixel> constructImgFromBuffer(const QByteArray& buffer);
132+
static dlib::array2d<dlib::rgb_pixel> decodeJPEG(const QByteArray& jpegBuffer, void* tjHandle);
133+
dlib::array2d<dlib::rgb_pixel> decodeJPEG(const QByteArray& jpegBuffer);
128134

129135
inline bool isBusy() const { return m_busy; };
130136

0 commit comments

Comments
 (0)