(sender());
131 | if (socket)
132 | {
133 | socket->deleteLater();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/client/qt/source/qml/Popups/AboutPopup.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | import QtQuick 2.15
19 | import QtQuick.Controls 2.15
20 | import QtQuick.Controls.Universal 2.15
21 |
22 | import "../Widgets" as Widgets
23 | import ".."
24 |
25 | Widgets.ModalPopup {
26 | title: "About"
27 |
28 | context: Component {
29 | Label {
30 | id: aboutLabel
31 |
32 | textFormat: Text.StyledText
33 |
34 | wrapMode: Text.Wrap
35 | text: "" + Common.title + "
" +
36 | "" +
37 | "- Address: github/fuzun/iot-facerecognition
" +
38 | "- License: GNU Affero GPL
" +
39 | "- Author: fuzun
" +
40 | "- Version: " + Qt.application.version + "
" +
41 | "
" +
42 |
43 | "Used Open Source Projects:
" +
44 |
45 | "Qt Framework Base
" +
46 | "" +
47 | "- Address: https://github.com/qt/qtbase" +
48 | "
- Used under GNU LGPL Version 3 license
" +
49 | "
" +
50 |
51 | "qtvirtualkeyboard
" +
52 | "Qt Module - QtQuick virtual keyboard
" +
53 | "" +
54 | "- Address: https://github.com/qt/qtvirtualkeyboard" +
55 | "
- Used under GNU GPL Version 3 license
" +
56 | "
" +
57 |
58 | "qtcharts
" +
59 | "Qt Module - QtCharts module
" +
60 | "" +
61 | "- Address: https://github.com/qt/qtcharts" +
62 | "
- Used under GNU GPL Version 3 license
" +
63 | "
" +
64 |
65 | "qtwebsockets
" +
66 | "Qt Module - Qt WebSockets
" +
67 | "" +
68 | "- Address: https://github.com/qt/qtwebsockets" +
69 | "
- Used under GNU LGPL Version 3 license
" +
70 | "
" +
71 |
72 | "qtquickcontrols2
" +
73 | "Qt Module - Qt Quick Controls 2
" +
74 | "" +
75 | "- Address: https://github.com/qt/qtquickcontrols2" +
76 | "
- Used under GNU LGPL Version 3 license
" +
77 | "
" +
78 |
79 | "OpenMaxIL-cpp
" +
80 | "OpenMax IL C++ wrapper for RaspberryPi
" +
81 | "" +
82 | "- Address: https://github.com//dridri/OpenMaxIL-cpp" +
83 | "
- Used under MIT License
" +
84 | "
" +
85 |
86 | "SampleYUVRenderer
" +
87 | "Very basic sketch of rendering YUV frames via Qt/OpenGL
" +
88 | "" +
89 | "- Address: https://github.com//MasterAler/SampleYUVRenderer" +
90 | "
- Used under MIT License
" +
91 | "
" +
92 |
93 | "Qt Official Docs
" +
94 | "Scene Graph - OpenGL Under QML
" +
95 | "" +
96 | "- Address: https://doc-snapshots.qt.io/qt5-5.15/qtquick-scenegraph-openglunderqml-example.html" +
97 | "
- Used under BSD License
" +
98 | "
" +
99 |
100 | "yuv2rgb
" +
101 | "C99 library for fast image conversion between yuv420p and rgb24
" +
102 | "" +
103 | "- Address: https://github.com/descampsa/yuv2rgb" +
104 | "
- Used under BSD-3-Clause License
" +
105 | "
" +
106 |
107 | "yuv2rgb
" +
108 | "C99 library for fast image conversion between yuv420p and rgb24
" +
109 | "" +
110 | "- Address: https://github.com/descampsa/yuv2rgb" +
111 | "
- Used under BSD-3-Clause License
" +
112 | "
" +
113 |
114 | "libjpeg-turbo
" +
115 | "" +
116 | "- Address: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master" +
117 | "
- Used under The Modified (3-clause) BSD License
" +
118 | "
" +
119 |
120 | "Background Images
" +
121 | intf.readTextFile(Common.bgLicense) +
122 |
123 | "Icons
" +
124 | intf.readTextFile(Common.iconLicense)
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/client/qt/source/Client/ClientSettings.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef CLIENTSETTINGS_H
20 | #define CLIENTSETTINGS_H
21 |
22 | #include "common/BaseSettings.hpp"
23 |
24 | class ClientSettings : public BaseSettings
25 | {
26 | Q_OBJECT
27 |
28 | Q_PROPERTY(bool objectDetectionEnabled READ objectDetectionEnabled WRITE setObjectDetectionEnabled NOTIFY objectDetectionEnabledChanged)
29 | Q_PROPERTY(size_t labelCount READ labelCount WRITE setLabelCount NOTIFY labelCountChanged)
30 | Q_PROPERTY(QString clientName READ clientName WRITE setClientName NOTIFY clientNameChanged)
31 | Q_PROPERTY(bool deterministicObjectDetection READ deterministicObjectDetection WRITE setDeterministicObjectDetection NOTIFY deterministicObjectDetectionChanged)
32 | Q_PROPERTY(bool faceRecognitionEnabled READ faceRecognitionEnabled WRITE setFaceRecognitionEnabled NOTIFY faceRecognitionEnabledChanged)
33 | Q_PROPERTY(bool ignoreSSLErrors READ ignoreSSLErrors WRITE setIgnoreSSLErrors NOTIFY ignoreSSLErrorsChanged)
34 |
35 | bool m_objectDetectionEnabled = true;
36 | size_t m_labelCount = 5;
37 | QString m_clientName {"Default"};
38 | bool m_deterministicObjectDetection = false;
39 | bool m_faceRecognitionEnabled = true;
40 | bool m_ignoreSSLErrors = false;
41 |
42 | public:
43 | inline explicit ClientSettings(QObject *parent = nullptr, bool load = true) : BaseSettings(parent)
44 | {
45 | qRegisterMetaType();
46 |
47 | if (!load)
48 | return;
49 |
50 | this->load(ClientSettings::staticMetaObject);
51 | }
52 |
53 | inline ~ClientSettings()
54 | {
55 | save();
56 | }
57 |
58 | Q_INVOKABLE void save()
59 | {
60 | BaseSettings::save(ClientSettings::staticMetaObject);
61 | }
62 |
63 | Q_INVOKABLE void reset()
64 | {
65 | ClientSettings nonLoadedInstance(this, false);
66 | BaseSettings::reset(ClientSettings::staticMetaObject, &nonLoadedInstance);
67 | }
68 |
69 | inline bool objectDetectionEnabled() const
70 | {
71 | return m_objectDetectionEnabled;
72 | }
73 |
74 | inline size_t labelCount() const
75 | {
76 | return m_labelCount;
77 | }
78 |
79 | inline QString clientName() const
80 | {
81 | return m_clientName;
82 | }
83 |
84 | inline bool deterministicObjectDetection() const
85 | {
86 | return m_deterministicObjectDetection;
87 | }
88 |
89 | inline bool faceRecognitionEnabled() const
90 | {
91 | return m_faceRecognitionEnabled;
92 | }
93 |
94 | inline bool ignoreSSLErrors() const
95 | {
96 | return m_ignoreSSLErrors;
97 | }
98 |
99 | public slots:
100 | inline void setObjectDetectionEnabled(bool objectDetectionEnabled)
101 | {
102 | if (m_objectDetectionEnabled == objectDetectionEnabled)
103 | return;
104 |
105 | m_objectDetectionEnabled = objectDetectionEnabled;
106 | emit objectDetectionEnabledChanged(m_objectDetectionEnabled);
107 | }
108 |
109 | inline void setLabelCount(size_t labelCount)
110 | {
111 | if (m_labelCount == labelCount)
112 | return;
113 |
114 | m_labelCount = labelCount;
115 | emit labelCountChanged(m_labelCount);
116 | }
117 |
118 | inline void setClientName(const QString& clientName)
119 | {
120 | if (m_clientName == clientName)
121 | return;
122 |
123 | m_clientName = clientName;
124 | emit clientNameChanged(m_clientName);
125 | }
126 |
127 | inline void setDeterministicObjectDetection(bool deterministicObjectDetection)
128 | {
129 | if (m_deterministicObjectDetection == deterministicObjectDetection)
130 | return;
131 |
132 | m_deterministicObjectDetection = deterministicObjectDetection;
133 | emit deterministicObjectDetectionChanged(m_deterministicObjectDetection);
134 | }
135 |
136 | inline void setFaceRecognitionEnabled(bool faceRecognitionEnabled)
137 | {
138 | if (m_faceRecognitionEnabled == faceRecognitionEnabled)
139 | return;
140 |
141 | m_faceRecognitionEnabled = faceRecognitionEnabled;
142 | emit faceRecognitionEnabledChanged(m_faceRecognitionEnabled);
143 | }
144 |
145 | inline void setIgnoreSSLErrors(bool ignoreSSLErrors)
146 | {
147 | if (m_ignoreSSLErrors == ignoreSSLErrors)
148 | return;
149 |
150 | m_ignoreSSLErrors = ignoreSSLErrors;
151 | emit ignoreSSLErrorsChanged(m_ignoreSSLErrors);
152 | }
153 |
154 | signals:
155 | void objectDetectionEnabledChanged(bool objectDetectionEnabled);
156 | void labelCountChanged(size_t labelCount);
157 | void clientNameChanged(QString clientName);
158 | void deterministicObjectDetectionChanged(bool deterministicObjectDetection);
159 | void faceRecognitionEnabledChanged(bool faceRecognitionEnabled);
160 | void ignoreSSLErrorsChanged(bool ignoreSSLErrors);
161 | };
162 |
163 | #endif // CLIENTSETTINGS_H
164 |
--------------------------------------------------------------------------------
/client/qt/source/Application/Application.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include "Application.hpp"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #ifdef NDEBUG
29 | #include
30 | #endif
31 |
32 | #include "common/Log.hpp"
33 | #include "common/SettingObject.hpp"
34 | #include "Client/Client.hpp"
35 | #include "Interface/Interface.hpp"
36 | #include "Interface/InterfaceSettings.hpp"
37 | #include "QMLGLYUVWidget/QMLGLYUVWidget.hpp"
38 | #include "Camera/CameraController.hpp"
39 | #include "Statistics/Statistics.hpp"
40 |
41 | #include "common/common.hpp"
42 |
43 | Application::Application(QObject *parent, QApplication* application)
44 | : QObject(parent)
45 | {
46 | QQuickStyle::setStyle("Universal");
47 |
48 | Q_INIT_RESOURCE(qml);
49 | Q_INIT_RESOURCE(assets);
50 |
51 | qRegisterMetaType("size_t");
52 |
53 | qmlRegisterType("com.iotfacerecognition.components", 1, 0, "YUVRendererWidget");
54 | qmlRegisterUncreatableMetaObject(Log::staticMetaObject, "com.iotfacerecognition.log", 1, 0, "Log", "This type is uncreatable!");
55 | qmlRegisterUncreatableType("com.iotfacerecognition.settings", 1, 0, "SettingObject", "This type is uncreatable!");
56 |
57 | engine = new QQmlApplicationEngine(this);
58 |
59 | statistics = new Statistics(this);
60 | engine->rootContext()->setContextProperty("statistics", statistics);
61 |
62 | client = new Client(this, statistics);
63 | engine->rootContext()->setContextProperty("client", client);
64 |
65 | camera = new CameraController(this, statistics);
66 | engine->rootContext()->setContextProperty("camera", camera);
67 |
68 | connect(client, &Client::connectedChanged, this, [this]() {
69 | if (client->connected())
70 | connect(camera, &CameraController::jpegReady, client, &Client::sendData);
71 | else
72 | disconnect(camera, &CameraController::jpegReady, client, &Client::sendData);
73 | });
74 |
75 | interface = new Interface(this);
76 | engine->rootContext()->setContextProperty("intf", interface);
77 | connect(interface, &Interface::setLoggingToFile, this, &Application::setLoggingToFile);
78 |
79 | qRegisterMetaType();
80 | connect(camera, &CameraController::log, interface, &Interface::log);
81 | connect(client, &Client::log, interface, &Interface::log);
82 | connect(interface, &Interface::log, this, &Application::log);
83 |
84 | const QUrl url(QStringLiteral("qrc:/source/qml/main.qml"));
85 | connect(engine, &QQmlApplicationEngine::objectCreated,
86 | application, [url](QObject *obj, const QUrl &objUrl) {
87 | if (!obj && url == objUrl)
88 | QCoreApplication::exit(-1);
89 | }, Qt::QueuedConnection);
90 |
91 | engine->load(url);
92 | }
93 |
94 | Application::~Application()
95 | {
96 | if(logStream)
97 | logStream->flush();
98 | delete logStream;
99 | }
100 |
101 | void Application::log(const QVariant &str, Log::LogType type)
102 | {
103 | assert(type != Log::ERROR);
104 |
105 | const Log log(str, type);
106 |
107 | QString ctx = QString("[%1] %2: %3").arg(log.timeStamp(), log.typeStr(), log.ctx().toString());
108 |
109 | if (logStream)
110 | *logStream << ctx;
111 |
112 | #ifndef NDEBUG
113 | qDebug() << ctx;
114 | #else
115 | std::string msg = ctx.toStdString();
116 |
117 | switch (type)
118 | {
119 | case Log::LogType::ERROR:
120 | qCritical(msg.c_str());
121 | if (!interface->settings()->testMode())
122 | {
123 | throw std::runtime_error(msg.c_str());
124 | }
125 | break;
126 | case Log::LogType::WARNING:
127 | qWarning(msg.c_str());
128 | break;
129 | case Log::LogType::INFORMATION:
130 | default:
131 | qInfo(msg.c_str());
132 | break;
133 | }
134 | #endif
135 | }
136 |
137 | void Application::setLoggingToFile(bool enabled)
138 | {
139 | if (enabled)
140 | {
141 | if (logFile)
142 | return;
143 |
144 | logFile = new QFile("log.txt", this);
145 | if(!logFile->open(QFile::Text | QFile::Append))
146 | {
147 | log(tr("Could not open log output file!"), Log::ERROR);
148 | delete logFile;
149 | logFile = nullptr;
150 | }
151 | else
152 | {
153 | logStream = new QTextStream(logFile);
154 | }
155 | }
156 | else
157 | {
158 | if (!logFile)
159 | return;
160 |
161 | logStream->flush();
162 | logFile->flush();
163 | logFile->close();
164 |
165 | delete logStream;
166 | logStream = nullptr;
167 | delete logFile;
168 | logFile = nullptr;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/client/qt/source/Camera/YUVProviderVideoSurface.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "YUVProviderVideoSurface.hpp"
19 |
20 | #include
21 | #include
22 |
23 | #ifdef TURBOJPEG_AVAILABLE
24 | #include "turbojpeg.h"
25 | #endif
26 |
27 | #include "yuv2rgb/yuv_rgb.h"
28 |
29 | #include "CameraSettings.hpp"
30 | #include "common/YUVFrame.hpp"
31 |
32 | YUVProviderVideoSurface::YUVProviderVideoSurface(QObject *parent, YUVFrame* frame, CameraSettings *settings)
33 | : QAbstractVideoSurface(parent),
34 | m_frame(frame),
35 | m_settings(settings)
36 | {
37 | assert(m_frame);
38 | assert(m_settings);
39 |
40 | #ifdef TURBOJPEG_AVAILABLE
41 | tjHandle = tjInitCompress();
42 | #endif
43 | }
44 |
45 | YUVProviderVideoSurface::~YUVProviderVideoSurface()
46 | {
47 | #ifdef TURBOJPEG_AVAILABLE
48 | tjDestroy(tjHandle);
49 | #endif
50 |
51 | cleanBuffer();
52 | }
53 |
54 | bool YUVProviderVideoSurface::present(const QVideoFrame &frame)
55 | {
56 | assert(m_frame);
57 |
58 | if (frame.pixelFormat() != QVideoFrame::Format_YUV420P
59 | && frame.pixelFormat() != QVideoFrame::Format_RGB32)
60 | {
61 | setError(IncorrectFormatError);
62 | return false;
63 | }
64 |
65 | if (!m_frame)
66 | return false;
67 |
68 | m_frame->lockForWrite();
69 |
70 | QVideoFrame mapped = const_cast(frame);
71 |
72 | if(!mapped.isValid() || !mapped.map(QAbstractVideoBuffer::ReadOnly))
73 | {
74 | setError(ResourceError);
75 | return false;
76 | }
77 |
78 | if (m_frame->buffer && (m_frame->width != mapped.width() || m_frame->height != mapped.height()))
79 | {
80 | cleanBuffer();
81 | }
82 |
83 | if (!m_frame->buffer)
84 | {
85 | m_frame->width = mapped.width();
86 | m_frame->height = mapped.height();
87 | m_frame->buffer = new YUVFrame::BufferType[m_frame->size()];
88 | }
89 |
90 | if (frame.pixelFormat() == QVideoFrame::Format_RGB32)
91 | {
92 | // convert to YUV420
93 | #ifdef _YUVRGB_SSE2_
94 | rgb32_yuv420_sseu
95 | #else
96 | rgb32_yuv420_std
97 | #endif
98 | (frame.width(),
99 | frame.height(),
100 | frame.bits(),
101 | frame.width() * 4,
102 | m_frame->y().second,
103 | m_frame->v().second,
104 | m_frame->u().second,
105 | frame.width(),
106 | (frame.width() + 1) / 2,
107 | YCbCrType::YCBCR_709);
108 | }
109 | else if (frame.pixelFormat() == QVideoFrame::Format_YUV420P)
110 | {
111 | memcpy(m_frame->buffer,
112 | mapped.bits(),
113 | static_cast(mapped.mappedBytes()) > m_frame->size() ? m_frame->size() : mapped.mappedBytes());
114 | }
115 | else
116 | {
117 | assert(false);
118 | }
119 |
120 | m_frame->unlock();
121 |
122 | emit updated();
123 |
124 | // Encode JPEG
125 |
126 | #ifdef TURBOJPEG_AVAILABLE
127 | unsigned char* jpegBuffer = nullptr;
128 |
129 | unsigned long bufSize;
130 |
131 | tjCompress2(tjHandle,
132 | mapped.bits(),
133 | mapped.width(),
134 | mapped.bytesPerLine(),
135 | mapped.height(),
136 | TJPF_BGRA,
137 | &jpegBuffer,
138 | &bufSize,
139 | TJSAMP_420,
140 | m_settings->jpegQuality(),
141 | TJFLAG_FASTDCT);
142 |
143 | if (jpegBuffer && bufSize > 0)
144 | {
145 | QByteArray bArray(bufSize, Qt::Initialization::Uninitialized);
146 | memcpy(bArray.data(), jpegBuffer, bufSize);
147 | emit jpegReady(bArray);
148 |
149 | tjFree(jpegBuffer);
150 | }
151 |
152 | #else
153 | const QImage img(mapped.bits(),
154 | mapped.width(),
155 | mapped.height(),
156 | QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
157 | QByteArray bArray;
158 | QBuffer buffer(&bArray);
159 | img.save(&buffer, "JPEG");
160 | emit jpegReady(bArray);
161 |
162 | #endif
163 |
164 | mapped.unmap();
165 |
166 | return true;
167 | }
168 |
169 | void YUVProviderVideoSurface::cleanBuffer()
170 | {
171 | assert(m_frame);
172 |
173 | if (m_frame)
174 | {
175 | m_frame->lockForWrite();
176 | if (m_frame->buffer)
177 | {
178 | delete[] static_cast(m_frame->buffer);
179 | m_frame->buffer = nullptr;
180 | }
181 | m_frame->unlock();
182 | }
183 | }
184 |
185 | QList YUVProviderVideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
186 | {
187 | QList ret;
188 |
189 | if (type == QAbstractVideoBuffer::NoHandle)
190 | return (ret << QVideoFrame::PixelFormat::Format_YUV420P << QVideoFrame::PixelFormat::Format_RGB32);
191 | else
192 | {
193 | return ret;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/client/qt/source/qml/Widgets/ModalPopup.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | import QtQuick 2.15
19 | import QtQuick.Controls 2.15
20 | import QtQuick.Controls.Universal 2.15
21 | import QtQuick.Layouts 1.12
22 |
23 | import QtGraphicalEffects 1.0
24 |
25 | import ".."
26 |
27 | Popup {
28 | id: popup
29 |
30 | modal: true
31 |
32 | x: root.width / 2 - width / 2
33 | y: root.height / 2 - height / 2 - root.header.height
34 |
35 | property real widthRate: 1.5
36 | property real heightRate: 1.35
37 |
38 | width: root.width / widthRate
39 | height: root.height / heightRate
40 |
41 | property string title
42 | property Component context: undefined
43 |
44 | Component.onCompleted: {
45 | console.assert(context != null)
46 | }
47 |
48 | Connections {
49 | target: root.inputPanel
50 |
51 | function onActiveChanged() {
52 | const diff = (popup.y + root.header.height + popup.height - (root.inputPanelY))
53 | if (root.inputPanel.active)
54 | popup.y -= diff
55 | else
56 | popup.y = Qt.binding(function() { return root.height / 2 - height / 2 - root.header.height })
57 | }
58 | }
59 |
60 | contentItem: Item {
61 |
62 | ColumnLayout {
63 | anchors.fill: parent
64 |
65 | implicitWidth: childrenRect.width
66 | implicitHeight: childrenRect.height
67 |
68 | Label {
69 | id: titleLabel
70 | Layout.fillWidth: true
71 | text: title
72 | horizontalAlignment: Text.AlignHCenter
73 |
74 | visible: title.length > 0
75 | }
76 |
77 | Rectangle {
78 | Layout.fillWidth: true
79 | height: 2
80 | color: "lightgray"
81 | opacity: 0.5
82 |
83 | visible: titleLabel.visible
84 | }
85 |
86 | Flickable {
87 | Layout.fillWidth: true
88 | Layout.fillHeight: true
89 |
90 | readonly property bool contentExceedsNaturalHeight: ctxLoader.implicitHeight > height
91 |
92 | contentWidth: width
93 | contentHeight: contentExceedsNaturalHeight ? ctxLoader.implicitHeight : height
94 |
95 | ScrollBar.vertical: ScrollBar { id: scrollBar; visible: parent.contentExceedsNaturalHeight; width: visible ? implicitWidth : 0 }
96 |
97 | clip: true
98 |
99 | Loader {
100 | id: ctxLoader
101 |
102 | anchors.left: parent.left
103 | anchors.right: parent.right
104 | anchors.rightMargin: scrollBar.width
105 | anchors.verticalCenter: parent.verticalCenter
106 |
107 | sourceComponent: context
108 |
109 | Connections {
110 | target: ctxLoader.item
111 |
112 | ignoreUnknownSignals: true
113 |
114 | function onClosePopup() {
115 | popup.close()
116 | }
117 | }
118 | }
119 | }
120 |
121 | RoundButton {
122 | Layout.fillWidth: true
123 |
124 | text: "Close"
125 |
126 | onClicked: {
127 | popup.close()
128 | }
129 | }
130 | }
131 | }
132 |
133 | background: Item {
134 | FastBlur {
135 | id: bgBlur
136 |
137 | anchors.fill: parent
138 |
139 | radius: 0
140 |
141 | Behavior on radius {
142 | NumberAnimation {
143 | duration: 200
144 | easing.type: Easing.InSine
145 | }
146 | }
147 |
148 | Connections {
149 | target: popup
150 |
151 | function onVisibleChanged() {
152 | if (popup.visible) {
153 | if (!root.background.isBlurred)
154 | bgBlur.radius = 64
155 | } else {
156 | bgBlur.radius = 0
157 | }
158 | }
159 | }
160 |
161 | source: ShaderEffectSource {
162 | sourceItem: root.background
163 | sourceRect: Qt.rect(popup.x, popup.y + root.header.height, popup.width,
164 | popup.height)
165 | }
166 | }
167 |
168 | Rectangle {
169 | anchors.fill: parent
170 | color: Common.setColorAlpha(bgColor, 0.15)
171 |
172 | readonly property color bgColor: root.isThemeDark ? "darkgray" : "lightgray"
173 | readonly property color borderColor: bgColor //"#00ABA9"
174 |
175 | border.width: 2
176 | border.color: Common.setColorAlpha(borderColor, 0.85)
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/client/qt/source/yuv2rgb/yuv_rgb.h:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Adrien Descamps
2 | // Distributed under BSD 3-Clause License
3 |
4 | // Provide optimized functions to convert images from 8bits yuv420 to rgb24 format
5 |
6 | // There are a few slightly different variations of the YCbCr color space with different parameters that
7 | // change the conversion matrix.
8 | // The three most common YCbCr color space, defined by BT.601, BT.709 and JPEG standard are implemented here.
9 | // See the respective standards for details
10 | // The matrix values used are derived from http://www.equasys.de/colorconversion.html
11 |
12 | // YUV420 is stored as three separate channels, with U and V (Cb and Cr) subsampled by a 2 factor
13 | // For conversion from yuv to rgb, no interpolation is done, and the same UV value are used for 4 rgb pixels. This
14 | // is suboptimal for image quality, but by far the fastest method.
15 |
16 | // For all methods, width and height should be even, if not, the last row/column of the result image won't be affected.
17 | // For sse methods, if the width if not divisable by 32, the last (width%32) pixels of each line won't be affected.
18 |
19 | #include
20 |
21 | #ifdef _MSC_VER
22 | // MSVC does not have __SSE2__ macro
23 | #if (defined(_M_AMD64) || defined(_M_X64) || (_M_IX86_FP == 2))
24 | #define _YUVRGB_SSE2_
25 | #endif
26 | #else
27 | // For else than MSVC
28 | #ifdef __SSE2__
29 | #define _YUVRGB_SSE2_
30 | #endif // __SSE2__
31 | #endif // _MSC_VER
32 |
33 | typedef enum
34 | {
35 | YCBCR_JPEG,
36 | YCBCR_601,
37 | YCBCR_709
38 | } YCbCrType;
39 |
40 | #ifdef __cplusplus
41 | extern "C" {
42 | #endif
43 |
44 | // yuv to rgb, standard c implementation
45 | void yuv420_rgb24_std(
46 | uint32_t width, uint32_t height,
47 | const uint8_t *y, const uint8_t *u, const uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
48 | uint8_t *rgb, uint32_t rgb_stride,
49 | YCbCrType yuv_type);
50 |
51 | // yuv to rgb, yuv in nv12 semi planar format
52 | void nv12_rgb24_std(
53 | uint32_t width, uint32_t height,
54 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
55 | uint8_t *rgb, uint32_t rgb_stride,
56 | YCbCrType yuv_type);
57 |
58 | // yuv to rgb, yuv in nv12 semi planar format
59 | void nv21_rgb24_std(
60 | uint32_t width, uint32_t height,
61 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
62 | uint8_t *rgb, uint32_t rgb_stride,
63 | YCbCrType yuv_type);
64 |
65 | // yuv to rgb, sse implementation
66 | // pointers must be 16 byte aligned, and strides must be divisable by 16
67 | void yuv420_rgb24_sse(
68 | uint32_t width, uint32_t height,
69 | const uint8_t *y, const uint8_t *u, const uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
70 | uint8_t *rgb, uint32_t rgb_stride,
71 | YCbCrType yuv_type);
72 |
73 | // yuv to rgb, sse implementation
74 | // pointers do not need to be 16 byte aligned
75 | void yuv420_rgb24_sseu(
76 | uint32_t width, uint32_t height,
77 | const uint8_t *y, const uint8_t *u, const uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
78 | uint8_t *rgb, uint32_t rgb_stride,
79 | YCbCrType yuv_type);
80 |
81 | // yuv nv12 to rgb, sse implementation
82 | // pointers must be 16 byte aligned, and strides must be divisable by 16
83 | void nv12_rgb24_sse(
84 | uint32_t width, uint32_t height,
85 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
86 | uint8_t *rgb, uint32_t rgb_stride,
87 | YCbCrType yuv_type);
88 |
89 | // yuv nv12 to rgb, sse implementation
90 | // pointers do not need to be 16 byte aligned
91 | void nv12_rgb24_sseu(
92 | uint32_t width, uint32_t height,
93 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
94 | uint8_t *rgb, uint32_t rgb_stride,
95 | YCbCrType yuv_type);
96 |
97 | // yuv nv21 to rgb, sse implementation
98 | // pointers must be 16 byte aligned, and strides must be divisable by 16
99 | void nv21_rgb24_sse(
100 | uint32_t width, uint32_t height,
101 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
102 | uint8_t *rgb, uint32_t rgb_stride,
103 | YCbCrType yuv_type);
104 |
105 | // yuv nv21 to rgb, sse implementation
106 | // pointers do not need to be 16 byte aligned
107 | void nv21_rgb24_sseu(
108 | uint32_t width, uint32_t height,
109 | const uint8_t *y, const uint8_t *uv, uint32_t y_stride, uint32_t uv_stride,
110 | uint8_t *rgb, uint32_t rgb_stride,
111 | YCbCrType yuv_type);
112 |
113 |
114 |
115 |
116 | // rgb to yuv, standard c implementation
117 | void rgb24_yuv420_std(
118 | uint32_t width, uint32_t height,
119 | const uint8_t *rgb, uint32_t rgb_stride,
120 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
121 | YCbCrType yuv_type);
122 |
123 | // rgb to yuv, sse implementation
124 | // pointers must be 16 byte aligned, and strides must be divisible by 16
125 | void rgb24_yuv420_sse(
126 | uint32_t width, uint32_t height,
127 | const uint8_t *rgb, uint32_t rgb_stride,
128 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
129 | YCbCrType yuv_type);
130 |
131 | // rgb to yuv, sse implementation
132 | // pointers do not need to be 16 byte aligned
133 | void rgb24_yuv420_sseu(
134 | uint32_t width, uint32_t height,
135 | const uint8_t *rgb, uint32_t rgb_stride,
136 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
137 | YCbCrType yuv_type);
138 |
139 | // rgba to yuv, standard c implementation
140 | // alpha channel is ignored
141 | void rgb32_yuv420_std(
142 | uint32_t width, uint32_t height,
143 | const uint8_t *rgba, uint32_t rgba_stride,
144 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
145 | YCbCrType yuv_type);
146 |
147 | // rgba to yuv, sse implementation
148 | // pointers must be 16 byte aligned, and strides must be divisible by 16
149 | // alpha channel is ignored
150 | void rgb32_yuv420_sse(
151 | uint32_t width, uint32_t height,
152 | const uint8_t *rgba, uint32_t rgba_stride,
153 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
154 | YCbCrType yuv_type);
155 |
156 | // rgba to yuv, sse implementation
157 | // pointers do not need to be 16 byte aligned
158 | // alpha channel is ignored
159 | void rgb32_yuv420_sseu(
160 | uint32_t width, uint32_t height,
161 | const uint8_t *rgba, uint32_t rgba_stride,
162 | uint8_t *y, uint8_t *u, uint8_t *v, uint32_t y_stride, uint32_t uv_stride,
163 | YCbCrType yuv_type);
164 |
165 | #ifdef __cplusplus
166 | }
167 | #endif
168 |
--------------------------------------------------------------------------------
/client/qt/source/qml/Views/MainView.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | import QtQuick 2.15
19 | import QtQuick.Controls 2.15
20 | import QtQuick.Controls.Universal 2.15
21 |
22 | import QtGraphicalEffects 1.0
23 |
24 | import ".."
25 | import "../Popups"
26 |
27 | Item {
28 | id: mainView
29 |
30 | readonly property string title: Common.title
31 |
32 | signal viewChangeRequest(string view)
33 |
34 | AboutPopup { id: aboutPopup }
35 | SendMessagePopup { id: sendMessagePopup }
36 |
37 | readonly property real buttonWidth: Math.max(96, root.width / 8.5)
38 | readonly property real buttonHeight: Math.max(128, root.height / 3.75)
39 |
40 | component ViewButton: Button {
41 | id: button
42 |
43 | display: AbstractButton.TextUnderIcon
44 | icon.height: buttonWidth * 0.75
45 | icon.width: buttonWidth * 0.75
46 |
47 | icon.color: "transparent"
48 |
49 | implicitHeight: buttonHeight
50 | implicitWidth: buttonWidth
51 |
52 | background: Item {
53 |
54 | RectangularGlow {
55 | anchors.fill: parent
56 | glowRadius: button.pressed ? 12 : 2
57 | spread: 0.2
58 | color: "#00ABA9"
59 | visible: button.pressed
60 |
61 | Behavior on glowRadius {
62 | NumberAnimation { duration: 75; easing.type: Easing.InOutSine }
63 | }
64 | }
65 | FastBlur {
66 | anchors.fill: parent
67 |
68 | radius: 48
69 |
70 | source: ShaderEffectSource {
71 | readonly property point mappedPoint: Qt.point(button.x, button.y)
72 |
73 | sourceItem: root.background
74 | sourceRect: Qt.rect(mappedPoint.x - flickable.contentX, mappedPoint.y - flickable.contentY + root.header.height, button.width,
75 | button.height)
76 | }
77 | }
78 |
79 | Rectangle {
80 | anchors.fill: parent
81 | color: Common.setColorAlpha(bgColor, 0.1)
82 |
83 | readonly property color bgColor: root.isThemeDark ? "darkgray" : "lightgray"
84 | readonly property color borderColor: bgColor //"#00ABA9"
85 |
86 | border.width: 2
87 | border.color: Common.setColorAlpha(borderColor, 0.85)
88 | }
89 |
90 | Rectangle {
91 | anchors.fill: parent
92 | color: "lightgray"
93 |
94 | opacity: 0.5
95 |
96 | visible: button.pressed
97 | }
98 | }
99 | }
100 |
101 | Image {
102 | anchors.right: parent.right
103 | anchors.bottom: parent.bottom
104 | anchors.rightMargin: width * 0.25
105 | anchors.bottomMargin: anchors.rightMargin
106 |
107 | asynchronous: true
108 | antialiasing: true
109 |
110 | width: implicitWidth * 0.175
111 | height: implicitHeight * 0.175
112 |
113 | source: Common.builtWithQt
114 | }
115 |
116 | Flickable {
117 | id: flickable
118 | anchors.fill: parent
119 |
120 | contentWidth: width
121 | contentHeight: flow.height
122 |
123 | ScrollBar.vertical: ScrollBar {
124 | visible: flickable.contentHeight > flickable.height
125 | policy: ScrollBar.AlwaysOn
126 | }
127 |
128 | Flow {
129 | id: flow
130 | anchors.left: parent.left
131 | anchors.right: parent.right
132 |
133 | padding: buttonWidth / 2
134 | spacing: padding
135 |
136 | ViewButton {
137 | text: "Camera"
138 |
139 | icon.source: Common.cameraIcon
140 |
141 | onClicked: {
142 | viewChangeRequest(Common.cameraView)
143 | }
144 | }
145 |
146 | ViewButton {
147 | text: "Logs"
148 |
149 | icon.source: Common.logIcon
150 |
151 | onClicked: {
152 | viewChangeRequest(Common.logView)
153 | }
154 | }
155 |
156 | ViewButton {
157 | text: "Settings"
158 |
159 | icon.source: Common.settingsIcon
160 |
161 | onClicked: {
162 | viewChangeRequest(Common.settingsView)
163 | }
164 | }
165 |
166 | ViewButton {
167 | enabled: client.connected
168 |
169 | text: "Send\nMessage"
170 |
171 | icon.source: Common.messageIcon
172 |
173 | onClicked: {
174 | sendMessagePopup.open()
175 | }
176 | }
177 |
178 | ViewButton {
179 | text: qsTr("Statistics")
180 |
181 | icon.source: Common.statisticsIcon
182 |
183 | onClicked: {
184 | viewChangeRequest(Common.statisticsView)
185 | }
186 | }
187 |
188 | ViewButton {
189 | text: "About"
190 |
191 | icon.source: Common.aboutIcon
192 |
193 | onClicked: {
194 | aboutPopup.open()
195 | }
196 | }
197 |
198 | ViewButton {
199 | text: "Shutdown"
200 |
201 | icon.source: Common.shutdownIcon
202 |
203 | onClicked: {
204 | Qt.quit()
205 | }
206 | }
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/server/source/DLIBWorker/DLIBWorker.h:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-server
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | #ifndef DLIBWORKER_H
19 | #define DLIBWORKER_H
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include
29 | #include
30 |
31 | class DLIBWorker : public QObject
32 | {
33 | Q_OBJECT
34 |
35 | friend class Client;
36 |
37 | public:
38 | using Face = std::tuple>;
39 | using Object = Face;
40 | using Cluster = std::pair>;
41 |
42 | private:
43 | //
44 |
45 | // (dnn_face_recognition_ex.cpp):
46 | template class, int, typename> class block, int N, templateclass BN, typename SUBNET>
47 | using residual = dlib::add_prev1>>;
48 |
49 | template class, int, typename> class block, int N, templateclass BN, typename SUBNET>
50 | using residual_down = dlib::add_prev2>>>>>;
51 |
52 | template class BN, int stride, typename SUBNET>
53 | using block = BN>>>>;
54 |
55 | template using ares = dlib::relu>;
56 | template using ares_down = dlib::relu>;
57 |
58 | template using alevel0 = ares_down<256, SUBNET>;
59 | template using alevel1 = ares<256, ares<256, ares_down<256, SUBNET>>>;
60 | template using alevel2 = ares<128, ares<128, ares_down<128, SUBNET>>>;
61 | template using alevel3 = ares<64, ares<64, ares<64, ares_down<64, SUBNET>>>>;
62 | template using alevel4 = ares<32, ares<32, ares<32, SUBNET>>>;
63 |
64 | using anet_type = dlib::loss_metric
72 | >>>>>>>>>>>>;
73 |
74 | // ResNet-34 (dnn_imagenet_ex.cpp):
75 |
76 | template using level1 = ares<512,ares<512,ares_down<512,SUBNET>>>;
77 | template using level2 = ares<256,ares<256,ares<256,ares<256,ares<256,ares_down<256,SUBNET>>>>>>;
78 | template using level3 = ares<128,ares<128,ares<128,ares_down<128,SUBNET>>>>;
79 | template using level4 = ares<64,ares<64,ares<64,SUBNET>>>;
80 |
81 | using anet_type_2 = dlib::loss_multiclass_log
88 | >>>>>>>>>>>;
89 |
90 | //
91 |
92 | dlib::frontal_face_detector detector;
93 | dlib::shape_predictor sp;
94 | anet_type net;
95 |
96 | std::vector labels;
97 | anet_type_2 net2;
98 | dlib::rand rnd;
99 | dlib::softmax snet;
100 |
101 | QVector> m_refPhotoFileList;
102 | QString m_faceLandmarkModelFile;
103 | QString m_faceRecognitionModelFile;
104 | size_t m_faceDetailSize;
105 | double m_faceDetailPadding;
106 | double m_threshold;
107 | bool m_busy;
108 | QString m_imageNetClassifierFile;
109 | size_t m_numCrops;
110 | bool m_faceRecognitionInitialized = false;
111 | bool m_objectRecognitionInitialized = false;
112 |
113 | #ifdef TURBOJPEG_AVAILABLE
114 | void* m_tjHandle = nullptr;
115 | #endif
116 |
117 | const struct Settings* m_settings;
118 |
119 | std::vector referenceFaces;
120 |
121 | void resolveReferenceFaces(const QVector>& list);
122 |
123 | bool initFaceRecognition();
124 | bool initObjectRecognition();
125 |
126 |
127 | static std::vector createGraph(const std::vector& faces, double threshold);
128 | static std::vector cluster(const std::vector& graph, const std::vector& faces);
129 |
130 | public slots:
131 | void process(const QByteArray& buffer);
132 |
133 | public:
134 | explicit DLIBWorker(class QSettings* config, const struct Settings* settings);
135 | ~DLIBWorker();
136 |
137 | static dlib::array2d decodeJPEG(const QByteArray& jpegBuffer, void* tjHandle);
138 | dlib::array2d decodeJPEG(const QByteArray& jpegBuffer);
139 |
140 | inline bool isBusy() const { return m_busy; };
141 |
142 | std::vector findFaces(const dlib::array2d& img);
143 | std::vector findFaces(const QString& fileName);
144 |
145 | static dlib::rectangle make_cropping_rect_resnet(const dlib::matrix& img, dlib::rand* rnd);
146 | static void crop_images(const dlib::matrix& img, dlib::array>& crops, dlib::rand* rnd, long num_crops);
147 |
148 | public:
149 | signals:
150 | void doneFace(const QVector>& results);
151 | void doneObject(const QVector>& results);
152 |
153 | void log(const QString& str);
154 | };
155 |
156 | #endif // DLIBWORKER_H
157 |
--------------------------------------------------------------------------------
/client/qt/source/Client/ClientWorker.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * iot-facerecognition-client-qt
3 | * Copyright (C) 2021 - fuzun
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "ClientWorker.hpp"
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | ClientWorker::ClientWorker(QObject *parent) :
26 | QObject(parent)
27 | {
28 |
29 | }
30 |
31 | qint64 ClientWorker::sendTextMessage(const QString &ctx)
32 | {
33 | if(!socket || !socket->isValid())
34 | {
35 | emit log(tr("Could not send text message! (socket is not available)"), Log::WARNING);
36 | return -1;
37 | }
38 |
39 | auto ret = socket->sendTextMessage(ctx);
40 | auto pSize = ctx.size();
41 |
42 | if (ret < pSize)
43 | {
44 | emit log(tr("Text Package loss. WANTED TO SEND / ACTUALLY SENT: %1 / %2").arg(pSize).arg(ret), Log::WARNING);
45 | }
46 |
47 | return ret;
48 | }
49 |
50 | qint64 ClientWorker::sendBinaryMessage(const QByteArray &ctx)
51 | {
52 | if(!socket || !socket->isValid())
53 | {
54 | emit log(tr("Could not send binary message! (socket is not available)"), Log::WARNING);
55 | return -1;
56 | }
57 |
58 | auto ret = socket->sendBinaryMessage(ctx);
59 | auto pSize = ctx.size();
60 |
61 | if (ret < pSize)
62 | {
63 | emit log(QString("Binary Package loss. WANTED TO SEND / ACTUALLY SENT: %1 / %2").arg(pSize).arg(ret), Log::WARNING);
64 | }
65 |
66 | return ret;
67 | }
68 |
69 | void ClientWorker::processCommand(ClientWorker::Command cmd, const QVariant &ctx)
70 | {
71 | switch (cmd)
72 | {
73 | case Command::MESSAGE:
74 | {
75 | emit log(tr("Server says: %1").arg(ctx.toString()));
76 | break;
77 | }
78 |
79 | case Command::MESSAGE_TAG_FACE:
80 | {
81 | const QVariantList list = ctx.toList();
82 |
83 | emit faceTagReceived(ctx.toList());
84 |
85 | for (const auto & i : list)
86 | {
87 | const QVariantMap map = i.toMap();
88 | emit log(tr("Received face tag: \"%0\" - X: %1, Y: %2, W: %3, H: %4")
89 | .arg(map["tag"].toString())
90 | .arg(map["x"].toInt())
91 | .arg(map["y"].toInt())
92 | .arg(map["width"].toInt())
93 | .arg(map["height"].toInt()));
94 | }
95 |
96 | break;
97 | }
98 |
99 | case Command::MESSAGE_TAG_OBJECT:
100 | {
101 | const QVariantList list = ctx.toList();
102 |
103 | emit objectTagReceived(list);
104 |
105 | QStringList strList;
106 | for (const auto& i : list)
107 | {
108 | const QVariantMap map = i.toMap();
109 | strList.append(QString("%1: %2").arg(map["prediction"].toString()).arg(map["label"].toString()));
110 | }
111 |
112 | emit log(tr("Received object tag: %1").arg(strList.join(", ")));
113 |
114 | break;
115 | }
116 |
117 | case Command::INIT_REQUEST:
118 | {
119 | emit initRequest();
120 | }
121 |
122 | default:
123 | break;
124 | }
125 | }
126 |
127 | void ClientWorker::processTextMessage(const QString &message)
128 | {
129 | QJsonDocument jDoc = QJsonDocument::fromJson(message.toUtf8());
130 |
131 | if (jDoc.isNull() || jDoc.isEmpty() || !jDoc.isObject())
132 | return;
133 |
134 | const unsigned int command = (unsigned)(jDoc[keyCommand].toInt(-1));
135 |
136 | QJsonValue ctxVal = jDoc[keyContext];
137 | QVariant context;
138 |
139 | if (ctxVal != QJsonValue::Undefined)
140 | context = ctxVal.toVariant();
141 |
142 | emit commandReceived(static_cast(command), context);
143 | }
144 |
145 | void ClientWorker::init()
146 | {
147 | socket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
148 |
149 | QObject::connect(this, &ClientWorker::commandReceived, this, &ClientWorker::processCommand);
150 | QObject::connect(socket, &QWebSocket::textMessageReceived, this, &ClientWorker::processTextMessage);
151 |
152 | QObject::connect(socket, &QWebSocket::bytesWritten, this, &ClientWorker::sentBytes);
153 |
154 | const auto receivedBytesEmitter = [this](const auto& frame, bool) {
155 | emit receivedBytes(frame.size());
156 | };
157 | QObject::connect(socket, &QWebSocket::textFrameReceived, this, receivedBytesEmitter);
158 | QObject::connect(socket, &QWebSocket::binaryFrameReceived, this, receivedBytesEmitter);
159 |
160 | QObject::connect(socket, QOverload::of(&QWebSocket::error), this, [this]() {
161 | emit log(QString("Socket error: %1").arg(socket->errorString()), Log::WARNING);
162 | });
163 | }
164 |
165 | void ClientWorker::sendCommand(ClientWorker::Command cmd, const QVariant &ctx)
166 | {
167 | QJsonObject obj;
168 |
169 | obj[keyCommand] = static_cast(cmd);
170 | if (ctx.isValid())
171 | obj[keyContext] = QJsonValue::fromVariant(ctx);
172 |
173 | QJsonDocument jDoc(obj);
174 |
175 | if (ctx.isValid())
176 | {
177 | emit log(tr("Sending command: %1, with context: %2").arg(static_cast(cmd)).arg(ctx.toString()));
178 | }
179 | else
180 | {
181 | emit log(tr("Sending command: %1").arg(static_cast(cmd)));
182 | }
183 |
184 | sendTextMessage(QString(jDoc.toJson(QJsonDocument::JsonFormat::Compact)));
185 | }
186 |
187 | void ClientWorker::connect(const QUrl &url)
188 | {
189 | emit log(tr("Connecting to %1...").arg(url.toString()));
190 | socket->open(url);
191 | }
192 |
193 | void ClientWorker::disconnect()
194 | {
195 | emit log(tr("Disconnecting from server..."));
196 | socket->close();
197 | }
198 |
199 | QString ClientWorker::serverAddress()
200 | {
201 | return socket->peerAddress().toString();
202 | }
203 |
204 | QVariant ClientWorker::serverPort()
205 | {
206 | return QVariant::fromValue(socket->peerPort());
207 | }
208 |
209 | void ClientWorker::sendMessage(const QString &string)
210 | {
211 | sendCommand(Command::MESSAGE, string);
212 | }
213 |
214 | void ClientWorker::sendData(const QByteArray &data)
215 | {
216 | sendBinaryMessage(data);
217 | }
218 |
219 |
--------------------------------------------------------------------------------