├── .clang-format ├── .clang_complete ├── .github └── workflows │ └── default.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── GitSemver.cmake └── StringToSemver.cmake ├── docs ├── building-android.md └── images │ ├── application │ ├── articated_application.jpg │ ├── articated_application_menu.jpg │ ├── articated_application_no_cam.jpg │ └── articated_application_test_video.jpg │ └── drawn_markers.jpg ├── res ├── mipmap-hdpi │ └── ic_launcher.png ├── mipmap-mdpi │ └── ic_launcher.png ├── mipmap-xhdpi │ └── ic_launcher.png ├── mipmap-xxhdpi │ └── ic_launcher.png └── mipmap-xxxhdpi │ └── ic_launcher.png ├── resources ├── 3D_models │ ├── 3D_models.qrc │ ├── articated.obj │ ├── nothing.obj │ ├── piramid.obj │ ├── shuttle.obj │ └── teapot.obj └── debug_samples │ ├── 3_markers_good.webm │ ├── debug_samples.qrc │ └── textest.png ├── source ├── CMakeLists.txt ├── augmentation_widget │ ├── CMakeLists.txt │ ├── augmentation_renderer.cpp │ ├── augmentation_renderer.hpp │ ├── augmentation_view.cpp │ ├── augmentation_view.hpp │ ├── model_loader.cpp │ ├── model_loader.hpp │ └── shaders │ │ ├── GL_shaders.qrc │ │ ├── background_fs.glsl │ │ ├── background_vs.glsl │ │ ├── object_fs.glsl │ │ └── object_vs.glsl ├── main.cpp ├── qml │ ├── Main.qml │ ├── MainView.qml │ ├── Settings.qml │ ├── SettingsView.qml │ └── qml.qrc ├── shared │ ├── CMakeLists.txt │ ├── frame_data.hpp │ └── movement3d │ │ ├── CMakeLists.txt │ │ ├── movement3d.cpp │ │ ├── movement3d.hpp │ │ ├── movement3d_filter.cpp │ │ └── movement3d_filter.hpp └── vision │ ├── CMakeLists.txt │ ├── algorithms │ ├── CMakeLists.txt │ ├── algorithm_interface.hpp │ ├── gpu │ │ ├── algorithm_gpu.cpp │ │ ├── algorithm_gpu.hpp │ │ └── shaders │ │ │ ├── blur_fs.glsl │ │ │ ├── passthrough_fs.glsl │ │ │ ├── passthrough_vs.glsl │ │ │ ├── segment_fs.glsl │ │ │ └── vision_gpu_shaders.qrc │ ├── original │ │ ├── algorithm_original.cpp │ │ └── algorithm_original.hpp │ ├── random │ │ ├── algorithm_random.cpp │ │ └── algorithm_random.hpp │ └── utils │ │ ├── classification.cpp │ │ ├── classification.hpp │ │ ├── frame_helper.cpp │ │ ├── frame_helper.hpp │ │ ├── image.hpp │ │ ├── operators.cpp │ │ ├── operators.hpp │ │ └── point.hpp │ ├── vision.cpp │ └── vision.hpp └── tests ├── CMakeLists.txt ├── augmentation ├── AugmentationTest.qml ├── CMakeLists.txt ├── augmentation_test.qrc ├── main.cpp ├── mock_algorithm.cpp └── mock_algorithm.hpp ├── classification ├── CMakeLists.txt ├── demo.cpp └── test.cpp ├── movement3d ├── CMakeLists.txt └── test.cpp ├── movement3d_filter ├── CMakeLists.txt └── test.cpp └── vision ├── CMakeLists.txt ├── VisionTest.qml ├── frame_data_lister.cpp ├── frame_data_lister.hpp ├── main.cpp └── vision_test.qrc /.clang-format: -------------------------------------------------------------------------------- 1 | # clang-format 2 | # Made by: Ingmar Delsink 3 | # idelsink.com 4 | # See http://clang.llvm.org/docs/ClangFormatStyleOptions.html 5 | # Tested with: clang-format version 3.7.1 6 | 7 | # General 8 | ######### 9 | 10 | # The style used for all options not specifically set in the configuration. 11 | # This option is supported only in the clang-format configuration (both within -style='{...}' and the .clang-format file). 12 | # Possible values: 13 | # LLVM A style complying with the LLVM coding standards 14 | # Google A style complying with Google’s C++ style guide 15 | # Chromium A style complying with Chromium’s style guide 16 | # Mozilla A style complying with Mozilla’s style guide 17 | # WebKit A style complying with WebKit’s style guide 18 | #BasedOnStyle: 19 | 20 | # TabWidth (unsigned) 21 | # The number of columns used for tab stops. 22 | TabWidth: 4 23 | 24 | # IndentWidth (unsigned) 25 | # The number of columns to use for indentation. 26 | IndentWidth: 4 27 | 28 | # UseTab (UseTabStyle) 29 | # The way to use tab characters in the resulting file. 30 | # Possible values: 31 | # UT_Never (in configuration: Never) Never use tab. 32 | # UT_ForIndentation (in configuration: ForIndentation) Use tabs only for indentation. 33 | # UT_Always (in configuration: Always) Use tabs whenever we need to fill whitespace that spans at least from one tab stop to the next one. 34 | UseTab: Never 35 | 36 | # C++ 37 | ##### 38 | 39 | # Language (LanguageKind) 40 | # Language, this format style is targeted at. 41 | # Possible values: 42 | # LK_None (in configuration: None) Do not use. 43 | # LK_Cpp (in configuration: Cpp) Should be used for C, C++, ObjectiveC, ObjectiveC++. 44 | # LK_Java (in configuration: Java) Should be used for Java. 45 | # LK_JavaScript (in configuration: JavaScript) Should be used for JavaScript. 46 | # LK_Proto (in configuration: Proto) Should be used for Protocol Buffers (https://developers.google.com/protocol-buffers/). 47 | # LK_TableGen (in configuration: TableGen) Should be used for TableGen code. 48 | Language: Cpp 49 | 50 | # Standard (LanguageStandard) 51 | # Format compatible with this standard, e.g. use A > instead of A> for LS_Cpp03. 52 | # Possible values: 53 | # LS_Cpp03 (in configuration: Cpp03) Use C++03-compatible syntax. 54 | # LS_Cpp11 (in configuration: Cpp11) Use features of C++11 (e.g. A> instead of A >). 55 | # LS_Auto (in configuration: Auto) Automatic detection based on the input. 56 | Standard: Cpp11 57 | 58 | # Pointer and reference alignment style. Possible values: Left, Right, Middle. 59 | PointerAlignment: Left 60 | 61 | # AccessModifierOffset (int) 62 | # The extra indent or outdent of access modifiers, e.g. public:. 63 | AccessModifierOffset: 0 64 | 65 | # AlignAfterOpenBracket (BracketAlignmentStyle) 66 | # If true, horizontally aligns arguments after an open bracket. 67 | # This applies to round brackets (parentheses), angle brackets and square brackets. 68 | # Possible values: 69 | # BAS_Align (in configuration: Align) Align parameters on the open bracket, e.g.: 70 | # someLongFunction(argument1, 71 | # argument2); 72 | # BAS_DontAlign (in configuration: DontAlign) Don’t align, instead use ContinuationIndentWidth, e.g.: 73 | # someLongFunction(argument1, 74 | # argument2); 75 | # BAS_AlwaysBreak (in configuration: AlwaysBreak) Always break after an open bracket, if the parameters don’t fit on a single line, e.g.: 76 | # someLongFunction( 77 | # argument1, argument2); 78 | AlignAfterOpenBracket: false 79 | 80 | # AlignConsecutiveAssignments (bool) 81 | # If true, aligns consecutive assignments. 82 | # This will align the assignment operators of consecutive lines. This will result in formattings like 83 | # int aaaa = 12; 84 | # int b = 23; 85 | # int ccc = 23; 86 | AlignConsecutiveAssignments: true 87 | 88 | # AlignEscapedNewlinesLeft (bool) 89 | # If true, aligns escaped newlines as far left as possible. Otherwise puts them into the right-most column. 90 | AlignEscapedNewlinesLeft: true 91 | 92 | # AlignOperands (bool) 93 | # If true, horizontally align operands of binary and ternary expressions. 94 | # Specifically, this aligns operands of a single expression that needs to be split over multiple lines, e.g.: 95 | # int aaa = bbbbbbbbbbbbbbb + 96 | # ccccccccccccccc; 97 | AlignOperands: false 98 | 99 | # AlignTrailingComments (bool) 100 | # If true, aligns trailing comments. 101 | AlignTrailingComments: true 102 | 103 | # AllowAllParametersOfDeclarationOnNextLine (bool) 104 | # Allow putting all parameters of a function declaration onto the next line even if BinPackParameters is false. 105 | AllowAllParametersOfDeclarationOnNextLine: false 106 | 107 | # AllowShortBlocksOnASingleLine (bool) 108 | # Allows contracting simple braced statements to a single line. 109 | AllowShortBlocksOnASingleLine: false 110 | 111 | # AllowShortCaseLabelsOnASingleLine (bool) 112 | # If true, short case labels will be contracted to a single line. 113 | AllowShortCaseLabelsOnASingleLine: true 114 | 115 | # AllowShortFunctionsOnASingleLine (ShortFunctionStyle) 116 | # Dependent on the value, int f() { return 0; } can be put on a single line. 117 | # Possible values: 118 | # SFS_None (in configuration: None) Never merge functions into a single line. 119 | # SFS_Empty (in configuration: Empty) Only merge empty functions. 120 | # SFS_Inline (in configuration: Inline) Only merge functions defined inside a class. Implies “empty”. 121 | # SFS_All (in configuration: All) Merge all functions fitting on a single line. 122 | AllowShortFunctionsOnASingleLine: false 123 | 124 | # AllowShortIfStatementsOnASingleLine (bool) 125 | # If true, if (a) return; can be put on a single line. 126 | AllowShortIfStatementsOnASingleLine: true 127 | 128 | # AllowShortLoopsOnASingleLine (bool) 129 | # If true, while (true) continue; can be put on a single line. 130 | AllowShortLoopsOnASingleLine: true 131 | 132 | # AlwaysBreakBeforeMultilineStrings (bool) 133 | # If true, always break before multiline string literals. 134 | # This flag is mean to make cases where there are multiple multiline strings in a file look more consistent. Thus, it will only take effect if wrapping the string at that point leads to it being indented ContinuationIndentWidth spaces from the start of the line. 135 | AlwaysBreakBeforeMultilineStrings: false 136 | 137 | # AlwaysBreakTemplateDeclarations (bool) 138 | # If true, always break after the template<...> of a template declaration. 139 | AlwaysBreakTemplateDeclarations: false 140 | 141 | # BinPackArguments (bool) 142 | # If false, a function call’s arguments will either be all on the same line or will have one line each. 143 | #BinPackArguments: false 144 | 145 | # BinPackParameters (bool) 146 | # If false, a function declaration’s or function definition’s parameters will either all be on the same line or will have one line each. 147 | BinPackParameters: false 148 | 149 | # BraceWrapping (BraceWrappingFlags) 150 | # Control of individual brace wrapping cases. 151 | # If BreakBeforeBraces is set to BS_Custom, use this to specify how each individual brace case should be handled. Otherwise, this is ignored. 152 | # Nested configuration flags: 153 | # bool AfterClass Wrap class definitions. 154 | # bool AfterControlStatement Wrap control statements (if/for/while/switch/..). 155 | # bool AfterEnum Wrap enum definitions. 156 | # bool AfterFunction Wrap function definitions. 157 | # bool AfterNamespace Wrap namespace definitions. 158 | # bool AfterObjCDeclaration Wrap ObjC definitions (@autoreleasepool, interfaces, ..). 159 | # bool AfterStruct Wrap struct definitions. 160 | # bool AfterUnion Wrap union definitions. 161 | # bool BeforeCatch Wrap before catch. 162 | # bool BeforeElse Wrap before else. 163 | # bool IndentBraces Indent the wrapped braces themselves. 164 | #BraceWrapping: 165 | 166 | # BreakAfterJavaFieldAnnotations (bool) 167 | # Break after each annotation on a field in Java files. 168 | #BreakAfterJavaFieldAnnotations: 169 | 170 | # BreakBeforeBinaryOperators (BinaryOperatorStyle) 171 | # The way to wrap binary operators. 172 | # Possible values: 173 | # BOS_None (in configuration: None) Break after operators. 174 | # BOS_NonAssignment (in configuration: NonAssignment) Break before operators that aren’t assignments. 175 | # BOS_All (in configuration: All) Break before operators. 176 | BreakBeforeBinaryOperators: false 177 | 178 | # BreakBeforeBraces (BraceBreakingStyle) 179 | # The brace breaking style to use. 180 | # Possible values: 181 | # BS_Attach (in configuration: Attach) Always attach braces to surrounding context. 182 | # BS_Linux (in configuration: Linux) Like Attach, but break before braces on function, namespace and class definitions. 183 | # BS_Mozilla (in configuration: Mozilla) Like Attach, but break before braces on enum, function, and record definitions. 184 | # BS_Stroustrup (in configuration: Stroustrup) Like Attach, but break before function definitions, catch, and else. 185 | # BS_Allman (in configuration: Allman) Always break before braces. 186 | # BS_GNU (in configuration: GNU) Always break before braces and add an extra level of indentation to braces of control statements, not to those of class, function or other definitions. 187 | # BS_WebKit (in configuration: WebKit) Like Attach, but break before functions. 188 | # BS_Custom (in configuration: Custom) Configure each individual brace in BraceWrapping. 189 | BreakBeforeBraces: Attach 190 | 191 | # BreakBeforeTernaryOperators (bool) 192 | # If true, ternary operators will be placed after line breaks. 193 | BreakBeforeTernaryOperators: false 194 | 195 | # BreakConstructorInitializersBeforeComma (bool) 196 | # Always break constructor initializers before commas and align the commas with the colon. 197 | BreakConstructorInitializersBeforeComma: true 198 | 199 | # BreakStringLiterals (bool) 200 | # Allow breaking string literals when formatting. 201 | #BreakStringLiterals: 202 | 203 | # ColumnLimit (unsigned) 204 | # The column limit. 205 | # A column limit of 0 means that there is no column limit. In this case, clang-format will respect the input’s line breaking decisions within statements unless they contradict other rules. 206 | ColumnLimit: 80 207 | 208 | # CommentPragmas (std::string) 209 | # A regular expression that describes comments with special meaning, which should not be split into lines or otherwise changed. 210 | CommentPragmas: '' 211 | 212 | # ConstructorInitializerAllOnOneLineOrOnePerLine (bool) 213 | # If the constructor initializers don’t fit on a line, put each initializer on its own line. 214 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 215 | 216 | # ConstructorInitializerIndentWidth (unsigned) 217 | # The number of characters to use for indentation of constructor initializer lists. 218 | ConstructorInitializerIndentWidth: 0 219 | 220 | # ContinuationIndentWidth (unsigned) 221 | # Indent width for line continuations. 222 | ContinuationIndentWidth: 0 223 | 224 | # Cpp11BracedListStyle (bool) 225 | # If true, format braced lists as best suited for C++11 braced lists. 226 | # Important differences: - No spaces inside the braced list. - No line break before the closing brace. - Indentation with the continuation indent, not with the block indent. 227 | # Fundamentally, C++11 braced lists are formatted exactly like function calls would be formatted in their place. If the braced list follows a name (e.g. a type or variable name), clang-format formats as if the {} were the parentheses of a function call with that name. If there is no name, a zero-length name is assumed. 228 | Cpp11BracedListStyle: false 229 | 230 | # DerivePointerAlignment (bool) 231 | # If true, analyze the formatted file for the most common alignment of & and \*. PointerAlignment is then used only as fallback. 232 | DerivePointerBinding: false 233 | 234 | # DisableFormat (bool) 235 | # Disables formatting completely. 236 | #DisableFormat: 237 | 238 | # ExperimentalAutoDetectBinPacking (bool) 239 | # If true, clang-format detects whether function calls and definitions are formatted with one parameter per line. 240 | # Each call can be bin-packed, one-per-line or inconclusive. If it is inconclusive, e.g. completely on one line, but a decision needs to be made, clang-format analyzes whether there are other bin-packed cases in the input file and act accordingly. 241 | # NOTE: This is an experimental flag, that might go away or be renamed. Do not use this in config files, etc. Use at your own risk. 242 | #ExperimentalAutoDetectBinPacking: 243 | 244 | # ForEachMacros (std::vector) 245 | # A vector of macros that should be interpreted as foreach loops instead of as function calls. 246 | # These are expected to be macros of the form: 247 | # FOREACH(, ...) 248 | # 249 | # In the .clang-format configuration file, this can be configured like: 250 | # ForEachMacros: ['RANGES_FOR', 'FOREACH'] 251 | # For example: BOOST_FOREACH. 252 | #ForEachMacros: 253 | 254 | # IncludeCategories (std::vector) 255 | # Regular expressions denoting the different #include categories used for ordering #includes. 256 | # These regular expressions are matched against the filename of an include (including the <> or “”) in order. The value belonging to the first matching regular expression is assigned and #includes are sorted first according to increasing category number and then alphabetically within each category. 257 | # If none of the regular expressions match, INT_MAX is assigned as category. The main header for a source file automatically gets category 0. so that it is generally kept at the beginning of the #includes (http://llvm.org/docs/CodingStandards.html#include-style). However, you can also assign negative priorities if you have certain headers that always need to be first. 258 | # To configure this in the .clang-format file, use: 259 | # IncludeCategories: 260 | # - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 261 | # Priority: 2 262 | # - Regex: '^(<|"(gtest|isl|json)/)' 263 | # Priority: 3 264 | # - Regex: '.\*' 265 | # Priority: 1 266 | #IncludeCategories: 267 | 268 | # IndentCaseLabels (bool) 269 | # Indent case labels one level from the switch statement. 270 | # When false, use the same indentation level as for the switch statement. Switch statement body is always indented one level more than case labels. 271 | IndentCaseLabels: true 272 | 273 | # IndentFunctionDeclarationAfterType (bool) 274 | # If true, indent when breaking function declarations which are not also definitions after the type. 275 | IndentFunctionDeclarationAfterType: false 276 | 277 | # IndentWrappedFunctionNames (bool) 278 | # Indent if a function definition or declaration is wrapped after the type. 279 | #IndentWrappedFunctionNames: 280 | 281 | # KeepEmptyLinesAtTheStartOfBlocks (bool) 282 | # If true, empty lines at the start of blocks are kept. 283 | #KeepEmptyLinesAtTheStartOfBlocks: 284 | 285 | # MacroBlockBegin (std::string) 286 | # A regular expression matching macros that start a block. 287 | #MacroBlockBegin: 288 | 289 | # MacroBlockEnd (std::string) 290 | # A regular expression matching macros that end a block. 291 | #MacroBlockEnd: 292 | 293 | # MaxEmptyLinesToKeep (unsigned) 294 | # The maximum number of consecutive empty lines to keep. 295 | MaxEmptyLinesToKeep: 2 296 | 297 | # NamespaceIndentation (NamespaceIndentationKind) 298 | # The indentation used for namespaces. 299 | # Possible values: 300 | # NI_None (in configuration: None) Don’t indent in namespaces. 301 | # NI_Inner (in configuration: Inner) Indent only in inner namespaces (nested in other namespaces). 302 | # NI_All (in configuration: All) Indent in all namespaces. 303 | NamespaceIndentation: None 304 | 305 | # ObjCBlockIndentWidth (unsigned) 306 | # The number of characters to use for indentation of ObjC blocks. 307 | #ObjCBlockIndentWidth: 308 | 309 | # ObjCSpaceAfterProperty (bool) 310 | # Add a space after @property in Objective-C, i.e. use @property (readonly) instead of @property(readonly). 311 | ObjCSpaceAfterProperty: true 312 | 313 | # ObjCSpaceBeforeProtocolList (bool) 314 | # Add a space in front of an Objective-C protocol list, i.e. use Foo instead of Foo. 315 | ObjCSpaceBeforeProtocolList: true 316 | 317 | # PenaltyBreakBeforeFirstCallParameter (unsigned) 318 | # The penalty for breaking a function call after call(. 319 | PenaltyBreakBeforeFirstCallParameter: 100 320 | 321 | # PenaltyBreakComment (unsigned) 322 | # The penalty for each line break introduced inside a comment. 323 | PenaltyBreakComment: 100 324 | 325 | # PenaltyBreakFirstLessLess (unsigned) 326 | # The penalty for breaking before the first <<. 327 | PenaltyBreakFirstLessLess: 0 328 | 329 | # PenaltyBreakString (unsigned) 330 | # The penalty for each line break introduced inside a string literal. 331 | PenaltyBreakString: 100 332 | 333 | # PenaltyExcessCharacter (unsigned) 334 | # The penalty for each character outside of the column limit. 335 | PenaltyExcessCharacter: 1 336 | 337 | # PenaltyReturnTypeOnItsOwnLine (unsigned) 338 | # Penalty for putting the return type of a function onto its own line. 339 | PenaltyReturnTypeOnItsOwnLine: 20 340 | 341 | # PointerAlignment (PointerAlignmentStyle) 342 | # Pointer and reference alignment style. 343 | # Possible values: 344 | # PAS_Left (in configuration: Left) Align pointer to the left. 345 | # PAS_Right (in configuration: Right) Align pointer to the right. 346 | # PAS_Middle (in configuration: Middle) Align pointer in the middle. 347 | #PointerAlignment: 348 | 349 | # ReflowComments (bool) 350 | # If true, clang-format will attempt to re-flow comments. 351 | #ReflowComments: true (from v3.9) 352 | 353 | # SortIncludes (bool) 354 | # If true, clang-format will sort #includes. 355 | #SortIncludes: false (from v3.9) 356 | 357 | # SpaceAfterCStyleCast (bool) 358 | # If true, a space may be inserted after C style casts. 359 | SpaceAfterCStyleCast: false 360 | 361 | # SpaceBeforeAssignmentOperators (bool) 362 | # If false, spaces will be removed before assignment operators. 363 | SpaceBeforeAssignmentOperators: true 364 | 365 | # SpaceBeforeParens (SpaceBeforeParensOptions) 366 | # Defines in which cases to put a space before opening parentheses. 367 | # Possible values: 368 | # SBPO_Never (in configuration: Never) Never put a space before opening parentheses. 369 | # SBPO_ControlStatements (in configuration: ControlStatements) Put a space before opening parentheses only after control statement keywords (for/if/while...). 370 | # SBPO_Always (in configuration: Always) Always put a space before opening parentheses, except when it’s prohibited by the syntax rules (in function-like macro definitions) or when determined by other style rules (after unary operators, opening parentheses, etc.) 371 | SpaceBeforeParens: Always 372 | 373 | # SpaceInEmptyParentheses (bool) 374 | # If true, spaces may be inserted into (). 375 | SpaceInEmptyParentheses: false 376 | 377 | # SpacesBeforeTrailingComments (unsigned) 378 | # The number of spaces before trailing line comments (// - comments). 379 | # This does not affect trailing block comments (/* - comments) as those commonly have different usage patterns and a number of special cases. 380 | SpacesBeforeTrailingComments: 1 381 | 382 | # SpacesInAngles (bool) 383 | # If true, spaces will be inserted after < and before > in template argument lists. 384 | SpacesInAngles: false 385 | 386 | # SpacesInCStyleCastParentheses (bool) 387 | # If true, spaces may be inserted into C style casts. 388 | SpacesInCStyleCastParentheses: false 389 | 390 | # SpacesInContainerLiterals (bool) 391 | # If true, spaces are inserted inside container literals (e.g. ObjC and Javascript array and dict literals). 392 | SpacesInContainerLiterals: false 393 | 394 | # SpacesInParentheses (bool) 395 | # If true, spaces will be inserted after ( and before ). 396 | SpacesInParentheses: false 397 | 398 | # SpacesInSquareBrackets (bool) 399 | # If true, spaces will be inserted after [ and before ]. 400 | SpacesInSquareBrackets: false 401 | -------------------------------------------------------------------------------- /.clang_complete: -------------------------------------------------------------------------------- 1 | -Isource 2 | -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | name: Articated CI 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: ghcr.io/derpicated/articated-ci-qt6:latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Run pre-commit 14 | run: pre-commit run --all-files 15 | 16 | - uses: actions/cache@v3 17 | with: 18 | path: ~/.cache/pre-commit 19 | key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} 20 | 21 | build: 22 | needs: [ lint ] 23 | runs-on: ubuntu-latest 24 | container: 25 | image: ghcr.io/derpicated/articated-ci-qt6:latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | - name: Checkout submodules 30 | run: git submodule update --init --recursive 31 | 32 | - name: Create build dir 33 | run: mkdir ./build 34 | 35 | - name: Configure 36 | working-directory: ./build 37 | run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DTESTS=ON 38 | 39 | - name: Make 40 | working-directory: ./build 41 | run: make 42 | 43 | - name: Test 44 | working-directory: ./build 45 | run: make check 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### C++ ### 2 | # Prerequisites 3 | *.d 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | 36 | ### OpenCV ### 37 | #OpenCV for Mac and Linux 38 | #build and release folders 39 | */CMakeFiles 40 | */CMakeCache.txt 41 | */Makefile 42 | */cmake_install.cmake 43 | .DS_Store 44 | 45 | 46 | ### Qt ### 47 | # C++ objects and libs 48 | 49 | *.slo 50 | *.lo 51 | *.o 52 | *.a 53 | *.la 54 | *.lai 55 | *.so 56 | *.dll 57 | *.dylib 58 | 59 | # Qt-es 60 | 61 | /.qmake.cache 62 | /.qmake.stash 63 | *.pro.user 64 | *.pro.user.* 65 | *.qbs.user 66 | *.qbs.user.* 67 | *.moc 68 | *_automoc.cpp 69 | *_automoc.cxx 70 | moc_*.cpp 71 | moc_*.cxx 72 | moc_*.cxx_parameters 73 | qrc_*.cpp 74 | ui_*.h 75 | Makefile* 76 | *build-* 77 | 78 | # QtCreator 79 | 80 | *.autosave 81 | 82 | # QtCtreator Qml 83 | *.qmlproject.user 84 | *.qmlproject.user.* 85 | 86 | # QtCtreator CMake 87 | CMakeLists.txt.user* 88 | 89 | 90 | ### CMake ### 91 | CMakeCache.txt 92 | CMakeFiles 93 | CMakeScripts 94 | Makefile 95 | cmake_install.cmake 96 | install_manifest.txt 97 | CTestTestfile.cmake 98 | 99 | ### CLion ### 100 | .idea 101 | 102 | # File-based project format 103 | *.iws 104 | 105 | # IntelliJ 106 | out/ 107 | 108 | # Crashlytics plugin (for Android Studio and IntelliJ) 109 | com_crashlytics_export_strings.xml 110 | crashlytics.properties 111 | crashlytics-build.properties 112 | fabric.properties 113 | 114 | ### output folders ### 115 | # Build 116 | cmake-build-*/ 117 | build/* 118 | docs/build* 119 | build_* 120 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "qt-android-cmake"] 2 | path = qt-android-cmake 3 | url = https://github.com/derpicated/qt-android-cmake.git 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v3.4.0 5 | hooks: 6 | - id: check-merge-conflict 7 | - id: check-added-large-files 8 | - id: check-merge-conflict 9 | - id: trailing-whitespace 10 | 11 | - repo: https://github.com/igorshubovych/markdownlint-cli 12 | rev: v0.26.0 13 | hooks: 14 | - id: markdownlint 15 | 16 | # - repo: https://github.com/cheshirekow/cmake-format-precommit 17 | # rev: v0.6.13 18 | # hooks: 19 | # - id: cmake-lint 20 | 21 | # - repo: https://github.com/pocc/pre-commit-hooks 22 | # rev: v1.1.1 23 | # hooks: 24 | # - id: clang-tidy 25 | # - id: cppcheck 26 | 27 | # - repo: https://github.com/bmorcos/pre-commit-hooks-cpp 28 | # rev: 9a5aa38207bf557961110d6a4f7e3a9d352911f9 29 | # hooks: 30 | # - id: cpplint 31 | 32 | #- repo: https://github.com/Mercotui/pre-commit-qmllint 33 | # rev: v1.0.0 34 | # hooks: 35 | # - id: qmllint 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING 4 | "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel.") 5 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 6 | Debug Release RelWithDebInfo MinSizeRel) 7 | 8 | # options 9 | option(ANDROID "switch to android build" OFF) 10 | option(DISABLE_ARTICATED_TESTS "Disable the ARticated unittests" OFF) 11 | 12 | if(ANDROID) 13 | set(ANDROID_NATIVE_API_LEVEL "27" CACHE STRING 14 | "Choose the Android NDK Native APIs version to use.") 15 | else(ANDROID) 16 | endif(ANDROID) 17 | 18 | project(articated_app) 19 | include(cmake/GitSemver.cmake) 20 | include_directories(source) 21 | add_subdirectory(source) 22 | 23 | if(NOT DISABLE_ARTICATED_TESTS) 24 | enable_testing() 25 | 26 | include(FetchContent) 27 | FetchContent_Declare( 28 | googletest 29 | GIT_REPOSITORY https://github.com/google/googletest.git 30 | GIT_TAG main 31 | ) 32 | FetchContent_GetProperties(googletest) 33 | if(NOT googletest_POPULATED) 34 | message("Downloading googletest") 35 | FetchContent_Populate(googletest) 36 | add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 37 | include_directories(${googletest_SOURCE_DIR}/include ${googletest_SOURCE_DIR}) 38 | endif() 39 | 40 | add_subdirectory(tests) 41 | add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure) 42 | endif(NOT DISABLE_ARTICATED_TESTS) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Derpicated 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARticated 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) 4 | ![Articated CI](https://github.com/derpicated/articated/workflows/Articated%20CI/badge.svg) 5 | 6 | An augmented reality application. 7 | 8 | ![ARticated](docs/images/application/articated_application.jpg) 9 | 10 | This augmented reality application uses ONLY camera images and 11 | some fancy math to track markers across 3D space. 12 | It then draws virtual 3D objects as if they were part of reality. 13 | 14 | ## Developers 15 | 16 | This application was developed as an educational project for two 17 | Embedded Systems Engineering students, for their minor in Embedded Vision. 18 | All vision processing, 2D to 3D transformation maths, 19 | and rendering code has been written from scratch by: 20 | 21 | - [Ingmar Delsink](https://github.com/idelsink) 22 | - [Menno van der Graaf](https://github.com/Mercotui) 23 | 24 | ## Usage 25 | 26 | Open the application, whether it be on desktop or an android phone, 27 | and point the camera so that at least 3 markers are clearly in view. 28 | Press the largest button to set the current position as reference. 29 | Now you can move the camera around, keeping at least 3 markers in view. 30 | 31 | ## Markers 32 | 33 | The application needs to track at least 3 unique markers to calculate 34 | the camera movement, although more can be used. 35 | 36 | These markers are loosely defined groups of black dots on a white background. 37 | The markers are identified by their dot count, so these have to be unique. 38 | A minimum of 2 and a maximum of 9 dots are required for a marker to be valid. 39 | 40 | See below, the markers can be drawn by hand. 41 | This configuration features marker #3, #4, and #5. 42 | Make sure to leave plenty of white-space between each marker, 43 | so they don't merge together. 44 | 45 | ![Markers](docs/images/drawn_markers.jpg) 46 | 47 | ## Dependencies 48 | 49 | To build ARticated, you need the following: 50 | 51 | - Qt 5.14 52 | - OpenGL 4.1 53 | 54 | ### On Fedora 55 | 56 | Install the following libraries on Fedora: 57 | 58 | ```sh 59 | dnf install \ 60 | qt5-qtbase-devel \ 61 | qt5-qtbase-gui \ 62 | qt5-qtquickcontrols2-devel \ 63 | qt5-qtmultimedia-devel 64 | 65 | ``` 66 | 67 | ## Building 68 | 69 | Also see [Building For Android](docs/building-android.md). 70 | 71 | When building this application for desktop, pass the following CMake variables: 72 | 73 | | Variable | Type | Description | 74 | |:-------------------|:-----|:------------------------------| 75 | | Qt5_DIR (Optional) | PATH | Path to the Qt CMake directory| 76 | 77 | ### Example 78 | 79 | ```sh 80 | mkdir build && cd build 81 | 82 | cmake .. -DQt5_DIR=/home/user/Qt/5.7/gcc_64/lib/cmake/Qt5 83 | 84 | make 85 | ``` 86 | 87 | ## License 88 | 89 | > You can check out the full license [here](./LICENSE) 90 | 91 | This project is licensed under the terms of the **MIT** license. 92 | -------------------------------------------------------------------------------- /cmake/GitSemver.cmake: -------------------------------------------------------------------------------- 1 | # Get the Semantic Versioning from git using CMake 2 | # 3 | # The idea was "taken" from: 4 | # The stlink project (github.com/texane/stlink) 5 | # See: https://github.com/texane/stlink/blob/master/LICENSE 6 | # 7 | # The following use cases work: 8 | # - Dirty (sources changed) 9 | # - No tags in repo (git hash is used) 10 | # - Tags as: v1.0.0, 1.0.0 11 | 12 | include(${CMAKE_CURRENT_LIST_DIR}/StringToSemver.cmake) 13 | 14 | # Find the git program 15 | function(find_git) 16 | find_program(git 17 | NAMES 18 | "git" # Linux, Windows 19 | ) 20 | if(NOT git) 21 | message(WARNING "Could not find the git executable") 22 | endif() 23 | endfunction() 24 | 25 | # Arguments: 26 | # MAJOR: Variable name for the major version return value 27 | # MINOR: Variable name for the minor version return value 28 | # PATCH: Variable name for the patch version return value 29 | # STRING: Variable name for the string version return value 30 | # PATH: Path where the git command is executed 31 | function(git_semver) 32 | set(options) 33 | set(oneValueArgs MAJOR MINOR PATCH STRING PATH) 34 | set(multiValueArgs) 35 | cmake_parse_arguments(git_semver "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 36 | 37 | if(NOT git_semver_MAJOR) 38 | message(WARNING "No MAJOR variable specified") 39 | set(git_semver_MAJOR "__major") 40 | endif() 41 | if(NOT git_semver_MINOR) 42 | message(WARNING "No MINOR variable specified") 43 | set(git_semver_MINOR "__minor") 44 | endif() 45 | if(NOT git_semver_PATCH) 46 | message(WARNING "No PATCH variable specified") 47 | set(git_semver_PATCH "__patch") 48 | endif() 49 | if(NOT git_semver_STRING) 50 | message(WARNING "No STRING variable specified") 51 | set(git_semver_STRING "__string") 52 | endif() 53 | if(NOT git_semver_PATH) 54 | message(WARNING "No PATH variable specified; Using CMAKE_CURRENT_LIST_DIR: (${CMAKE_CURRENT_LIST_DIR})") 55 | set(git_semver_PATH "${CMAKE_CURRENT_LIST_DIR}") 56 | endif() 57 | 58 | # Set default values for variables 59 | set(${git_semver_MAJOR} "0") 60 | set(${git_semver_MINOR} "0") 61 | set(${git_semver_PATCH} "0") 62 | set(${git_semver_STRING} "") 63 | 64 | find_git() 65 | if(git) 66 | # Check if HEAD is pointing to a tag 67 | execute_process ( 68 | COMMAND "${git}" describe --tags --always 69 | WORKING_DIRECTORY "${git_semver_PATH}" 70 | OUTPUT_VARIABLE "${git_semver_STRING}" 71 | OUTPUT_STRIP_TRAILING_WHITESPACE) 72 | # If the sources have been changed locally, add -dirty to the version. 73 | execute_process ( 74 | COMMAND "${git}" diff --quiet 75 | WORKING_DIRECTORY "${git_semver_PATH}" 76 | RESULT_VARIABLE res) 77 | 78 | if (res EQUAL 1) 79 | set (${git_semver_STRING} "${${git_semver_STRING}}-dirty") 80 | endif() 81 | 82 | semver(${${git_semver_STRING}} ${git_semver_MAJOR} ${git_semver_MINOR} ${git_semver_PATCH}) 83 | endif() 84 | 85 | # Apply variables to parent scope 86 | set(${git_semver_MAJOR} ${${git_semver_MAJOR}} PARENT_SCOPE) 87 | set(${git_semver_MINOR} ${${git_semver_MINOR}} PARENT_SCOPE) 88 | set(${git_semver_PATCH} ${${git_semver_PATCH}} PARENT_SCOPE) 89 | set(${git_semver_STRING} ${${git_semver_STRING}} PARENT_SCOPE) 90 | endfunction() 91 | -------------------------------------------------------------------------------- /cmake/StringToSemver.cmake: -------------------------------------------------------------------------------- 1 | # Get the Semantic Version from a string in to form of: 2 | # - x1.2.3 3 | # - 1.2.3 4 | # 5 | 6 | # v_string_: The string where the version needs to be extracted 7 | # v_major_: Variable name for the major version 8 | # v_minor_: Variable name for the minor version 9 | # v_patch_: Variable name for the patch version 10 | function(semver v_string_ v_major_ v_minor_ v_patch_) 11 | # Set default values for variables 12 | set(${v_major_} "0") 13 | set(${v_minor_} "0") 14 | set(${v_patch_} "0") 15 | 16 | # Get major, minor and patch versions 17 | string( 18 | REGEX REPLACE 19 | "(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$" 20 | "\\1;\\2;\\3" 21 | match_version_list_ 22 | ${v_string_} 23 | ) 24 | list(LENGTH match_version_list_ len) 25 | if(len EQUAL 3) 26 | list(GET match_version_list_ 0 ${v_major_}) 27 | list(GET match_version_list_ 1 ${v_minor_}) 28 | list(GET match_version_list_ 2 ${v_patch_}) 29 | endif() 30 | # Drop character(s) before the version number of major 31 | string( 32 | REGEX REPLACE 33 | "^[^[1-9]]*([1-9]+)" 34 | "\\1;" 35 | match_version_list_ 36 | ${${v_major_}} 37 | ) 38 | list(GET match_version_list_ 0 ${v_major_}) 39 | 40 | # Apply variables to parent scope 41 | set(${v_major_} ${${v_major_}} PARENT_SCOPE) 42 | set(${v_minor_} ${${v_minor_}} PARENT_SCOPE) 43 | set(${v_patch_} ${${v_patch_}} PARENT_SCOPE) 44 | set(${v_string_} ${${v_string_}} PARENT_SCOPE) 45 | endfunction() 46 | -------------------------------------------------------------------------------- /docs/building-android.md: -------------------------------------------------------------------------------- 1 | # Building For Android 2 | 3 | Building for android is currently untested and potentially problematic. 4 | 5 | ## Dependencies 6 | 7 | - Qt 5.14 8 | - OpenGL-ES 3.0 9 | - Android SDK + NDK 10 | 11 | ## Building 12 | 13 | When building this application for Android, pass the following CMake variables: 14 | 15 | | Variable | Type | Description | 16 | |:--------------------|:-------|:--------------------------------| 17 | | Qt5_DIR | PATH | Path to the Qt CMake directory. | 18 | | QT_ANDROID_SDK_ROOT | PATH | Path to the Android SDK root | 19 | | ANDROID_NDK | PATH | Path to the Android NDK root | 20 | | JAVA_HOME | PATH | Path to the Java root | 21 | | ANDROID | ON/OFF | Build for Android | 22 | 23 | ### Example 24 | 25 | ```sh 26 | mkdir build-android && cd build-android 27 | 28 | cmake .. \ 29 | -DCMAKE_TOOLCHAIN_FILE=Sdk/ndk-bundle/build/cmake/android.toolchain.cmake \ 30 | -DANDROID=ON \ 31 | -DQt5_DIR=/home/user/Qt/5.14.1/android_armv7/lib/cmake/Qt5 \ 32 | -DQT_ANDROID_SDK_ROOT=Sdk \ 33 | -DANDROID_NDK=Sdk/ndk-bundle \ 34 | -DJAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk 35 | 36 | make 37 | ``` 38 | 39 | ## Extra note 40 | 41 | When switching targets, make sure to delete the old build files. So an 42 | `rm -rf ./` on the build directory would suffice. Or for convenience, two build 43 | folders can be made, one for android and one for desktop. This way switching to 44 | a different target is a mere switching of directories. 45 | -------------------------------------------------------------------------------- /docs/images/application/articated_application.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/docs/images/application/articated_application.jpg -------------------------------------------------------------------------------- /docs/images/application/articated_application_menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/docs/images/application/articated_application_menu.jpg -------------------------------------------------------------------------------- /docs/images/application/articated_application_no_cam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/docs/images/application/articated_application_no_cam.jpg -------------------------------------------------------------------------------- /docs/images/application/articated_application_test_video.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/docs/images/application/articated_application_test_video.jpg -------------------------------------------------------------------------------- /docs/images/drawn_markers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/docs/images/drawn_markers.jpg -------------------------------------------------------------------------------- /res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /resources/3D_models/3D_models.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | articated.obj 4 | piramid.obj 5 | shuttle.obj 6 | teapot.obj 7 | nothing.obj 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/3D_models/articated.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.78 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib articated.mtl 4 | o articated_logo 5 | v 1.769644 0.393449 -2.617397 6 | v 1.706920 -0.002659 -2.157976 7 | v 1.709536 0.392630 -2.156931 8 | v 1.767029 -0.001840 -2.618441 9 | v 0.881922 0.395250 -1.075888 10 | v 1.247401 -0.001727 -1.360032 11 | v 0.879306 -0.000039 -1.076932 12 | v 1.250017 0.393563 -1.358987 13 | v 1.708530 0.395070 -3.077727 14 | v 1.705915 -0.000220 -3.078772 15 | v 1.532300 0.392669 -1.727712 16 | v 1.529685 -0.002621 -1.728756 17 | v -0.471431 0.412811 -4.333142 18 | v -0.902867 0.019888 -4.156003 19 | v -0.474046 0.017522 -4.334186 20 | v -0.900252 0.415178 -4.154959 21 | v 0.878558 0.403413 -4.156932 22 | v 0.446733 0.011432 -4.335208 23 | v 0.875942 0.008123 -4.157977 24 | v 0.449349 0.406721 -4.334164 25 | v 1.530358 0.397382 -3.506554 26 | v 1.527742 0.002092 -3.507598 27 | v -1.730481 0.022508 -3.074959 28 | v -1.787974 0.416979 -2.613450 29 | v -1.790590 0.021689 -2.614494 30 | v -1.727866 0.417798 -3.073915 31 | v 1.247270 0.400228 -3.874650 32 | v 1.244654 0.004938 -3.875694 33 | v -1.551303 0.017757 -1.725338 34 | v -1.265599 0.410201 -1.356197 35 | v -1.268215 0.014911 -1.357241 36 | v -1.548688 0.413046 -1.724293 37 | v -1.726860 0.415358 -2.153119 38 | v -1.729475 0.020069 -2.154163 39 | v -0.013723 0.014637 -4.395309 40 | v -0.011107 0.409927 -4.394265 41 | v -1.553246 0.022469 -3.504179 42 | v -1.550630 0.417759 -3.503135 43 | v -1.270962 0.021576 -3.872903 44 | v -1.268346 0.416866 -3.871859 45 | v -0.899503 0.011726 -1.074959 46 | v -0.896888 0.407015 -1.073914 47 | v 0.847570 0.003633 -2.387222 48 | v 0.761568 0.398942 -2.171567 49 | v 0.758952 0.003652 -2.172612 50 | v 0.850185 0.398922 -2.386177 51 | v 0.616437 0.007431 -3.246081 52 | v 0.760597 0.401298 -3.060988 53 | v 0.757981 0.006008 -3.062033 54 | v 0.619052 0.402721 -3.245037 55 | v 0.620426 0.399388 -1.987205 56 | v 0.617810 0.004099 -1.988250 57 | v 0.433763 0.004943 -1.846700 58 | v 0.436378 0.400232 -1.845656 59 | v -0.453026 0.406115 -1.844669 60 | v -0.639998 0.012418 -1.986854 61 | v -0.455642 0.010825 -1.845713 62 | v -0.637382 0.407708 -1.985810 63 | v -0.779897 0.411487 -3.059279 64 | v -0.871131 0.016216 -2.845714 65 | v -0.868515 0.411506 -2.844669 66 | v -0.782513 0.016197 -3.060324 67 | v -0.868013 0.410286 -2.384271 68 | v -0.781542 0.013841 -2.170903 69 | v -0.778926 0.409130 -2.169858 70 | v -0.870628 0.014997 -2.385315 71 | v -0.898570 0.411097 -2.614437 72 | v -0.901185 0.015807 -2.615481 73 | v -0.638756 0.411040 -3.243641 74 | v -0.641371 0.015750 -3.244685 75 | v -0.457324 0.014906 -3.386235 76 | v -0.454708 0.410196 -3.385191 77 | v -0.242913 0.013723 -3.475327 78 | v -0.240298 0.409013 -3.474283 79 | v -0.010136 0.407571 -3.504845 80 | v 0.217476 0.010678 -3.475838 81 | v -0.012752 0.012281 -3.505889 82 | v 0.220092 0.405968 -3.474793 83 | v 0.849683 0.400142 -2.846576 84 | v 0.847067 0.004852 -2.847620 85 | v 0.432081 0.009024 -3.387222 86 | v 0.434696 0.404314 -3.386178 87 | v 0.880240 0.399332 -2.616410 88 | v 0.877624 0.004042 -2.617455 89 | v -4.096736 0.024871 1.956527 90 | v -3.915947 0.417849 2.386397 91 | v -3.918563 0.022560 2.385353 92 | v -4.094121 0.420161 1.957572 93 | v -3.267512 0.419981 -0.044268 94 | v -3.638222 0.026379 0.237787 95 | v -3.270127 0.024691 -0.045313 96 | v -3.635606 0.421668 0.238832 97 | v -3.632860 0.415004 2.754494 98 | v -3.635475 0.019714 2.753449 99 | v -3.266763 0.016528 3.035732 100 | v -3.264147 0.411818 3.036776 101 | v -1.485338 0.400053 3.034803 102 | v -1.119859 0.003076 2.750659 103 | v -1.487954 0.004763 3.033758 104 | v -1.117244 0.398366 2.751703 105 | v -0.834960 0.397472 2.382979 106 | v -0.837575 0.002182 2.381935 107 | v -2.837554 0.013220 3.212963 108 | v -2.834938 0.408510 3.214007 109 | v -2.374483 0.405304 3.274109 110 | v -1.916774 0.007130 3.211941 111 | v -2.377099 0.010014 3.273065 112 | v -1.914159 0.402420 3.212986 113 | v -3.920506 0.027272 0.606511 114 | v -4.095126 0.422601 1.036775 115 | v -4.097741 0.027311 1.035731 116 | v -3.917890 0.422562 0.607555 117 | v -4.157850 0.026492 1.496196 118 | v -4.155234 0.421782 1.497241 119 | v -0.657724 0.397433 1.953759 120 | v -0.660340 0.002144 1.952715 121 | v -0.597616 0.398252 1.493294 122 | v -0.600231 0.002962 1.492249 123 | v -1.119991 0.405030 0.236041 124 | v -1.491318 0.012926 -0.047286 125 | v -1.122606 0.009741 0.234996 126 | v -1.488702 0.408216 -0.046242 127 | v -0.839518 0.006895 0.603093 128 | v -0.836902 0.402185 0.604137 129 | v -0.658730 0.399873 1.032963 130 | v -0.661345 0.004583 1.031918 131 | v -3.235273 0.415089 1.726419 132 | v -3.148802 0.018643 1.939788 133 | v -3.146187 0.413933 1.940832 134 | v -3.237888 0.019799 1.725375 135 | v -2.375454 0.407661 2.384688 136 | v -2.608297 0.013974 2.353593 137 | v -2.378069 0.012371 2.383644 138 | v -2.605682 0.409263 2.354637 139 | v -1.608308 0.008455 1.938079 140 | v -1.746834 0.404191 2.123485 141 | v -1.749450 0.008902 2.122441 142 | v -1.605692 0.403744 1.939123 143 | v -1.519690 0.008435 1.723469 144 | v -1.517075 0.403725 1.724513 145 | v -1.932564 0.409116 0.724513 146 | v -1.750823 0.012234 0.864610 147 | v -1.935179 0.013827 0.723468 148 | v -1.748208 0.407524 0.865654 149 | v -1.489636 0.008845 1.493236 150 | v -1.487020 0.404135 1.494280 151 | v -1.930882 0.405035 2.265035 152 | v -2.147907 0.010929 2.353082 153 | v -1.933497 0.009745 2.263991 154 | v -2.145292 0.406218 2.354126 155 | v -2.820286 0.410918 2.266022 156 | v -3.007258 0.017221 2.123836 157 | v -2.822902 0.015628 2.264977 158 | v -3.004642 0.412510 2.124881 159 | v -1.520193 0.009655 1.263071 160 | v -1.517577 0.404945 1.264115 161 | v -3.265830 0.415899 1.496254 162 | v -3.268445 0.020610 1.495210 163 | v -3.006016 0.415843 0.867049 164 | v -3.149773 0.021000 1.050367 165 | v -3.147158 0.416290 1.051411 166 | v -3.008631 0.020553 0.866005 167 | v -1.609279 0.010811 1.048658 168 | v -1.606663 0.406101 1.049702 169 | v -3.238391 0.021019 1.264977 170 | v -3.235775 0.416309 1.266021 171 | v -2.824584 0.019709 0.724455 172 | v -2.821969 0.414999 0.725500 173 | v 1.921523 0.383626 0.720237 174 | v 0.014750 3.560277 0.131638 175 | v 2.367066 0.378644 1.490005 176 | v 1.475980 0.388608 -0.049531 177 | v -0.009165 0.405214 -2.615424 178 | v 0.881922 0.395250 -1.075888 179 | v 0.436378 0.400232 -1.845656 180 | v -1.932564 0.409116 0.724513 181 | v -2.376425 0.410017 1.495267 182 | v -1.488702 0.408216 -0.046242 183 | v -0.896888 0.407015 -1.073914 184 | v -0.453026 0.406115 -1.844669 185 | v 1.477662 0.384526 1.490991 186 | v 0.588257 0.390409 1.491978 187 | v -0.597616 0.398252 1.493294 188 | v -1.487020 0.404135 1.494280 189 | v 4.082146 -0.026790 1.026656 190 | v 4.083151 -0.029229 1.947452 191 | v 4.143260 -0.028411 1.486987 192 | v 3.905916 -0.029191 2.376672 193 | v 3.903974 -0.024478 0.597830 194 | v 3.623632 -0.028297 2.745396 195 | v 3.620886 -0.021632 0.229734 196 | v 3.255538 -0.026609 3.028496 197 | v 3.252173 -0.018447 -0.052549 198 | v 3.253856 -0.022528 1.487973 199 | v 3.223299 -0.021718 1.257808 200 | v 2.822964 -0.015138 -0.229780 201 | v 3.134212 -0.020562 1.043395 202 | v 2.992668 -0.019139 0.859347 203 | v 2.808312 -0.017546 0.718206 204 | v 2.362509 -0.011933 -0.289882 205 | v 2.593708 -0.015892 0.629590 206 | v 2.363480 -0.014289 0.599539 207 | v 2.133318 -0.012847 0.630101 208 | v 1.902185 -0.009049 -0.228758 209 | v 1.918908 -0.011664 0.719192 210 | v 1.473364 -0.006682 -0.050575 211 | v 3.223801 -0.022938 1.718206 212 | v 2.826717 -0.024243 3.206679 213 | v 3.135184 -0.022918 1.932816 214 | v 2.994042 -0.022471 2.117178 215 | v 2.809994 -0.021628 2.258728 216 | v 2.366393 -0.021358 3.267802 217 | v 2.595584 -0.020444 2.347819 218 | v 2.365422 -0.019002 2.378381 219 | v 1.905937 -0.018153 3.207700 220 | v 2.135194 -0.017399 2.348330 221 | v 1.920590 -0.015745 2.259715 222 | v 1.736234 -0.014152 2.118573 223 | v 1.476728 -0.014845 3.030469 224 | v 1.594689 -0.012730 1.934525 225 | v 1.505603 -0.011574 1.720112 226 | v 1.475046 -0.010763 1.489947 227 | v 1.108016 -0.011659 2.748187 228 | v 0.585642 -0.004881 1.490934 229 | v 0.824928 -0.008813 2.380090 230 | v 0.646756 -0.006501 1.951265 231 | v 3.258153 0.368680 3.029540 232 | v 3.226417 0.372352 1.719250 233 | v 3.256471 0.372762 1.489018 234 | v 2.829332 0.371047 3.207723 235 | v 3.137799 0.372372 1.933860 236 | v 2.996657 0.372818 2.118222 237 | v 2.812610 0.373662 2.259772 238 | v 2.369009 0.373931 3.268847 239 | v 2.598199 0.374846 2.348864 240 | v 2.368038 0.376288 2.379426 241 | v 1.908553 0.377137 3.208745 242 | v 2.137810 0.377890 2.349375 243 | v 1.923205 0.379545 2.260759 244 | v 1.738849 0.381137 2.119618 245 | v 1.479344 0.380445 3.031513 246 | v 1.597305 0.382560 1.935570 247 | v 1.508219 0.383716 1.721157 248 | v 1.477662 0.384526 1.490991 249 | v 1.110632 0.383631 2.749231 250 | v 0.588257 0.390409 1.491978 251 | v 0.827544 0.386476 2.381135 252 | v 0.649371 0.388788 1.952309 253 | v 4.085767 0.366060 1.948496 254 | v 4.084762 0.368500 1.027700 255 | v 4.145876 0.366879 1.488031 256 | v 3.908532 0.366099 2.377716 257 | v 3.906590 0.370812 0.598875 258 | v 3.626248 0.366993 2.746440 259 | v 3.623501 0.373657 0.230778 260 | v 3.254789 0.376843 -0.051504 261 | v 3.225914 0.373572 1.258852 262 | v 2.825580 0.380151 -0.228736 263 | v 3.136828 0.374728 1.044440 264 | v 2.995284 0.376151 0.860391 265 | v 2.810928 0.377744 0.719250 266 | v 2.365124 0.383357 -0.288837 267 | v 2.596323 0.379398 0.630634 268 | v 2.366095 0.381000 0.600584 269 | v 2.135934 0.382443 0.631145 270 | v 1.904801 0.386241 -0.227714 271 | v 1.921523 0.383626 0.720237 272 | v 1.475980 0.388608 -0.049531 273 | usemtl Mat_3_-1 274 | s 1 275 | f 1 2 3 276 | f 2 1 4 277 | f 5 6 7 278 | f 6 5 8 279 | f 9 4 1 280 | f 4 9 10 281 | f 11 6 8 282 | f 6 11 12 283 | f 3 12 11 284 | f 12 3 2 285 | f 13 14 15 286 | f 14 13 16 287 | f 17 18 19 288 | f 18 17 20 289 | f 21 10 9 290 | f 10 21 22 291 | f 23 24 25 292 | f 24 23 26 293 | f 27 19 28 294 | f 19 27 17 295 | f 29 30 31 296 | f 30 29 32 297 | f 25 33 34 298 | f 33 25 24 299 | f 20 35 18 300 | f 35 20 36 301 | f 37 26 23 302 | f 26 37 38 303 | f 27 22 21 304 | f 22 27 28 305 | f 16 39 14 306 | f 39 16 40 307 | f 39 38 37 308 | f 38 39 40 309 | f 30 41 31 310 | f 41 30 42 311 | f 36 15 35 312 | f 15 36 13 313 | f 34 32 29 314 | f 32 34 33 315 | f 43 44 45 316 | f 44 43 46 317 | f 47 48 49 318 | f 48 47 50 319 | f 45 51 52 320 | f 51 45 44 321 | f 51 53 52 322 | f 53 51 54 323 | f 55 56 57 324 | f 56 55 58 325 | f 59 60 61 326 | f 60 59 62 327 | f 63 64 65 328 | f 64 63 66 329 | f 67 66 63 330 | f 66 67 68 331 | f 61 68 67 332 | f 68 61 60 333 | f 69 62 59 334 | f 62 69 70 335 | f 69 71 70 336 | f 71 69 72 337 | f 72 73 71 338 | f 73 72 74 339 | f 75 76 77 340 | f 76 75 78 341 | f 49 79 80 342 | f 79 49 48 343 | f 65 56 58 344 | f 56 65 64 345 | f 74 77 73 346 | f 77 74 75 347 | f 78 81 76 348 | f 81 78 82 349 | f 82 47 81 350 | f 47 82 50 351 | f 80 83 84 352 | f 83 80 79 353 | f 84 46 43 354 | f 46 84 83 355 | f 53 5 7 356 | f 5 53 54 357 | f 55 41 42 358 | f 41 55 57 359 | f 10 2 4 360 | f 2 10 22 361 | f 2 22 12 362 | f 12 22 6 363 | f 6 22 28 364 | f 6 28 7 365 | f 7 28 84 366 | f 84 28 19 367 | f 84 19 80 368 | f 80 19 18 369 | f 80 18 49 370 | f 49 18 47 371 | f 47 18 81 372 | f 81 18 35 373 | f 81 35 76 374 | f 76 35 77 375 | f 77 35 73 376 | f 73 35 15 377 | f 73 15 71 378 | f 71 15 70 379 | f 70 15 14 380 | f 70 14 62 381 | f 62 14 60 382 | f 60 14 68 383 | f 68 14 39 384 | f 68 39 31 385 | f 31 39 29 386 | f 29 39 37 387 | f 29 37 23 388 | f 29 23 34 389 | f 34 23 25 390 | f 56 41 57 391 | f 41 56 64 392 | f 41 64 66 393 | f 41 66 68 394 | f 41 68 31 395 | f 7 52 53 396 | f 52 7 45 397 | f 45 7 43 398 | f 43 7 84 399 | f 5 46 83 400 | f 46 5 44 401 | f 44 5 51 402 | f 51 5 54 403 | f 42 58 55 404 | f 58 42 65 405 | f 65 42 63 406 | f 63 42 67 407 | f 67 42 30 408 | f 3 9 1 409 | f 9 3 21 410 | f 21 3 11 411 | f 21 11 8 412 | f 21 8 27 413 | f 27 8 5 414 | f 27 5 83 415 | f 27 83 17 416 | f 17 83 79 417 | f 17 79 20 418 | f 20 79 48 419 | f 20 48 50 420 | f 20 50 82 421 | f 20 82 36 422 | f 36 82 78 423 | f 36 78 75 424 | f 36 75 74 425 | f 36 74 13 426 | f 13 74 72 427 | f 13 72 69 428 | f 13 69 16 429 | f 16 69 59 430 | f 16 59 61 431 | f 16 61 67 432 | f 16 67 40 433 | f 40 67 30 434 | f 40 30 32 435 | f 40 32 38 436 | f 38 32 26 437 | f 26 32 33 438 | f 26 33 24 439 | usemtl Mat_1_-1 440 | f 85 86 87 441 | f 86 85 88 442 | f 89 90 91 443 | f 90 89 92 444 | f 87 93 94 445 | f 93 87 86 446 | f 93 95 94 447 | f 95 93 96 448 | f 97 98 99 449 | f 98 97 100 450 | f 101 98 100 451 | f 98 101 102 452 | f 96 103 95 453 | f 103 96 104 454 | f 105 106 107 455 | f 106 105 108 456 | f 104 107 103 457 | f 107 104 105 458 | f 109 110 111 459 | f 110 109 112 460 | f 113 88 85 461 | f 88 113 114 462 | f 115 102 101 463 | f 102 115 116 464 | f 117 116 115 465 | f 116 117 118 466 | f 108 99 106 467 | f 99 108 97 468 | f 90 112 109 469 | f 112 90 92 470 | f 111 114 113 471 | f 114 111 110 472 | f 119 120 121 473 | f 120 119 122 474 | f 119 123 124 475 | f 123 119 121 476 | f 125 118 117 477 | f 118 125 126 478 | f 124 126 125 479 | f 126 124 123 480 | f 127 128 129 481 | f 128 127 130 482 | f 131 132 133 483 | f 132 131 134 484 | f 135 136 137 485 | f 136 135 138 486 | f 139 138 135 487 | f 138 139 140 488 | f 141 142 143 489 | f 142 141 144 490 | f 145 140 139 491 | f 140 145 146 492 | f 147 148 149 493 | f 148 147 150 494 | f 151 152 153 495 | f 152 151 154 496 | f 129 152 154 497 | f 152 129 128 498 | f 155 146 145 499 | f 146 155 156 500 | f 136 149 137 501 | f 149 136 147 502 | f 157 130 127 503 | f 130 157 158 504 | f 150 133 148 505 | f 133 150 131 506 | f 159 160 161 507 | f 160 159 162 508 | f 163 156 155 509 | f 156 163 164 510 | f 134 153 132 511 | f 153 134 151 512 | f 142 164 163 513 | f 164 142 144 514 | f 161 165 166 515 | f 165 161 160 516 | f 159 167 162 517 | f 167 159 168 518 | f 166 158 157 519 | f 158 166 165 520 | f 120 141 143 521 | f 141 120 122 522 | f 115 146 117 523 | f 146 115 101 524 | f 146 101 100 525 | f 146 100 97 526 | f 146 97 140 527 | f 140 97 108 528 | f 140 108 138 529 | f 138 108 136 530 | f 136 108 147 531 | f 147 108 105 532 | f 147 105 150 533 | f 150 105 131 534 | f 131 105 134 535 | f 134 105 104 536 | f 134 104 151 537 | f 151 104 154 538 | f 154 104 96 539 | f 154 96 129 540 | f 129 96 127 541 | f 127 96 157 542 | f 157 96 93 543 | f 159 89 168 544 | f 89 159 161 545 | f 89 161 166 546 | f 89 166 157 547 | f 89 157 92 548 | f 92 157 93 549 | f 92 93 86 550 | f 92 86 112 551 | f 112 86 88 552 | f 112 88 110 553 | f 110 88 114 554 | f 89 167 168 555 | f 167 89 91 556 | f 126 116 118 557 | f 116 126 123 558 | f 116 123 102 559 | f 102 123 121 560 | f 102 121 98 561 | f 98 121 120 562 | f 98 120 145 563 | f 145 120 155 564 | f 155 120 163 565 | f 163 120 142 566 | f 142 120 143 567 | f 91 162 167 568 | f 162 91 160 569 | f 160 91 165 570 | f 165 91 158 571 | f 158 91 90 572 | f 158 90 94 573 | f 94 90 87 574 | f 87 90 109 575 | f 87 109 85 576 | f 85 109 111 577 | f 85 111 113 578 | f 145 99 98 579 | f 99 145 139 580 | f 99 139 106 581 | f 106 139 135 582 | f 106 135 137 583 | f 106 137 149 584 | f 106 149 107 585 | f 107 149 148 586 | f 107 148 133 587 | f 107 133 132 588 | f 107 132 103 589 | f 103 132 153 590 | f 103 153 152 591 | f 103 152 95 592 | f 95 152 128 593 | f 95 128 130 594 | f 95 130 158 595 | f 95 158 94 596 | f 146 125 117 597 | f 125 146 124 598 | f 124 146 119 599 | f 119 146 122 600 | f 122 146 156 601 | f 122 156 164 602 | f 122 164 144 603 | f 122 144 141 604 | usemtl Mat_4_-1 605 | f 185 186 187 606 | f 186 185 188 607 | f 188 185 189 608 | f 188 189 190 609 | f 190 189 191 610 | f 190 191 192 611 | f 192 191 193 612 | f 192 193 194 613 | f 194 193 195 614 | f 195 193 196 615 | f 195 196 197 616 | f 197 196 198 617 | f 198 196 199 618 | f 199 196 200 619 | f 199 200 201 620 | f 201 200 202 621 | f 202 200 203 622 | f 203 200 204 623 | f 203 204 205 624 | f 205 204 206 625 | f 192 207 208 626 | f 207 192 194 627 | f 208 207 209 628 | f 208 209 210 629 | f 208 210 211 630 | f 208 211 212 631 | f 212 211 213 632 | f 212 213 214 633 | f 212 214 215 634 | f 215 214 216 635 | f 215 216 217 636 | f 215 217 218 637 | f 215 218 219 638 | f 219 218 220 639 | f 219 220 221 640 | f 219 221 222 641 | f 219 222 223 642 | f 223 222 224 643 | f 223 224 225 644 | f 225 224 226 645 | f 227 228 229 646 | f 228 227 230 647 | f 228 230 231 648 | f 231 230 232 649 | f 232 230 233 650 | f 233 230 234 651 | f 233 234 235 652 | f 235 234 236 653 | f 236 234 237 654 | f 236 237 238 655 | f 238 237 239 656 | f 239 237 240 657 | f 240 237 241 658 | f 240 241 242 659 | f 242 241 243 660 | f 243 241 244 661 | f 244 241 245 662 | f 244 245 246 663 | f 246 245 247 664 | f 246 247 248 665 | f 249 250 251 666 | f 250 249 252 667 | f 250 252 253 668 | f 253 252 254 669 | f 253 254 255 670 | f 255 254 227 671 | f 255 227 256 672 | f 256 227 229 673 | f 256 229 257 674 | f 256 257 258 675 | f 258 257 259 676 | f 258 259 260 677 | f 258 260 261 678 | f 258 261 262 679 | f 262 261 263 680 | f 262 263 264 681 | f 262 264 265 682 | f 262 265 266 683 | f 266 265 267 684 | f 266 267 268 685 | f 244 224 222 686 | f 224 244 246 687 | f 267 203 205 688 | f 203 267 265 689 | f 238 217 216 690 | f 217 238 239 691 | f 232 211 210 692 | f 211 232 233 693 | f 195 229 194 694 | f 229 195 257 695 | f 265 202 203 696 | f 202 265 264 697 | f 198 259 197 698 | f 259 198 260 699 | f 197 257 195 700 | f 257 197 259 701 | f 244 221 243 702 | f 221 244 222 703 | f 261 198 199 704 | f 198 261 260 705 | f 207 231 209 706 | f 231 207 228 707 | f 235 214 213 708 | f 214 235 236 709 | f 194 228 207 710 | f 228 194 229 711 | f 236 216 214 712 | f 216 236 238 713 | f 209 232 210 714 | f 232 209 231 715 | f 243 220 242 716 | f 220 243 221 717 | f 263 199 201 718 | f 199 263 261 719 | f 233 213 211 720 | f 213 233 235 721 | f 239 218 217 722 | f 218 239 240 723 | f 264 201 202 724 | f 201 264 263 725 | f 242 218 240 726 | f 218 242 220 727 | f 206 267 205 728 | f 267 206 268 729 | f 249 188 252 730 | f 188 249 186 731 | f 230 192 208 732 | f 192 230 227 733 | f 237 212 215 734 | f 212 237 234 735 | f 225 245 223 736 | f 245 225 247 737 | f 226 247 225 738 | f 247 226 248 739 | f 258 200 196 740 | f 200 258 262 741 | f 266 206 204 742 | f 206 266 268 743 | f 255 189 253 744 | f 189 255 191 745 | f 253 185 250 746 | f 185 253 189 747 | f 252 190 254 748 | f 190 252 188 749 | f 224 248 226 750 | f 248 224 246 751 | f 245 219 223 752 | f 219 245 241 753 | f 241 215 219 754 | f 215 241 237 755 | f 250 187 251 756 | f 187 250 185 757 | f 234 208 212 758 | f 208 234 230 759 | f 251 186 249 760 | f 186 251 187 761 | f 256 196 193 762 | f 196 256 258 763 | f 255 193 191 764 | f 193 255 256 765 | f 227 190 192 766 | f 190 227 254 767 | f 262 204 200 768 | f 204 262 266 769 | usemtl Mat_2_-1 770 | f 169 170 171 771 | f 170 169 172 772 | f 170 172 173 773 | f 173 172 174 774 | f 173 174 175 775 | f 170 176 177 776 | f 176 170 178 777 | f 178 170 173 778 | f 178 173 179 779 | f 179 173 180 780 | f 170 181 171 781 | f 181 170 182 782 | f 182 170 183 783 | f 183 170 184 784 | f 184 170 177 785 | -------------------------------------------------------------------------------- /resources/3D_models/nothing.obj: -------------------------------------------------------------------------------- 1 | # its nothing 2 | -------------------------------------------------------------------------------- /resources/3D_models/piramid.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.78 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib piramid.mtl 4 | o piramid 5 | v -1.000000 -0.000000 1.000000 6 | v -1.000000 -0.000000 -1.000000 7 | v 1.000000 0.000000 -1.000000 8 | v 1.000000 0.000000 1.000000 9 | v -0.000000 1.200000 0.000000 10 | vn -0.0000 0.5773 0.8165 11 | vn 0.8165 0.5773 0.0000 12 | vn -0.8165 0.5773 -0.0000 13 | vn 0.0000 -1.0000 0.0000 14 | vn 0.0000 0.5773 -0.8165 15 | usemtl red 16 | s 1 17 | f 1//1 4//1 5//1 18 | usemtl green 19 | f 4//2 3//2 5//2 20 | usemtl yellow 21 | f 2//3 1//3 5//3 22 | usemtl cyan 23 | f 1//4 2//4 3//4 4//4 24 | usemtl blue 25 | f 3//5 2//5 5//5 26 | -------------------------------------------------------------------------------- /resources/debug_samples/3_markers_good.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/resources/debug_samples/3_markers_good.webm -------------------------------------------------------------------------------- /resources/debug_samples/debug_samples.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3_markers_good.webm 4 | textest.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/debug_samples/textest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derpicated/articated/7a0175c2678ff88ede2854ee4292bb8c0bc5f473/resources/debug_samples/textest.png -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #cmake_minimum_required(VERSION 2.8.11) 2 | set(CMAKE_VERBOSE_MAKEFILE OFF) 3 | 4 | ################################################################################ 5 | # C++ Options 6 | ################################################################################ 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | set(CMAKE_CXX_EXTENSIONS OFF) 10 | add_compile_options(-Wall -Wextra -Wpedantic) 11 | 12 | ################################################################################ 13 | # Qt library 14 | ################################################################################ 15 | set(CMAKE_AUTOMOC ON) 16 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 17 | find_package(Qt6 REQUIRED COMPONENTS Core) 18 | 19 | ################################################################################ 20 | # ARticated app 21 | ################################################################################ 22 | add_subdirectory(augmentation_widget) 23 | add_subdirectory(shared) 24 | add_subdirectory(vision) 25 | 26 | qt_add_resources(articated_app_rcc 27 | ${CMAKE_SOURCE_DIR}/source/qml/qml.qrc 28 | ${CMAKE_SOURCE_DIR}/resources/debug_samples/debug_samples.qrc) 29 | 30 | 31 | set(articated_app_SOURCES main.cpp) 32 | include_directories(AFTER SYSTEM src ${CMAKE_BINARY_DIR}) 33 | 34 | if(ANDROID) 35 | add_library(articated_app SHARED ${articated_app_SOURCES} ${articated_app_HEADERS} ${articated_app_rcc} ${articated_app_qml} ${3D_models_rcc}) 36 | else() 37 | add_executable(articated_app ${articated_app_SOURCES} ${articated_app_HEADERS} ${articated_app_rcc} ${articated_app_qml} ${3D_models_rcc}) 38 | endif() 39 | target_link_libraries(articated_app vision frame_data augmentation) 40 | 41 | # Get git version 42 | git_semver( 43 | MAJOR VERSION_MAJOR 44 | MINOR VERSION_MINOR 45 | PATCH VERSION_PATCH 46 | STRING VERSION_STRING 47 | PATH "${CMAKE_CURRENT_LIST_DIR}" 48 | ) 49 | # Set version as a property to the ARticated target 50 | set_target_properties(articated_app PROPERTIES VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") 51 | 52 | if(ANDROID) 53 | # Used by the qt-android-cmake project 54 | set( ANDROID_PLATFORM_LEVEL ${ANDROID_NATIVE_API_LEVEL}) 55 | # Using the LLVM from the latest Android SDK (NDK v19) 56 | # This is used in the qt-android-cmake project 57 | set( ANDROID_USE_LLVM TRUE) 58 | 59 | # The STL lib to use 60 | set(ANDROID_STL_SHARED_LIBRARIES "c++_shared") 61 | 62 | include(../qt-android-cmake/AddQtAndroidApk.cmake) 63 | 64 | # Define the app 65 | add_qt_android_apk( 66 | articated_apk 67 | articated_app 68 | NAME "ARticated" 69 | # VERSION_CODE is single number 70 | # Concatenate the version to single number 71 | # E.g. 72 | # 1.0.0 => 100 73 | # 10.25.1 => 10251 74 | VERSION_CODE "${VERSION_MAJOR}${VERSION_MINOR}${VERSION_PATCH}" 75 | PACKAGE_NAME "org.derpicated.articated" 76 | ) 77 | endif() 78 | 79 | # copy over resource folder 80 | add_custom_command( 81 | TARGET articated_app PRE_BUILD 82 | COMMAND ${CMAKE_COMMAND} -E copy_directory 83 | ${CMAKE_SOURCE_DIR}/res 84 | ${CMAKE_CURRENT_BINARY_DIR}/package/res) 85 | 86 | if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/package/AndroidManifest.xml") 87 | # read manifest 88 | file(READ ${CMAKE_CURRENT_BINARY_DIR}/package/AndroidManifest.xml ANDROID_MANIFEST_TMP) 89 | # add icon 90 | STRING(REGEX REPLACE 91 | "\n :GLESv3>) 21 | target_include_directories(augmentation PUBLIC ${INCLUDE_DIR}) 22 | -------------------------------------------------------------------------------- /source/augmentation_widget/augmentation_renderer.cpp: -------------------------------------------------------------------------------- 1 | // augmentation_renderer.cpp 2 | 3 | #include "augmentation_renderer.hpp" 4 | 5 | #ifdef ANDROID 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | Q_LOGGING_CATEGORY (augmentationRendererLog, "augmentation.renderer", QtInfoMsg) 15 | 16 | AugmentationRenderer::AugmentationRenderer (QObject* parent) 17 | : QObject (parent) 18 | , window_ (nullptr) 19 | , transform_ () 20 | , is_grayscale_ (0) 21 | , vertex_count_ (0) { 22 | Q_INIT_RESOURCE (GL_shaders); 23 | } 24 | 25 | AugmentationRenderer::~AugmentationRenderer () { 26 | glDeleteBuffers (1, &object_vbo_); 27 | glDeleteVertexArrays (1, &object_vao_); 28 | Q_CLEANUP_RESOURCE (GL_shaders); 29 | } 30 | 31 | void AugmentationRenderer::SetObject (const QString& path) { 32 | if (is_initialized_) { 33 | if (path != object_path_) { 34 | object_path_ = path; 35 | if (LoadObject (object_path_)) { 36 | qCDebug (augmentationRendererLog, "Loaded model from: %s", 37 | path.toLocal8Bit ().data ()); 38 | } else { 39 | qCWarning (augmentationRendererLog, 40 | "Failed to load model from: %s", path.toLocal8Bit ().data ()); 41 | } 42 | } 43 | } 44 | } 45 | 46 | bool AugmentationRenderer::LoadObject (const QString& path) { 47 | window_->beginExternalCommands (); 48 | bool status = false; 49 | 50 | // extract model from resources into filesystem and parse it 51 | QString resource_path = ":/3D_models/" + path; 52 | QFile resource_file (resource_path); 53 | if (resource_file.exists ()) { 54 | auto temp_file = QTemporaryFile::createNativeFile (resource_file); 55 | QString fs_path = temp_file->fileName (); 56 | 57 | if (!fs_path.isEmpty ()) { 58 | std::vector model_interleaved = object_.Load (fs_path.toStdString ()); 59 | 60 | glBindBuffer (GL_ARRAY_BUFFER, object_vbo_); 61 | glBufferData (GL_ARRAY_BUFFER, sizeof (float) * model_interleaved.size (), 62 | model_interleaved.data (), GL_STATIC_DRAW); 63 | glBindBuffer (GL_ARRAY_BUFFER, 0); 64 | vertex_count_ = model_interleaved.size () / object_.DataPerVertex (); 65 | object_.Unload (); 66 | 67 | status = true; 68 | } 69 | } 70 | 71 | window_->endExternalCommands (); 72 | return status; 73 | } 74 | 75 | void AugmentationRenderer::SetBackground (GLuint tex, bool is_grayscale) { 76 | current_handle_ = tex; 77 | is_grayscale_ = is_grayscale; 78 | } 79 | 80 | GLuint AugmentationRenderer::Background () { 81 | return texture_background_; 82 | } 83 | 84 | void AugmentationRenderer::SetTransform (Movement3D transform) { 85 | transform_ = transform; 86 | transform_.pitch (-(transform_.pitch () - 90)); 87 | transform_.yaw (-transform_.yaw ()); 88 | transform_.roll (-transform_.roll ()); 89 | } 90 | 91 | Movement3D AugmentationRenderer::Transform () { 92 | return transform_; 93 | } 94 | 95 | void AugmentationRenderer::init () { 96 | if (!is_initialized_) { 97 | // Check if we're using OpenGL 98 | QSGRendererInterface* rif = window_->rendererInterface (); 99 | Q_ASSERT (rif->graphicsApi () == QSGRendererInterface::OpenGL || 100 | rif->graphicsApi () == QSGRendererInterface::OpenGLRhi); 101 | 102 | initializeOpenGLFunctions (); 103 | 104 | glGenTextures (1, &texture_background_); 105 | glBindTexture (GL_TEXTURE_2D, texture_background_); 106 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 107 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 108 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 109 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 110 | glBindTexture (GL_TEXTURE_2D, 0); 111 | 112 | // generate a buffer to bind to textures 113 | glGenFramebuffers (1, &readback_buffer_); 114 | 115 | // compile and link shaders 116 | CompileShaders (); 117 | 118 | // generate vertex array buffers 119 | GenerateBuffers (); 120 | 121 | // TODO is this needed here? 122 | // setup projection matrix 123 | mat_projection_.ortho (-2.0f, +2.0f, -2.0f, +2.0f, 1.0f, 25.0f); 124 | 125 | is_initialized_ = true; 126 | emit InitializedOpenGL (); 127 | } 128 | } 129 | 130 | void AugmentationRenderer::GenerateBuffers () { 131 | // setup background vao 132 | { 133 | glGenVertexArrays (1, &background_vao_); 134 | glGenBuffers (1, &background_vbo_); 135 | 136 | glBindVertexArray (background_vao_); 137 | glBindBuffer (GL_ARRAY_BUFFER, background_vbo_); 138 | 139 | int pos_location = program_background_.attributeLocation ("position"); 140 | glVertexAttribPointer (pos_location, 2, GL_FLOAT, GL_FALSE, 141 | 4 * sizeof (float), reinterpret_cast (0)); 142 | glEnableVertexAttribArray (pos_location); 143 | 144 | int tex_location = program_background_.attributeLocation ("tex"); 145 | glVertexAttribPointer (tex_location, 2, GL_FLOAT, GL_FALSE, 146 | 4 * sizeof (float), reinterpret_cast (2 * sizeof (float))); 147 | glEnableVertexAttribArray (tex_location); 148 | 149 | // fill buffer with data 150 | GLfloat interleaved_background_buff[6 * 4] = { 151 | -1.0, 1.0, // poly 1 a 152 | 0.0, 0.0, // poly 1 a tex 153 | -1.0, -1.0, // poly 1 b 154 | 0.0, 1.0, // poly 1 b tex 155 | 1.0, 1.0, // poly 1 c 156 | 1.0, 0.0, // poly 1 c tex 157 | 1.0, 1.0, // poly 2 a 158 | 1.0, 0.0, // poly 2 a tex 159 | -1.0, -1.0, // poly 2 b 160 | 0.0, 1.0, // poly 2 b tex 161 | 1.0, -1.0, // poly 2 c 162 | 1.0, 1.0 // poly 2 c tex 163 | }; 164 | glBufferData (GL_ARRAY_BUFFER, sizeof (float) * 6 * 4, 165 | interleaved_background_buff, GL_STATIC_DRAW); 166 | } 167 | 168 | // setup object vao 169 | { 170 | glGenVertexArrays (1, &object_vao_); 171 | glGenBuffers (1, &object_vbo_); 172 | 173 | glBindVertexArray (object_vao_); 174 | glBindBuffer (GL_ARRAY_BUFFER, object_vbo_); 175 | 176 | int pos_location = program_object_.attributeLocation ("position"); 177 | glVertexAttribPointer (pos_location, 3, GL_FLOAT, GL_FALSE, 178 | 10 * sizeof (float), reinterpret_cast (0)); 179 | glEnableVertexAttribArray (pos_location); 180 | 181 | int nor_location = program_object_.attributeLocation ("normal"); 182 | glVertexAttribPointer (nor_location, 3, GL_FLOAT, GL_FALSE, 183 | 10 * sizeof (float), reinterpret_cast (3 * sizeof (float))); 184 | glEnableVertexAttribArray (nor_location); 185 | 186 | int col_location = program_object_.attributeLocation ("color"); 187 | glVertexAttribPointer (col_location, 4, GL_FLOAT, GL_FALSE, 188 | 10 * sizeof (float), reinterpret_cast (6 * sizeof (float))); 189 | glEnableVertexAttribArray (col_location); 190 | 191 | glBindBuffer (GL_ARRAY_BUFFER, 0); 192 | glBindVertexArray (0); 193 | } 194 | } 195 | 196 | void AugmentationRenderer::CompileShaders () { 197 | // background shaders 198 | { 199 | QFile vs_file (":/GL_shaders/background_vs.glsl"); 200 | QFile fs_file (":/GL_shaders/background_fs.glsl"); 201 | vs_file.open (QIODevice::ReadOnly); 202 | fs_file.open (QIODevice::ReadOnly); 203 | QByteArray vs_source = vs_file.readAll (); 204 | QByteArray fs_source = fs_file.readAll (); 205 | 206 | if (QOpenGLContext::currentContext ()->isOpenGLES ()) { 207 | vs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 208 | fs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 209 | } else { 210 | vs_source.prepend (QByteArrayLiteral ("#version 410\n")); 211 | fs_source.prepend (QByteArrayLiteral ("#version 410\n")); 212 | } 213 | 214 | program_background_.addShaderFromSourceCode (QOpenGLShader::Vertex, vs_source); 215 | program_background_.addShaderFromSourceCode (QOpenGLShader::Fragment, fs_source); 216 | program_background_.link (); 217 | 218 | program_background_.bind (); 219 | program_background_.setUniformValue ("u_tex_background", 0); 220 | program_background_.release (); 221 | } 222 | // object shaders 223 | { 224 | QFile vs_file (":/GL_shaders/object_vs.glsl"); 225 | QFile fs_file (":/GL_shaders/object_fs.glsl"); 226 | vs_file.open (QIODevice::ReadOnly); 227 | fs_file.open (QIODevice::ReadOnly); 228 | QByteArray vs_source = vs_file.readAll (); 229 | QByteArray fs_source = fs_file.readAll (); 230 | 231 | if (QOpenGLContext::currentContext ()->isOpenGLES ()) { 232 | vs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 233 | fs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 234 | } else { 235 | vs_source.prepend (QByteArrayLiteral ("#version 410\n")); 236 | fs_source.prepend (QByteArrayLiteral ("#version 410\n")); 237 | } 238 | 239 | program_object_.addShaderFromSourceCode (QOpenGLShader::Vertex, vs_source); 240 | program_object_.addShaderFromSourceCode (QOpenGLShader::Fragment, fs_source); 241 | program_object_.link (); 242 | } 243 | } 244 | 245 | void AugmentationRenderer::setViewportSize (const QSize& size) { 246 | view_width_ = size.width (); 247 | view_height_ = size.height (); 248 | 249 | mat_projection_.setToIdentity (); 250 | // TODO: replace with perspective, or possibly intrinsic camera matrix 251 | mat_projection_.ortho (-2.0f, +2.0f, -2.0f, +2.0f, 1.0f, 25.0f); 252 | } 253 | 254 | void AugmentationRenderer::setWindow (QQuickWindow* window) { 255 | window_ = window; 256 | } 257 | 258 | void AugmentationRenderer::paint () { 259 | window_->beginExternalCommands (); 260 | 261 | glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 262 | glViewport (0, 0, view_width_, view_height_); 263 | glEnable (GL_BLEND); 264 | glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 265 | 266 | DrawBackground (); 267 | DrawObject (); 268 | 269 | window_->endExternalCommands (); 270 | 271 | glClear (GL_DEPTH_BUFFER_BIT); 272 | } 273 | 274 | void AugmentationRenderer::DrawBackground () { 275 | program_background_.bind (); 276 | program_background_.setUniformValue ("is_GLRED", is_grayscale_); 277 | 278 | glDisable (GL_DEPTH_TEST); 279 | 280 | // draw the 2 triangles that form the background 281 | glBindVertexArray (background_vao_); 282 | glActiveTexture (GL_TEXTURE0); 283 | glBindTexture (GL_TEXTURE_2D, current_handle_); 284 | glDrawArrays (GL_TRIANGLES, 0, 6); 285 | glBindTexture (GL_TEXTURE_2D, 0); 286 | glBindVertexArray (0); 287 | } 288 | 289 | void AugmentationRenderer::DrawObject () { 290 | QMatrix4x4 mat_modelview; 291 | mat_modelview.translate ( 292 | transform_.translation ().x, transform_.translation ().y, -10.0); 293 | mat_modelview.scale (transform_.scale ()); 294 | mat_modelview.rotate (transform_.pitch (), 1, 0, 0); 295 | mat_modelview.rotate (transform_.yaw (), 0, 1, 0); 296 | mat_modelview.rotate (transform_.roll (), 0, 0, 1); 297 | mat_modelview = mat_projection_ * mat_modelview; 298 | 299 | program_object_.bind (); 300 | program_object_.setUniformValue ("view_matrix", mat_modelview); 301 | 302 | glEnable (GL_DEPTH_TEST); 303 | // glEnable (GL_CULL_FACE); 304 | 305 | // draw the object 306 | glBindVertexArray (object_vao_); 307 | glDrawArrays (GL_TRIANGLES, 0, vertex_count_); 308 | glBindVertexArray (0); 309 | } 310 | -------------------------------------------------------------------------------- /source/augmentation_widget/augmentation_renderer.hpp: -------------------------------------------------------------------------------- 1 | // augmentation_renderer.hpp 2 | 3 | #ifndef AUGMENTATION_RENDERER_HPP 4 | #define AUGMENTATION_RENDERER_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "model_loader.hpp" 18 | #include "shared/movement3d/movement3d.hpp" 19 | 20 | Q_DECLARE_LOGGING_CATEGORY(augmentationRendererLog) 21 | 22 | class AugmentationRenderer : public QObject, protected QOpenGLExtraFunctions { 23 | Q_OBJECT 24 | public: 25 | AugmentationRenderer (QObject* parent = 0); 26 | ~AugmentationRenderer (); 27 | 28 | // QOpenGLWidget reimplemented functions 29 | void init (); 30 | void setViewportSize (const QSize& size); 31 | void setWindow (QQuickWindow* window); 32 | void paint (); 33 | 34 | public slots: 35 | void SetObject (const QString& path); 36 | void SetBackground (GLuint tex, bool is_grayscale); 37 | GLuint Background (); 38 | void SetTransform (Movement3D transform); 39 | Movement3D Transform (); 40 | 41 | signals: 42 | void InitializedOpenGL (); 43 | 44 | private: 45 | bool LoadObject (const QString& path); 46 | void GenerateBuffers (); 47 | void CompileShaders (); 48 | void DrawObject (); 49 | void DrawBackground (); 50 | 51 | bool is_initialized_{ false }; 52 | ModelLoader object_; 53 | QString object_path_; 54 | QQuickWindow* window_; 55 | int view_width_; 56 | int view_height_; 57 | Movement3D transform_; 58 | QMatrix4x4 mat_projection_; 59 | GLuint is_grayscale_; 60 | GLuint texture_background_; 61 | GLuint current_handle_; 62 | GLuint readback_buffer_; 63 | GLuint background_vao_; 64 | GLuint object_vao_; 65 | GLuint background_vbo_; 66 | GLuint object_vbo_; 67 | GLuint vertex_count_; 68 | QOpenGLShaderProgram program_background_; 69 | QOpenGLShaderProgram program_object_; 70 | }; 71 | 72 | #endif // AUGMENTATION_RENDERER_HPP 73 | -------------------------------------------------------------------------------- /source/augmentation_widget/augmentation_view.cpp: -------------------------------------------------------------------------------- 1 | #include "augmentation_view.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "shared/movement3d/movement3d.hpp" 7 | 8 | AugmentationView::AugmentationView () 9 | : renderer_ (nullptr) { 10 | Q_INIT_RESOURCE (3D_models); 11 | readModels (); 12 | setModel (0); 13 | connect (this, &QQuickItem::windowChanged, this, &AugmentationView::handleWindowChanged); 14 | } 15 | 16 | AugmentationView::~AugmentationView () { 17 | Q_CLEANUP_RESOURCE (3D_models); 18 | } 19 | 20 | void AugmentationView::readModels () { 21 | models_ = QDir (":/3D_models/").entryList (QDir::Files); 22 | emit modelsChanged (); 23 | } 24 | 25 | void AugmentationView::handleWindowChanged (QQuickWindow* win) { 26 | if (win) { 27 | connect (win, &QQuickWindow::beforeSynchronizing, this, 28 | &AugmentationView::sync, Qt::DirectConnection); 29 | connect (win, &QQuickWindow::sceneGraphInvalidated, this, 30 | &AugmentationView::cleanup, Qt::DirectConnection); 31 | win->setColor ("magenta"); 32 | } 33 | } 34 | 35 | void AugmentationView::drawFrame (FrameData frame) { 36 | auto& data = frame.data; 37 | 38 | try { 39 | transform_ = std::any_cast (data["transform"]); 40 | background_texture_ = std::any_cast (data["background"]); 41 | 42 | if (auto is_grayscale_it = data.find ("backgroundIsGrayscale"); 43 | is_grayscale_it != data.end ()) { 44 | background_is_grayscale_ = std::any_cast (is_grayscale_it->second); 45 | } else { 46 | background_is_grayscale_ = false; 47 | } 48 | } catch (const std::bad_any_cast& e) { 49 | std::cout << e.what () << '\n'; 50 | } 51 | 52 | if (window ()) { 53 | window ()->update (); 54 | } 55 | } 56 | 57 | void AugmentationView::setModel (int model) { 58 | model_ = model; 59 | object_path_ = models_[model]; 60 | emit modelChanged (); 61 | } 62 | 63 | void AugmentationView::sync () { 64 | if (!renderer_) { 65 | renderer_ = new AugmentationRenderer (); 66 | connect (window (), &QQuickWindow::beforeRendering, renderer_, 67 | &AugmentationRenderer::init, Qt::DirectConnection); 68 | connect (window (), &QQuickWindow::beforeRenderPassRecording, renderer_, 69 | &AugmentationRenderer::paint, Qt::DirectConnection); 70 | } 71 | 72 | renderer_->setViewportSize (window ()->size () * window ()->devicePixelRatio ()); 73 | renderer_->setWindow (window ()); 74 | renderer_->SetTransform (transform_); 75 | renderer_->SetBackground (background_texture_, background_is_grayscale_); 76 | renderer_->SetObject (object_path_); 77 | } 78 | 79 | void AugmentationView::cleanup () { 80 | delete renderer_; 81 | renderer_ = nullptr; 82 | } 83 | 84 | void AugmentationView::releaseResources () { 85 | window ()->scheduleRenderJob ( 86 | new CleanupJob (renderer_), QQuickWindow::BeforeSynchronizingStage); 87 | renderer_ = nullptr; 88 | } 89 | -------------------------------------------------------------------------------- /source/augmentation_widget/augmentation_view.hpp: -------------------------------------------------------------------------------- 1 | // augmentation_view.hpp 2 | 3 | #ifndef AUGMENTATION_VIEW_HPP 4 | #define AUGMENTATION_VIEW_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "augmentation_renderer.hpp" 11 | #include "shared/frame_data.hpp" 12 | 13 | class CleanupJob : public QRunnable { 14 | public: 15 | CleanupJob (AugmentationRenderer* renderer) 16 | : renderer_ (renderer) { 17 | } 18 | void run () override { 19 | delete renderer_; 20 | } 21 | 22 | private: 23 | AugmentationRenderer* renderer_; 24 | }; 25 | 26 | class AugmentationView : public QQuickItem { 27 | Q_OBJECT 28 | 29 | Q_PROPERTY (QStringList models MEMBER models_ NOTIFY modelsChanged) 30 | Q_PROPERTY (int model MEMBER model_ WRITE setModel NOTIFY modelChanged) 31 | 32 | public: 33 | AugmentationView (); 34 | ~AugmentationView (); 35 | 36 | void setModel (int model); 37 | 38 | signals: 39 | void modelsChanged (); 40 | void modelChanged (); 41 | 42 | public slots: 43 | void sync (); 44 | void cleanup (); 45 | void drawFrame (FrameData frame); 46 | 47 | private slots: 48 | void handleWindowChanged (QQuickWindow* win); 49 | 50 | private: 51 | void releaseResources () override; 52 | void readModels (); 53 | 54 | AugmentationRenderer* renderer_; 55 | QString object_path_; 56 | QStringList models_; 57 | int model_{ 0 }; 58 | Movement3D transform_; 59 | GLuint background_texture_{ 0 }; 60 | bool background_is_grayscale_; 61 | }; 62 | 63 | #endif // AUGMENTATION_VIEW_HPP 64 | -------------------------------------------------------------------------------- /source/augmentation_widget/model_loader.cpp: -------------------------------------------------------------------------------- 1 | #include "model_loader.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DATA_PER_VERTEX 10 // 3 coords + 3 normal + 4 color 13 | #define MAX_COORDINATE 1 14 | 15 | ModelLoader::ModelLoader () 16 | : is_loaded_ (false) 17 | , scale_factor_ (1.0f) 18 | , current_rgba_{ { 1, 1, 1, 1 } } { 19 | } 20 | 21 | void ModelLoader::Unload () { 22 | is_loaded_ = false; 23 | scale_factor_ = 1.0f; 24 | vertices_.clear (); 25 | normals_.clear (); 26 | faces_.clear (); 27 | faces_normals_.clear (); 28 | faces_colors_.clear (); 29 | interleaved_faces_.clear (); 30 | current_rgba_ = { { 1, 1, 1, 1 } }; 31 | } 32 | 33 | int ModelLoader::DataPerVertex () { 34 | return DATA_PER_VERTEX; 35 | } 36 | 37 | void ModelLoader::CalculateNormals (const std::vector& vertices, 38 | std::vector& normals) { 39 | // calculate Vector1 and Vector2 40 | float norm[3], va[3], vb[3], vr[3], val; 41 | va[0] = vertices[0] - vertices[3]; 42 | va[1] = vertices[1] - vertices[4]; 43 | va[2] = vertices[2] - vertices[5]; 44 | 45 | vb[0] = vertices[0] - vertices[6]; 46 | vb[1] = vertices[1] - vertices[7]; 47 | vb[2] = vertices[2] - vertices[8]; 48 | 49 | // cross product 50 | vr[0] = va[1] * vb[2] - vb[1] * va[2]; 51 | vr[1] = vb[0] * va[2] - va[0] * vb[2]; 52 | vr[2] = va[0] * vb[1] - vb[0] * va[1]; 53 | 54 | // normalization factor 55 | val = sqrt (vr[0] * vr[0] + vr[1] * vr[1] + vr[2] * vr[2]); 56 | 57 | norm[0] = vr[0] / val; 58 | norm[1] = vr[1] / val; 59 | norm[2] = vr[2] / val; 60 | 61 | // push back the norms for all 3 vertices 62 | normals.push_back (norm[0]); 63 | normals.push_back (norm[1]); 64 | normals.push_back (norm[2]); 65 | 66 | normals.push_back (norm[0]); 67 | normals.push_back (norm[1]); 68 | normals.push_back (norm[2]); 69 | 70 | normals.push_back (norm[0]); 71 | normals.push_back (norm[1]); 72 | normals.push_back (norm[2]); 73 | } 74 | 75 | float ModelLoader::CalculateScale () { 76 | // ensure that every vertex fits into range -1 to 1 77 | float max_val = 0.0f; 78 | for (float val : faces_) { 79 | float abs_val = std::fabs (val); 80 | if (max_val < abs_val) { 81 | max_val = abs_val; 82 | } 83 | } 84 | return (MAX_COORDINATE / max_val); 85 | } 86 | 87 | void ModelLoader::NormalizeVertices () { 88 | float scale_factor = CalculateScale (); 89 | 90 | for (float& val : faces_) { 91 | val = val * scale_factor; 92 | } 93 | 94 | scale_factor_ = scale_factor; 95 | } 96 | 97 | void ModelLoader::Interleave () { 98 | int vertex_count = faces_.size () / 3; 99 | 100 | for (int vert_idx = 0; vert_idx < vertex_count; vert_idx++) { 101 | int position_idx = vert_idx * 3; 102 | int normal_idx = vert_idx * 3; 103 | int color_idx = vert_idx * 4; 104 | 105 | interleaved_faces_.push_back (faces_.at (position_idx)); 106 | interleaved_faces_.push_back (faces_.at (position_idx + 1)); 107 | interleaved_faces_.push_back (faces_.at (position_idx + 2)); 108 | 109 | interleaved_faces_.push_back (faces_normals_.at (normal_idx)); 110 | interleaved_faces_.push_back (faces_normals_.at (normal_idx + 1)); 111 | interleaved_faces_.push_back (faces_normals_.at (normal_idx + 2)); 112 | 113 | interleaved_faces_.push_back (faces_colors_.at (color_idx)); 114 | interleaved_faces_.push_back (faces_colors_.at (color_idx + 1)); 115 | interleaved_faces_.push_back (faces_colors_.at (color_idx + 2)); 116 | interleaved_faces_.push_back (faces_colors_.at (color_idx + 3)); 117 | } 118 | } 119 | 120 | const std::vector& ModelLoader::Load (const std::string filename, bool normalize) { 121 | bool status = true; 122 | std::string line; 123 | 124 | if (is_loaded_) { 125 | Unload (); 126 | } 127 | 128 | std::ifstream objFile (filename); 129 | if (objFile.is_open ()) // If obj file is open, continue 130 | { 131 | while ((!objFile.eof ()) && status) { 132 | getline (objFile, line); 133 | if (!line.empty ()) { 134 | status = ParseLine (line); 135 | } 136 | } 137 | objFile.close (); // Close OBJ file 138 | } else { 139 | status = false; 140 | } 141 | 142 | 143 | if (status == true) { 144 | if (normalize) { 145 | NormalizeVertices (); 146 | } 147 | 148 | Interleave (); 149 | is_loaded_ = true; 150 | } 151 | 152 | return interleaved_faces_; 153 | } 154 | 155 | bool ModelLoader::ParseLine (const std::string& line) { 156 | bool status = true; 157 | 158 | std::string value; 159 | std::string keyword; 160 | size_t split_pos = line.find (' '); 161 | if (split_pos != std::string::npos) { 162 | keyword = line.substr (0, split_pos); 163 | value = line.substr (split_pos); 164 | } else { 165 | keyword = line; 166 | value = ""; 167 | } 168 | 169 | if (keyword == "#" || keyword.empty ()) { 170 | ; // comment line, ignore 171 | } else if (keyword == "v") { 172 | status = ParseVertex (value); 173 | } else if (keyword == "vn") { 174 | status = ParseNormal (value); 175 | } else if (keyword == "f") { 176 | status = ParseFace (value); 177 | } else if (keyword == "usemtl") { 178 | status = ParseUseMTL (value); 179 | } else { 180 | if (unknown_options_.find (keyword) == unknown_options_.end ()) { 181 | // std::cout << "unsupporterd keyword: " << keyword << std::endl; 182 | unknown_options_.insert (keyword); 183 | } 184 | } 185 | return status; 186 | } 187 | 188 | bool ModelLoader::ParseVertex (const std::string& line) { 189 | bool status = true; 190 | 191 | std::vector values = TokenizeString (line, " "); 192 | 193 | if (values.size () == 3) { 194 | std::array vertex; 195 | 196 | try { 197 | vertex[0] = std::atof (values[0].c_str ()); 198 | vertex[1] = std::atof (values[1].c_str ()); 199 | vertex[2] = std::atof (values[2].c_str ()); 200 | } catch (std::invalid_argument const&) { 201 | status = false; 202 | // std::cout << "failed line" << line << std::endl; 203 | } 204 | if (status == true) { 205 | vertices_.insert (std::end (vertices_), std::begin (vertex), std::end (vertex)); 206 | } 207 | } else { 208 | status = false; 209 | } 210 | return status; 211 | } 212 | 213 | bool ModelLoader::ParseNormal (const std::string& line) { 214 | bool status = true; 215 | 216 | std::vector values = TokenizeString (line, " "); 217 | 218 | if (values.size () == 3) { 219 | std::array normal; 220 | 221 | try { 222 | normal[0] = std::atof (values[0].c_str ()); 223 | normal[1] = std::atof (values[1].c_str ()); 224 | normal[2] = std::atof (values[2].c_str ()); 225 | } catch (std::invalid_argument const&) { 226 | status = false; 227 | // std::cout << "failed line" << line << std::endl; 228 | } 229 | if (status == true) { 230 | normals_.insert (std::end (normals_), std::begin (normal), std::end (normal)); 231 | } 232 | } else { 233 | status = false; 234 | } 235 | return status; 236 | } 237 | 238 | 239 | bool ModelLoader::ParseFace (const std::string& line) { 240 | bool status = true; 241 | 242 | std::vector values = TokenizeString (line, " "); 243 | 244 | if (values.size () == 3) { 245 | // create triangle ABC 246 | status = ParseTriangle (values); 247 | } else if (values.size () == 4) { 248 | // create triangles ABC and ACD from quad ABCD 249 | std::vector triangle_2 = { values[0], values[2], values[3] }; 250 | values.pop_back (); 251 | status = ParseTriangle (values); // ABC 252 | if (status == true) { 253 | status = ParseTriangle (triangle_2); // ACD 254 | } 255 | } else { 256 | status = false; 257 | } 258 | 259 | return status; 260 | } 261 | 262 | bool ModelLoader::ParseTriangle (const std::vector& vertices_str) { 263 | bool status = true; 264 | bool normals_provided = true; 265 | int vert_idx = 0; // vertex indices 266 | int text_idx = 0; // texture point indices 267 | int norm_idx = 0; // normal indices 268 | std::vector vertices; 269 | std::vector normals; 270 | 271 | for (auto vertex_str : vertices_str) { 272 | // each vertex can be in format v or v//vn or v/vt/vn 273 | // TODO: use regex instead 274 | std::vector values = TokenizeString (vertex_str, "/"); 275 | 276 | if (values.size () == 1) { 277 | normals_provided = false; 278 | vert_idx = std::atoi (values[0].c_str ()); 279 | } else if (values.size () == 2) { 280 | vert_idx = std::atoi (values[0].c_str ()); 281 | norm_idx = std::atoi (values[1].c_str ()); 282 | } else if (values.size () == 3) { 283 | vert_idx = std::atoi (values[0].c_str ()); 284 | text_idx = std::atoi (values[1].c_str ()); 285 | norm_idx = std::atoi (values[2].c_str ()); 286 | } else { 287 | status = false; 288 | } 289 | 290 | if (status) { 291 | // OBJ index starts at 1, and 3 floats per vertex 292 | vert_idx = (vert_idx - 1) * 3; 293 | text_idx = (text_idx - 1) * 3; 294 | norm_idx = (norm_idx - 1) * 3; 295 | 296 | // retrieve the XYZ coords 297 | vertices.push_back (vertices_.at (vert_idx)); 298 | vertices.push_back (vertices_.at (vert_idx + 1)); 299 | vertices.push_back (vertices_.at (vert_idx + 2)); 300 | 301 | if (normals_provided == true) { // retrieve the XYZ angles 302 | normals.push_back (normals_.at (norm_idx)); 303 | normals.push_back (normals_.at (norm_idx + 1)); 304 | normals.push_back (normals_.at (norm_idx + 2)); 305 | } 306 | 307 | // push back color per vertex 308 | faces_colors_.insert (std::end (faces_colors_), 309 | std::begin (current_rgba_), std::end (current_rgba_)); 310 | } 311 | } 312 | 313 | // push back all vertices 314 | faces_.insert (std::end (faces_), std::begin (vertices), std::end (vertices)); 315 | 316 | // if any of the normals are missing, recalculate all 317 | if (normals_provided == false) { 318 | normals.clear (); 319 | CalculateNormals (vertices, normals); 320 | } 321 | 322 | // push back all normals 323 | faces_normals_.insert ( 324 | std::end (faces_normals_), std::begin (normals), std::end (normals)); 325 | 326 | return status; 327 | } 328 | 329 | bool ModelLoader::ParseUseMTL (const std::string& value) { 330 | bool status = true; // TODO: check status 331 | 332 | std::string mat = value.substr (value.find (" ")); // get from space 333 | mat = TrimString (mat); 334 | 335 | if (mat == "black") { // shuttle materials 336 | current_rgba_ = { { 0.0, 0.0, 0.0, 1.0 } }; 337 | } else if (mat == "glass") { 338 | current_rgba_ = { { 0.5, 0.65, 0.75, 1.0 } }; 339 | } else if (mat == "bone") { 340 | current_rgba_ = { { 0.75, 0.75, 0.65, 1.0 } }; 341 | } else if (mat == "brass") { 342 | current_rgba_ = { { 0.45, 0.35, 0.12, 1.0 } }; 343 | } else if (mat == "dkdkgrey") { 344 | current_rgba_ = { { 0.30, 0.35, 0.35, 1.0 } }; 345 | } else if (mat == "fldkdkgrey") { 346 | current_rgba_ = { { 0.30, 0.35, 0.35, 1.0 } }; 347 | } else if (mat == "redbrick") { 348 | current_rgba_ = { { 0.61, 0.16, 0.0, 1.0 } }; 349 | } else if (mat == "Mat_1_-1") { // articated materials 350 | current_rgba_ = { { 0.0, 0.0, 1.0, 1.0 } }; 351 | } else if (mat == "Mat_2_-1") { 352 | current_rgba_ = { { 0.2, 1.0, 1.0, 0.4 } }; 353 | } else if (mat == "Mat_3_-1") { 354 | current_rgba_ = { { 1.0, 0.0, 0.0, 1.0 } }; 355 | } else if (mat == "Mat_4_-1") { 356 | current_rgba_ = { { 0.0, 1.0, 0.0, 1.0 } }; 357 | } else if (mat == "Black") { // dino materials 358 | current_rgba_ = { { 0.09, 0.09, 0.09, 1.0 } }; 359 | } else if (mat == "LightBlue") { 360 | current_rgba_ = { { 0.57, 0.73, 0.92, 1.0 } }; 361 | } else if (mat == "DarkBlue") { 362 | current_rgba_ = { { 0.16, 0.40, 0.67, 1.0 } }; 363 | } else if (mat == "red") { // general collors 364 | current_rgba_ = { { 1.0, 0.0, 0.0, 1.0 } }; 365 | } else if (mat == "green") { 366 | current_rgba_ = { { 0.0, 1.0, 0.0, 1.0 } }; 367 | } else if (mat == "blue") { 368 | current_rgba_ = { { 0.0, 0.0, 1.0, 1.0 } }; 369 | } else if (mat == "cyan") { 370 | current_rgba_ = { { 0.0, 1.0, 1.0, 1.0 } }; 371 | } else if (mat == "yellow") { 372 | current_rgba_ = { { 1.0, 1.0, 0.0, 1.0 } }; 373 | } else { 374 | // default to dark purple 375 | current_rgba_ = { { 0.2, 0, 0.2, 1 } }; 376 | 377 | if (unknown_options_.find (mat) == unknown_options_.end ()) { 378 | std::cout << "unknown material: " << mat << std::endl; 379 | unknown_options_.insert (mat); 380 | } 381 | } 382 | return status; 383 | } 384 | 385 | inline std::vector 386 | ModelLoader::TokenizeString (const std::string& in, const std::string& delim) { 387 | size_t split_pos; 388 | std::string right = in; 389 | std::string left; 390 | std::vector ret; 391 | 392 | do { 393 | split_pos = right.find (delim); 394 | if (split_pos != std::string::npos) { 395 | left = right.substr (0, split_pos); 396 | right = right.substr (split_pos + 1); 397 | left = TrimString (left); 398 | right = TrimString (right); 399 | if (!left.empty ()) { 400 | ret.push_back (left); 401 | } 402 | } 403 | } while (split_pos != std::string::npos); 404 | 405 | // the last token 406 | if (!right.empty ()) { 407 | ret.push_back (right); 408 | } 409 | 410 | return ret; 411 | } 412 | 413 | inline std::string ModelLoader::TrimString (const std::string& s) { 414 | auto wsfront = std::find_if_not ( 415 | s.begin (), s.end (), [] (int c) { return std::isspace (c); }); 416 | auto wsback = std::find_if_not (s.rbegin (), s.rend (), [] (int c) { 417 | return std::isspace (c); 418 | }).base (); 419 | return (wsback <= wsfront ? std::string () : std::string (wsfront, wsback)); 420 | } 421 | -------------------------------------------------------------------------------- /source/augmentation_widget/model_loader.hpp: -------------------------------------------------------------------------------- 1 | // model_loader.hpp 2 | #ifndef MODEL_LOADER_HPP 3 | #define MODEL_LOADER_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class ModelLoader { 11 | public: 12 | ModelLoader (); 13 | const std::vector& Load (const std::string filename, bool normalize = true); 14 | int DataPerVertex (); 15 | void Unload (); 16 | 17 | private: 18 | void CalculateNormals (const std::vector& vertices, std::vector& normals); 19 | float CalculateScale (); 20 | void NormalizeVertices (); 21 | void Interleave (); 22 | bool ParseLine (const std::string& line); 23 | bool ParseVertex (const std::string& line); 24 | bool ParseNormal (const std::string& line); 25 | bool ParseFace (const std::string& line); 26 | bool ParseTriangle (const std::vector& values); 27 | bool ParseUseMTL (const std::string& line); 28 | inline std::string TrimString (const std::string& s); 29 | inline std::vector 30 | TokenizeString (const std::string& in, const std::string& delim); 31 | 32 | bool is_loaded_; 33 | float scale_factor_; 34 | std::array current_rgba_; 35 | std::vector vertices_; 36 | std::vector normals_; 37 | std::vector faces_; 38 | std::vector faces_normals_; 39 | std::vector faces_colors_; 40 | std::vector interleaved_faces_; 41 | std::set unknown_options_; 42 | }; 43 | 44 | #endif // MODEL_LOADER_HPP 45 | -------------------------------------------------------------------------------- /source/augmentation_widget/shaders/GL_shaders.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | object_vs.glsl 4 | object_fs.glsl 5 | background_vs.glsl 6 | background_fs.glsl 7 | 8 | 9 | -------------------------------------------------------------------------------- /source/augmentation_widget/shaders/background_fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump int; 3 | precision mediump float; 4 | #endif 5 | 6 | uniform sampler2D u_tex_background; 7 | flat in int vtf_is_GLRED; 8 | in vec2 vtf_texcoord; 9 | 10 | out highp vec4 frag_color; 11 | 12 | void main() { 13 | vec4 tex_sample = texture(u_tex_background, vtf_texcoord); 14 | if (vtf_is_GLRED == 1) { 15 | frag_color = vec4(tex_sample.r, tex_sample.r, tex_sample.r, 1.0); 16 | } else { 17 | frag_color = tex_sample; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/augmentation_widget/shaders/background_vs.glsl: -------------------------------------------------------------------------------- 1 | in vec2 position; 2 | in vec2 tex; 3 | 4 | uniform int is_GLRED; 5 | 6 | flat out int vtf_is_GLRED; 7 | out vec2 vtf_texcoord; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(position, 0.0, 1.0); 12 | vtf_texcoord = tex; 13 | vtf_is_GLRED = is_GLRED; 14 | } 15 | -------------------------------------------------------------------------------- /source/augmentation_widget/shaders/object_fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump int; 3 | precision mediump float; 4 | #endif 5 | 6 | in vec3 vtf_normal; 7 | in highp vec4 vtf_color; 8 | out highp vec4 frag_color; 9 | 10 | void main() 11 | { 12 | float dot_product = dot( vtf_normal, vec3(0.17, 0.98, 0) ); 13 | float cosTheta = clamp(dot_product, 0.0, 1.0); 14 | vec3 diffuse = vtf_color.rgb * cosTheta; 15 | vec3 ambient = vtf_color.rgb * 0.5; 16 | frag_color = vec4(vec3(diffuse + ambient), vtf_color.a); 17 | } 18 | -------------------------------------------------------------------------------- /source/augmentation_widget/shaders/object_vs.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 view_matrix; 2 | in vec3 position; 3 | in vec3 normal; 4 | in vec4 color; 5 | 6 | out vec4 vtf_color; 7 | out vec3 vtf_normal; 8 | 9 | void main() 10 | { 11 | vtf_color = color; 12 | vtf_normal = normal; 13 | gl_Position = view_matrix * vec4(position, 1.0); 14 | } 15 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "augmentation_widget/augmentation_view.hpp" 8 | #include "vision/vision.hpp" 9 | 10 | namespace { 11 | void configureOpengl (bool force_gles) { 12 | // set GL version 13 | QSurfaceFormat glFormat; 14 | if (QOpenGLContext::openGLModuleType () == QOpenGLContext::LibGL && !force_gles) { 15 | // on desktop, require opengl 4.1 16 | glFormat.setVersion (4, 1); 17 | } else { 18 | // on mobile, require opengles 3.0 19 | glFormat.setRenderableType (QSurfaceFormat::OpenGLES); 20 | glFormat.setVersion (3, 0); 21 | } 22 | 23 | glFormat.setProfile (QSurfaceFormat::CoreProfile); 24 | QSurfaceFormat::setDefaultFormat (glFormat); 25 | 26 | QCoreApplication::setAttribute (Qt::AA_ShareOpenGLContexts); 27 | } 28 | } // namespace 29 | 30 | int main (int argc, char* argv[]) { 31 | QCommandLineParser parser; 32 | parser.addHelpOption (); 33 | parser.setApplicationDescription ( 34 | "ARticated: an augmented reality application"); 35 | QCommandLineOption force_gles_option ("force-gles", "force usage of openGLES"); 36 | parser.addOption (force_gles_option); 37 | 38 | // parse arguments 39 | QStringList arguments; 40 | for (int i = 0; i < argc; ++i) { 41 | arguments.append (argv[i]); 42 | } 43 | parser.process (arguments); 44 | 45 | // configure opengl 46 | bool force_gles = parser.isSet (force_gles_option); 47 | configureOpengl (force_gles); 48 | 49 | // create the main app & window 50 | QGuiApplication app (argc, argv); 51 | setlocale (LC_NUMERIC, "C"); 52 | QQmlApplicationEngine engine; 53 | 54 | // register custom qml components 55 | qmlRegisterType ("articated.vision", 1, 0, "Vision"); 56 | qmlRegisterType ( 57 | "articated.augmentation.augmentation_view", 1, 0, "AugmentationView"); 58 | engine.load (QUrl (QStringLiteral ("qrc:/qml/Main.qml"))); 59 | 60 | return app.exec (); 61 | } 62 | -------------------------------------------------------------------------------- /source/qml/Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import QtQuick.Controls.Material 2.14 4 | import QtQuick.Layouts 1.14 5 | 6 | ApplicationWindow { 7 | id: appWindow 8 | height: 350 9 | title: "ARticated" 10 | visible: true 11 | width: 600 12 | 13 | StackView { 14 | id: mainStack 15 | anchors.fill: parent 16 | focus: true 17 | 18 | initialItem: MainView { 19 | id: mainView 20 | onOpenSettings: { 21 | mainStack.push("SettingsView.qml", { 22 | "settings": settings 23 | }); 24 | mainStack.currentItem.onExit.connect(function () { 25 | mainStack.pop(); 26 | }); 27 | } 28 | } 29 | } 30 | Shortcut { 31 | sequence: "Ctrl+W" 32 | 33 | onActivated: Qt.quit() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/qml/MainView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import QtQuick.Controls.Material 2.14 4 | import QtQuick.Layouts 1.14 5 | import articated.vision 1.0 6 | import articated.augmentation.augmentation_view 1.0 7 | import "." as Components 8 | 9 | Item { 10 | property alias settings: settings_instance 11 | 12 | signal openSettings 13 | 14 | function loadModel(model_index) { 15 | augmentation.model = model_index; 16 | } 17 | function selectAlgorithm(algorithm) { 18 | vision.algorithm = algorithm; 19 | } 20 | function selectSource(source) { 21 | vision.source = source; 22 | } 23 | function setDebugLevel(level) { 24 | vision.debugLevel = level; 25 | } 26 | 27 | Layout.fillHeight: true 28 | Layout.fillWidth: true 29 | 30 | Keys.onEscapePressed: openSettings() 31 | Keys.onPressed: event => { 32 | if (event.key == Qt.Key_Space) { 33 | vision.SetReference(); 34 | referenceButton.down = true; 35 | event.accepted = true; 36 | } else if (event.key == Qt.Key_Plus) { 37 | settings_instance.currentDebugLevel = Math.min(settings.debugLevels, settings_instance.currentDebugLevel + 1); 38 | event.accepted = true; 39 | } else if (event.key == Qt.Key_Minus) { 40 | settings_instance.currentDebugLevel = Math.max(0, settings_instance.currentDebugLevel - 1); 41 | event.accepted = true; 42 | } 43 | } 44 | Keys.onReleased: event => { 45 | if (event.key == Qt.Key_Space) { 46 | referenceButton.down = false; 47 | event.accepted = true; 48 | } 49 | } 50 | 51 | Components.Settings { 52 | id: settings_instance 53 | algorithms: vision.algorithms 54 | debugLevels: vision.maxDebugLevel 55 | models: augmentation.models 56 | } 57 | Vision { 58 | id: vision 59 | algorithm: settings_instance.currentAlgorithm 60 | debugLevel: settings_instance.currentDebugLevel 61 | isPaused: false 62 | source: settings_instance.currentSource 63 | } 64 | Connections { 65 | function onFrameProcessed(frame_data) { 66 | augmentation.drawFrame(frame_data); 67 | } 68 | 69 | target: vision 70 | } 71 | AugmentationView { 72 | id: augmentation 73 | anchors.fill: parent 74 | model: settings_instance.currentModel 75 | z: 0 76 | } 77 | ColumnLayout { 78 | anchors.bottom: parent.bottom 79 | anchors.right: parent.right 80 | anchors.rightMargin: height / 40 81 | anchors.top: parent.top 82 | width: height / 4 83 | z: 5 // don't be shy, come closer to people 84 | 85 | RoundButton { 86 | Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom 87 | implicitHeight: width 88 | implicitWidth: parent.width / 1.5 89 | text: vision.isPaused ? "⏵" : "⏸" 90 | z: 6 91 | 92 | background: Rectangle { 93 | opacity: parent.down ? 0.5 : 0.1 94 | radius: parent.width 95 | } 96 | 97 | onPressed: vision.isPaused = !vision.isPaused 98 | } 99 | RoundButton { 100 | id: referenceButton 101 | Layout.fillWidth: true 102 | implicitHeight: width 103 | z: 6 104 | 105 | background: Rectangle { 106 | opacity: parent.down ? 0.7 : 0.5 107 | radius: parent.width 108 | } 109 | 110 | onPressed: vision.SetReference() 111 | } 112 | RoundButton { 113 | Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 114 | implicitHeight: width 115 | implicitWidth: parent.width / 1.5 116 | text: "⚙️" 117 | z: 6 118 | 119 | background: Rectangle { 120 | opacity: parent.down ? 0.5 : 0.1 121 | radius: parent.width 122 | } 123 | 124 | onPressed: openSettings() 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /source/qml/Settings.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | 3 | QtObject { 4 | property var algorithms 5 | property int currentAlgorithm 6 | property int currentDebugLevel 7 | property int currentModel 8 | property string currentSource 9 | property int debugLevels 10 | property var models 11 | } 12 | -------------------------------------------------------------------------------- /source/qml/SettingsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import QtQuick.Controls.Material 2.14 4 | import QtQuick.Layouts 1.14 5 | import QtMultimedia 6.4 6 | import "." as Components 7 | 8 | Page { 9 | id: settingsPage 10 | 11 | property Components.Settings settings 12 | 13 | signal exit 14 | 15 | Layout.fillHeight: true 16 | Layout.fillWidth: true 17 | title: "Settings" 18 | 19 | header: ToolBar { 20 | RowLayout { 21 | anchors.fill: parent 22 | 23 | ToolButton { 24 | text: "‹" 25 | 26 | onClicked: exit() 27 | } 28 | Label { 29 | Layout.fillWidth: true 30 | elide: Label.ElideRight 31 | horizontalAlignment: Qt.AlignHCenter 32 | text: settingsPage.title 33 | verticalAlignment: Qt.AlignVCenter 34 | } 35 | } 36 | } 37 | 38 | Keys.onEscapePressed: exit() 39 | 40 | GridLayout { 41 | anchors.fill: parent 42 | anchors.margins: 20 43 | columns: 2 44 | 45 | Button { 46 | Layout.columnSpan: 2 47 | Layout.fillWidth: true 48 | text: "Load Demo Video" 49 | 50 | onPressed: settings.currentSource = ":/debug_samples/3_markers_good.webm" 51 | } 52 | Text { 53 | text: "Input:" 54 | } 55 | ComboBox { 56 | Layout.fillWidth: true 57 | currentIndex: -1 58 | model: devices.videoInputs 59 | textRole: "description" 60 | 61 | delegate: ItemDelegate { 62 | text: modelData.description 63 | width: parent.width 64 | } 65 | 66 | Component.onCompleted: { 67 | // find current selected camera, if any 68 | for (var i = 0; i < model.length; i++) { 69 | if (model[i].deviceId == settings.currentSource) { 70 | currentIndex = i; 71 | } 72 | } 73 | } 74 | onActivated: index => { 75 | settings.currentSource = model[index].id; 76 | } 77 | 78 | MediaDevices { 79 | id: devices 80 | } 81 | } 82 | Text { 83 | text: "Algorithm:" 84 | } 85 | ComboBox { 86 | Layout.fillWidth: true 87 | currentIndex: settings.currentAlgorithm 88 | model: settings.algorithms 89 | 90 | onActivated: index => { 91 | settings.currentAlgorithm = index; 92 | } 93 | } 94 | Text { 95 | text: "Model:" 96 | } 97 | ComboBox { 98 | Layout.fillWidth: true 99 | currentIndex: settings.currentModel 100 | model: settings.models 101 | 102 | onActivated: inxed => { 103 | settings.currentModel = index; 104 | } 105 | } 106 | Text { 107 | text: "Debug Level:" 108 | } 109 | SpinBox { 110 | to: settings.debugLevels 111 | value: settings.currentDebugLevel 112 | 113 | onValueModified: { 114 | settings.currentDebugLevel = value; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /source/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Main.qml 4 | MainView.qml 5 | SettingsView.qml 6 | Settings.qml 7 | 8 | 9 | -------------------------------------------------------------------------------- /source/shared/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Shared 2 | find_package(Qt6 REQUIRED COMPONENTS Core) 3 | 4 | add_subdirectory(movement3d) 5 | 6 | set(CMAKE_AUTOMOC ON) 7 | 8 | add_library(frame_data STATIC frame_data.hpp) 9 | target_link_libraries( frame_data Qt::Core) 10 | -------------------------------------------------------------------------------- /source/shared/frame_data.hpp: -------------------------------------------------------------------------------- 1 | // frame_data.hpp 2 | #ifndef FRAME_DATA_HPP 3 | #define FRAME_DATA_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class FrameData : public QObject { 11 | Q_OBJECT 12 | public: 13 | FrameData (){}; 14 | FrameData (const FrameData& newdata) 15 | : QObject () 16 | , data (newdata.data){}; 17 | FrameData (std::initializer_list> data_list) 18 | : QObject () { 19 | for (const auto& data_pair : data_list) { 20 | data.insert (data_pair); 21 | } 22 | }; 23 | 24 | std::any& operator[] (const std::string& key) { 25 | return data[key]; 26 | }; 27 | 28 | std::map data; 29 | }; 30 | 31 | Q_DECLARE_METATYPE (FrameData) 32 | 33 | #endif // FRAME_DATA_HPP 34 | -------------------------------------------------------------------------------- /source/shared/movement3d/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # movement3d 2 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 3 | 4 | set(MOVEMENT3D_INC movement3d.hpp) 5 | set(MOVEMENT3D_SRC movement3d.cpp) 6 | 7 | add_library( movement3d STATIC ${MOVEMENT3D_SRC} ${MOVEMENT3D_INC} ) 8 | 9 | # movement3d_filter 10 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 11 | 12 | set(MOVEMENT3D_FILTER_INC movement3d_filter.hpp) 13 | set(MOVEMENT3D_FILTER_SRC movement3d_filter.cpp) 14 | 15 | add_library( movement3d_filter STATIC ${MOVEMENT3D_FILTER_SRC} ${MOVEMENT3D_FILTER_INC} ) 16 | target_link_libraries(movement3d_filter movement3d ) 17 | target_include_directories ( movement3d_filter PUBLIC ${INCLUDE_DIR} ) 18 | -------------------------------------------------------------------------------- /source/shared/movement3d/movement3d.cpp: -------------------------------------------------------------------------------- 1 | #include "movement3d.hpp" 2 | #include 3 | 4 | Movement3D::Movement3D () 5 | : _scale (0) 6 | , _translation ({ 0, 0 }) 7 | , _yaw (0) 8 | , _pitch (0) 9 | , _roll (0) { 10 | } 11 | Movement3D::~Movement3D () { 12 | } 13 | 14 | float Movement3D::translation_delta_to_absolute (const float d_value, 15 | const int ref_width, 16 | const float t_min, 17 | const float t_max) const { 18 | return (((t_max - t_min) * d_value) / ref_width); 19 | } 20 | 21 | void Movement3D::scale (const float s) { 22 | _scale = s; 23 | } 24 | float Movement3D::scale () const { 25 | return _scale; 26 | } 27 | 28 | void Movement3D::translation (const translation_t& t) { 29 | _translation = t; 30 | } 31 | translation_t Movement3D::translation () const { 32 | return _translation; 33 | } 34 | 35 | void Movement3D::yaw (const float y) { 36 | _yaw = y; 37 | } 38 | float Movement3D::yaw () const { 39 | return _yaw; 40 | } 41 | 42 | void Movement3D::pitch (const float p) { 43 | _pitch = p; 44 | } 45 | float Movement3D::pitch () const { 46 | return _pitch; 47 | } 48 | 49 | void Movement3D::roll (const float r) { 50 | _roll = r; 51 | } 52 | float Movement3D::roll () const { 53 | return _roll; 54 | } 55 | 56 | std::ostream& operator<< (std::ostream& os, const Movement3D& movement) { 57 | (void)os; 58 | (void)movement; 59 | os << "T(" << movement.translation ().x << "," << movement.translation ().y 60 | << ")" << std::endl; 61 | os << "S: " << movement.scale () << std::endl; 62 | os << "yaw: " << movement.yaw () << std::endl; 63 | os << "pitch: " << movement.pitch () << std::endl; 64 | os << "roll: " << movement.roll () << std::endl; 65 | return os; 66 | } 67 | 68 | Movement3D& Movement3D::operator+= (const Movement3D& movement) { 69 | this->scale (this->scale () + movement.scale ()); 70 | 71 | this->translation ({ this->translation ().x + movement.translation ().x, 72 | this->translation ().y + movement.translation ().y }); 73 | 74 | this->yaw (this->yaw () + movement.yaw ()); 75 | this->pitch (this->pitch () + movement.pitch ()); 76 | this->roll (this->roll () + movement.roll ()); 77 | 78 | return *this; 79 | } 80 | 81 | Movement3D Movement3D::operator+ (const Movement3D& movement) { 82 | *this += movement; 83 | return *this; 84 | } 85 | 86 | Movement3D& Movement3D::operator/= (const float factor) { 87 | this->scale (this->scale () / factor); 88 | 89 | this->translation ({ this->translation ().x / factor, this->translation ().y / factor }); 90 | 91 | this->yaw (this->yaw () / factor); 92 | this->pitch (this->pitch () / factor); 93 | this->roll (this->roll () / factor); 94 | 95 | return *this; 96 | } 97 | 98 | Movement3D Movement3D::operator/ (const float factor) { 99 | *this /= factor; 100 | return *this; 101 | } 102 | -------------------------------------------------------------------------------- /source/shared/movement3d/movement3d.hpp: -------------------------------------------------------------------------------- 1 | #ifndef movement3d_HPP 2 | #define movement3d_HPP 3 | 4 | #include 5 | 6 | typedef struct translation_t { 7 | float x; 8 | float y; 9 | } translation_t; 10 | 11 | class Movement3D { 12 | private: 13 | // scale 14 | float _scale; 15 | // translation 16 | translation_t _translation; 17 | // yaw 18 | float _yaw; 19 | // pitch 20 | float _pitch; 21 | // roll 22 | float _roll; 23 | 24 | public: 25 | Movement3D (); 26 | ~Movement3D (); 27 | 28 | Movement3D& operator+= (const Movement3D& movement); 29 | Movement3D operator+ (const Movement3D& movement); 30 | 31 | Movement3D& operator/= (const float factor); 32 | Movement3D operator/ (float factor); 33 | 34 | /** 35 | * convert a translation delta to an absolute value on a plane 36 | * (t_max - t_min) = steps that can be taken 37 | * [cross-multiplication] 38 | * absolute_value = steps 39 | * delta_value = ref_width 40 | * @param d_value delta value 41 | * @param ref_width reference width (e.g. image width) 42 | * @param t_min translate minimum 43 | * @param t_max translate maximum 44 | * @return the absolute value 45 | */ 46 | float translation_delta_to_absolute (const float d_value, 47 | const int ref_width, 48 | const float t_min = -1.0, 49 | const float t_max = 1.0) const; 50 | 51 | /** 52 | * rotation in x direction 53 | * @param x rotation matrix 54 | */ 55 | void scale (const float s); 56 | float scale () const; 57 | 58 | void translation (const translation_t& t); 59 | translation_t translation () const; 60 | 61 | void yaw (const float y); 62 | float yaw () const; 63 | 64 | void pitch (const float p); 65 | float pitch () const; 66 | 67 | void roll (const float r); 68 | float roll () const; 69 | }; 70 | 71 | // operator functions 72 | std::ostream& operator<< (std::ostream& os, const Movement3D& movement); 73 | 74 | #endif // movement3d_HPP 75 | -------------------------------------------------------------------------------- /source/shared/movement3d/movement3d_filter.cpp: -------------------------------------------------------------------------------- 1 | #include "movement3d_filter.hpp" 2 | #include "movement3d.hpp" 3 | 4 | Movement3DFilter::Movement3DFilter (unsigned int samples) 5 | : _samples (samples) { 6 | } 7 | 8 | Movement3DFilter::~Movement3DFilter () { 9 | } 10 | 11 | Movement3D Movement3DFilter::average () { 12 | Movement3D average; 13 | for (Movement3D movement : _movements) { 14 | average += movement; 15 | } 16 | if (!_movements.empty ()) { 17 | average /= _movements.size (); 18 | } 19 | return average; 20 | } 21 | 22 | Movement3D Movement3DFilter::average (Movement3D movement) { 23 | _movements.push_back (movement); 24 | if (_movements.size () > _samples) { 25 | _movements.pop_front (); 26 | } 27 | return average (); 28 | } 29 | -------------------------------------------------------------------------------- /source/shared/movement3d/movement3d_filter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef movement3d_filter_HPP 2 | #define movement3d_filter_HPP 3 | 4 | #include "movement3d.hpp" 5 | #include 6 | #include 7 | 8 | class Movement3DFilter { 9 | std::deque _movements; 10 | unsigned int _samples = 1; 11 | 12 | private: 13 | public: 14 | Movement3DFilter (unsigned int samples = 1); 15 | ~Movement3DFilter (); 16 | 17 | /** 18 | * calculate average between all movements stored 19 | * @return returns the calculated movement 20 | */ 21 | Movement3D average (); 22 | 23 | /** 24 | * add a movement and calculate average between all movements stored 25 | * @param movement a movement to add 26 | * @return returns the calculated movement 27 | */ 28 | Movement3D average (Movement3D movement); 29 | }; 30 | 31 | #endif // movement3d_filter_HPP 32 | -------------------------------------------------------------------------------- /source/vision/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia OpenGL) 2 | 3 | set(CMAKE_AUTOMOC ON) 4 | 5 | add_subdirectory(algorithms) 6 | 7 | add_library(vision STATIC vision.cpp) 8 | target_link_libraries(vision 9 | Qt::Core 10 | Qt::Gui 11 | Qt::Multimedia 12 | Qt::OpenGL 13 | frame_data 14 | algorithms 15 | movement3d 16 | movement3d_filter 17 | ) 18 | target_include_directories(vision PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 19 | -------------------------------------------------------------------------------- /source/vision/algorithms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # vision 2 | find_package(Qt6 REQUIRED COMPONENTS Core Multimedia OpenGL) 3 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 4 | 5 | qt_add_resources(ALGORITHM_RESOURCES gpu/shaders/vision_gpu_shaders.qrc) 6 | 7 | add_library( 8 | algorithms STATIC 9 | utils/operators.cpp 10 | utils/classification.cpp 11 | utils/frame_helper.cpp 12 | gpu/algorithm_gpu.cpp 13 | random/algorithm_random.cpp 14 | original/algorithm_original.cpp 15 | ${ALGORITHM_RESOURCES} 16 | ) 17 | 18 | # TODO(articated #44) Remove Qt6::MultemediaPrivate dependency once a suitable replacement 19 | # has been found for the QAbstractVideoBuffer API. 20 | target_link_libraries(algorithms 21 | frame_data movement3d movement3d_filter 22 | Qt::Core Qt::Multimedia Qt6::MultimediaPrivate Qt::OpenGL 23 | $<$:GLESv3> 24 | ) 25 | -------------------------------------------------------------------------------- /source/vision/algorithms/algorithm_interface.hpp: -------------------------------------------------------------------------------- 1 | // vision_algorithm.hpp 2 | 3 | #ifndef VISION_ALGORITHM_HPP 4 | #define VISION_ALGORITHM_HPP 5 | 6 | #include 7 | 8 | #include "shared/frame_data.hpp" 9 | 10 | class AlgorithmInterface { 11 | public: 12 | virtual ~AlgorithmInterface () = default; 13 | 14 | [[nodiscard]] virtual int MaxDebugLevel () const = 0; 15 | [[nodiscard]] virtual int DebugLevel () const = 0; 16 | virtual void SetDebugLevel (const int& new_level) = 0; 17 | 18 | virtual void SetReference () = 0; 19 | virtual FrameData Execute (const QVideoFrame& const_buffer) = 0; 20 | }; 21 | 22 | #endif // VISION_ALGORITHM_HPP 23 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/algorithm_gpu.cpp: -------------------------------------------------------------------------------- 1 | #include "algorithm_gpu.hpp" 2 | 3 | #include 4 | 5 | Q_LOGGING_CATEGORY (visionAlgorithmGpuLog, "vision.algorithm.gpu", QtInfoMsg) 6 | 7 | AlgorithmGpu::AlgorithmGpu () 8 | : AlgorithmInterface () 9 | , QOpenGLExtraFunctions () { 10 | Q_INIT_RESOURCE (vision_gpu_shaders); 11 | initializeOpenGLFunctions (); 12 | GenerateTextures (); 13 | CompileShaders (); 14 | GenerateFramebuffer (); 15 | GenerateVertexbuffer (); 16 | } 17 | 18 | int AlgorithmGpu::MaxDebugLevel () const { 19 | return max_debug_level_; 20 | } 21 | 22 | int AlgorithmGpu::DebugLevel () const { 23 | return debug_level_; 24 | } 25 | 26 | void AlgorithmGpu::SetDebugLevel (const int& new_level) { 27 | int level = new_level; 28 | level = level < 0 ? 0 : level; 29 | level = level > max_debug_level_ ? max_debug_level_ : level; 30 | debug_level_ = level; 31 | } 32 | 33 | void AlgorithmGpu::GenerateTextures () { 34 | // set up blurred image texture 35 | glGenTextures (1, &blurred_image_texture_); 36 | glBindTexture (GL_TEXTURE_2D, blurred_image_texture_); 37 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 38 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 39 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 40 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 41 | glTexImage2D (GL_TEXTURE_2D, 0, GL_R8, kImageProcessingWidth, 42 | kImageProcessingHeight, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); 43 | glBindTexture (GL_TEXTURE_2D, 0); 44 | 45 | // set up segmented image texture 46 | glGenTextures (1, &segmented_image_texture_); 47 | glBindTexture (GL_TEXTURE_2D, segmented_image_texture_); 48 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 49 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 50 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 51 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 52 | glTexImage2D (GL_TEXTURE_2D, 0, GL_R8, kImageProcessingWidth, 53 | kImageProcessingHeight, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); 54 | glBindTexture (GL_TEXTURE_2D, 0); 55 | 56 | // the extraction texture is created on demand by the frame helper class, init to 0 57 | extracted_image_texture_ = 0; 58 | } 59 | 60 | void AlgorithmGpu::GenerateFramebuffer () { 61 | // set up vision framebuffer 62 | glGenFramebuffers (1, &framebuffer_); 63 | } 64 | 65 | void AlgorithmGpu::GenerateVertexbuffer () { 66 | glGenVertexArrays (1, &background_vao_); 67 | glGenBuffers (1, &background_vbo_); 68 | 69 | glBindVertexArray (background_vao_); 70 | glBindBuffer (GL_ARRAY_BUFFER, background_vbo_); 71 | 72 | int pos_location = segmentation_program_.attributeLocation ("position"); 73 | glVertexAttribPointer (pos_location, 2, GL_FLOAT, GL_FALSE, 74 | 4 * sizeof (float), reinterpret_cast (0)); 75 | glEnableVertexAttribArray (pos_location); 76 | 77 | int tex_location = segmentation_program_.attributeLocation ("tex"); 78 | glVertexAttribPointer (tex_location, 2, GL_FLOAT, GL_FALSE, 79 | 4 * sizeof (float), reinterpret_cast (2 * sizeof (float))); 80 | glEnableVertexAttribArray (tex_location); 81 | 82 | // fill buffer with data 83 | GLfloat interleaved_background_buff[6 * 4] = { 84 | -1.0, 1.0, // poly 1 a 85 | 0.0, 0.0, // poly 1 a tex 86 | -1.0, -1.0, // poly 1 b 87 | 0.0, 1.0, // poly 1 b tex 88 | 1.0, 1.0, // poly 1 c 89 | 1.0, 0.0, // poly 1 c tex 90 | 1.0, 1.0, // poly 2 a 91 | 1.0, 0.0, // poly 2 a tex 92 | -1.0, -1.0, // poly 2 b 93 | 0.0, 1.0, // poly 2 b tex 94 | 1.0, -1.0, // poly 2 c 95 | 1.0, 1.0 // poly 2 c tex 96 | }; 97 | glBufferData (GL_ARRAY_BUFFER, sizeof (float) * 6 * 4, 98 | interleaved_background_buff, GL_STATIC_DRAW); 99 | } 100 | 101 | void AlgorithmGpu::CompileShaders () { 102 | { 103 | QFile vs_file (":/vision_gpu_shaders/passthrough_vs.glsl"); 104 | QFile fs_file (":/vision_gpu_shaders/blur_fs.glsl"); 105 | vs_file.open (QIODevice::ReadOnly); 106 | fs_file.open (QIODevice::ReadOnly); 107 | QByteArray vs_source = vs_file.readAll (); 108 | QByteArray fs_source = fs_file.readAll (); 109 | 110 | if (QOpenGLContext::currentContext ()->isOpenGLES ()) { 111 | vs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 112 | fs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 113 | } else { 114 | vs_source.prepend (QByteArrayLiteral ("#version 410\n")); 115 | fs_source.prepend (QByteArrayLiteral ("#version 410\n")); 116 | } 117 | 118 | blur_program_.addShaderFromSourceCode (QOpenGLShader::Vertex, vs_source); 119 | blur_program_.addShaderFromSourceCode (QOpenGLShader::Fragment, fs_source); 120 | blur_program_.link (); 121 | 122 | blur_program_.bind (); 123 | blur_program_.setUniformValue ("u_tex_background", 0); 124 | blur_program_.release (); 125 | } 126 | { 127 | QFile vs_file (":/vision_gpu_shaders/passthrough_vs.glsl"); 128 | QFile fs_file (":/vision_gpu_shaders/segment_fs.glsl"); 129 | vs_file.open (QIODevice::ReadOnly); 130 | fs_file.open (QIODevice::ReadOnly); 131 | QByteArray vs_source = vs_file.readAll (); 132 | QByteArray fs_source = fs_file.readAll (); 133 | 134 | if (QOpenGLContext::currentContext ()->isOpenGLES ()) { 135 | vs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 136 | fs_source.prepend (QByteArrayLiteral ("#version 300 es\n")); 137 | } else { 138 | vs_source.prepend (QByteArrayLiteral ("#version 410\n")); 139 | fs_source.prepend (QByteArrayLiteral ("#version 410\n")); 140 | } 141 | 142 | segmentation_program_.addShaderFromSourceCode (QOpenGLShader::Vertex, vs_source); 143 | segmentation_program_.addShaderFromSourceCode (QOpenGLShader::Fragment, fs_source); 144 | segmentation_program_.link (); 145 | 146 | segmentation_program_.bind (); 147 | segmentation_program_.setUniformValue ("u_tex_background", 0); 148 | segmentation_program_.release (); 149 | } 150 | } 151 | 152 | void AlgorithmGpu::SetReference () { 153 | markers_mutex_.lock (); 154 | reference_ = markers_; 155 | markers_mutex_.unlock (); 156 | } 157 | 158 | FrameData AlgorithmGpu::Execute (const QVideoFrame& const_buffer) { 159 | FrameData frame_data; 160 | Movement3D movement; 161 | GLuint texture_handle = 0; 162 | 163 | // Create intermediate image for RAM based processing steps 164 | image_t image; 165 | image.height = kImageProcessingHeight; 166 | image.width = kImageProcessingWidth; // TODO Fix better aspectratio 167 | // A pixel size of 4 is used, as this fits everything from R up to RGBA 168 | image.data = (uint8_t*)malloc (image.width * image.height * 4); 169 | 170 | // Upload image to GPU if necessary 171 | std::optional optional_texture = 172 | frame_helper_.FrameToTexture (const_buffer, background_is_grayscale_); 173 | if (optional_texture) { 174 | texture_handle = optional_texture.value (); 175 | } else { 176 | qCWarning (visionAlgorithmGpuLog, "Could not upload frame to texture"); 177 | } 178 | 179 | if (debug_level_ == 0) { 180 | frame_data["background"] = texture_handle; 181 | frame_data["backgroundIsGrayscale"] = background_is_grayscale_; 182 | } 183 | 184 | RenderSetup (); 185 | DownscaleAndBlur (texture_handle); 186 | if (debug_level_ == 1) { 187 | frame_data["background"] = blurred_image_texture_; 188 | frame_data["backgroundIsGrayscale"] = true; 189 | } 190 | 191 | Segmentation (image); 192 | if (debug_level_ == 2) { 193 | frame_data["background"] = segmented_image_texture_; 194 | frame_data["backgroundIsGrayscale"] = true; 195 | } 196 | RenderCleanup (); 197 | 198 | qCDebug (visionAlgorithmGpuLog, "Output texture: %u", texture_handle); 199 | 200 | if (Extraction (image, movement)) { 201 | last_movement_ = movement; 202 | } else { 203 | movement = last_movement_; 204 | } 205 | frame_data["transform"] = movement; 206 | if (debug_level_ == 3) { 207 | // following parameters are set by the Extraction operator 208 | frame_data["background"] = background_tex_; 209 | frame_data["backgroundIsGrayscale"] = background_is_grayscale_; 210 | } 211 | 212 | free (image.data); 213 | 214 | return frame_data; 215 | } 216 | 217 | void AlgorithmGpu::RenderSetup () { 218 | glBindFramebuffer (GL_FRAMEBUFFER, framebuffer_); 219 | glViewport (0, 0, kImageProcessingWidth, kImageProcessingHeight); 220 | glActiveTexture (GL_TEXTURE0); 221 | glBindVertexArray (background_vao_); 222 | } 223 | 224 | void AlgorithmGpu::RenderCleanup () { 225 | glBindVertexArray (0); 226 | glBindFramebuffer (GL_FRAMEBUFFER, 0); 227 | glUseProgram (0); 228 | } 229 | 230 | 231 | void AlgorithmGpu::DownscaleAndBlur (GLuint texture_handle) { 232 | glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 233 | blurred_image_texture_, 0); 234 | glBindTexture (GL_TEXTURE_2D, texture_handle); 235 | blur_program_.bind (); 236 | glDrawArrays (GL_TRIANGLES, 0, 6); 237 | } 238 | 239 | void AlgorithmGpu::Segmentation (image_t& image) { 240 | std::vector histogram; 241 | histogram.resize (UINT8_MAX + 1, 0); 242 | CalculateHistogram (histogram); 243 | float threshold = static_cast (CalculateThreshold (histogram)) / 255; 244 | 245 | glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 246 | segmented_image_texture_, 0); 247 | glBindTexture (GL_TEXTURE_2D, blurred_image_texture_); 248 | segmentation_program_.bind (); 249 | segmentation_program_.setUniformValue ("u_threshold", threshold); 250 | glDrawArrays (GL_TRIANGLES, 0, 6); 251 | glReadPixels ( 252 | 0, 0, image.width, image.height, GL_RED, GL_UNSIGNED_BYTE, image.data); 253 | image.format = GREY8; 254 | } 255 | 256 | void AlgorithmGpu::CalculateHistogram (std::vector& histogram) { 257 | std::vector pixels; 258 | pixels.resize (kImageProcessingHeight * kImageProcessingWidth); 259 | 260 | glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 261 | blurred_image_texture_, 0); 262 | glReadPixels (0, 0, kImageProcessingWidth, kImageProcessingHeight, GL_RED, 263 | GL_UNSIGNED_BYTE, pixels.data ()); 264 | 265 | for (int pixel : pixels) { 266 | ++histogram[pixel]; 267 | } 268 | } 269 | 270 | int AlgorithmGpu::CalculateThreshold (const std::vector& histogram) { 271 | const int pValues = 256; 272 | uint8_t lPixel = 255; 273 | uint8_t hPixel = 0; 274 | int T = 0; // mean between bright and dark 275 | int Told = 0; // old mean 276 | int cnt = 0; // some random counter for... counting 277 | // find hPixel 278 | for (cnt = pValues; cnt != 0; --cnt) { 279 | if (histogram[cnt - 1]) { 280 | hPixel = cnt - 1; 281 | cnt = 1; // not 0 because for loop 282 | } 283 | } 284 | // find lPixel 285 | for (cnt = pValues; cnt != 0; --cnt) { 286 | if (histogram[pValues - cnt]) { 287 | lPixel = pValues - cnt; 288 | cnt = 1; // not 0 because for loop 289 | } 290 | } 291 | // check for zero or same value 292 | if (lPixel == hPixel) { 293 | T = lPixel; 294 | } else { 295 | T = (int)(lPixel + hPixel) / 2 + 0.5; // center of pixels 296 | uint32_t meanDark = 0; // mean dark (from 0 to T) 297 | uint32_t meanBright = 0; // mean bright (from T to and including end) 298 | while (Told != T) { 299 | Told = T; 300 | uint32_t pCnt = 0; // pixels 301 | // mean left (using Told) 302 | // 0 to Told 303 | meanDark = 0; 304 | pCnt = 0; 305 | for (cnt = 0; cnt <= Told; ++cnt) { 306 | meanDark += cnt * histogram[cnt]; // pixel value 307 | pCnt += histogram[cnt]; // pixel count 308 | } 309 | meanDark /= pCnt; 310 | 311 | // mean right (using Told) 312 | // Told to end 313 | meanBright = 0; 314 | pCnt = 0; 315 | for (cnt = 255; cnt > Told; --cnt) { 316 | meanBright += cnt * histogram[cnt]; // pixel value 317 | pCnt += histogram[cnt]; // pixel count 318 | } 319 | meanBright /= pCnt; 320 | // mean of means (rounded) 321 | T = (int)(meanDark + meanBright) / 2 + 0.5; 322 | } 323 | } 324 | 325 | return T; 326 | } 327 | 328 | bool AlgorithmGpu::Extraction (image_t& image, Movement3D& movement) { 329 | markers_mutex_.lock (); 330 | markers_.clear (); 331 | operators_.remove_border_blobs (image, FOUR); 332 | operators_.extraction (image, markers_); 333 | if (debug_level_ == 3) { 334 | extracted_image_texture_ = 335 | frame_helper_.UploadImage (image, background_is_grayscale_); 336 | } 337 | 338 | bool is_classified = operators_.classification (reference_, markers_, movement); // classify 339 | if (is_classified) { 340 | movement = movement3d_average_.average (movement); 341 | translation_t translation = movement.translation (); 342 | movement.translation ( 343 | { movement.translation_delta_to_absolute (translation.x, image.width, -1, 1), 344 | movement.translation_delta_to_absolute (translation.y, image.height, -1, 1) }); 345 | } 346 | 347 | markers_mutex_.unlock (); 348 | 349 | return is_classified; 350 | } 351 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/algorithm_gpu.hpp: -------------------------------------------------------------------------------- 1 | // algorithm_gpu.hpp 2 | 3 | #ifndef ALGORITHM_GPU_HPP 4 | #define ALGORITHM_GPU_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../algorithm_interface.hpp" 14 | #include "../utils/frame_helper.hpp" 15 | #include "../utils/operators.hpp" 16 | #include "shared/movement3d/movement3d.hpp" 17 | #include "shared/movement3d/movement3d_filter.hpp" 18 | 19 | Q_DECLARE_LOGGING_CATEGORY (visionAlgorithmGpuLog) 20 | 21 | class AlgorithmGpu final : public AlgorithmInterface, protected QOpenGLExtraFunctions { 22 | public: 23 | AlgorithmGpu (); 24 | ~AlgorithmGpu () final = default; 25 | 26 | [[nodiscard]] int MaxDebugLevel () const final; 27 | [[nodiscard]] int DebugLevel () const final; 28 | void SetDebugLevel (const int& new_level) final; 29 | 30 | void SetReference () final; 31 | FrameData Execute (const QVideoFrame& const_buffer) final; 32 | 33 | private: 34 | const size_t kImageProcessingHeight = 400; 35 | const size_t kImageProcessingWidth = 700; 36 | 37 | void GenerateTextures (); 38 | void GenerateFramebuffer (); 39 | void GenerateVertexbuffer (); 40 | void CompileShaders (); 41 | void RenderSetup (); 42 | void RenderCleanup (); 43 | void DownscaleAndBlur (GLuint texture_handle); 44 | void Segmentation (image_t& image); 45 | void CalculateHistogram (std::vector& histogram); 46 | int CalculateThreshold (const std::vector& histogram); 47 | bool Extraction (image_t& image, Movement3D& movement); 48 | 49 | FrameHelper frame_helper_; 50 | const int max_debug_level_{ 3 }; 51 | int debug_level_{ 0 }; 52 | bool background_is_grayscale_; 53 | GLuint background_tex_; 54 | GLuint framebuffer_; 55 | GLuint blurred_image_texture_; 56 | GLuint segmented_image_texture_; 57 | GLuint extracted_image_texture_; 58 | GLuint background_vao_; 59 | GLuint background_vbo_; 60 | QOpenGLShaderProgram blur_program_; 61 | QOpenGLShaderProgram segmentation_program_; 62 | points_t markers_; 63 | points_t reference_; 64 | operators operators_; 65 | QMutex markers_mutex_; 66 | Movement3D last_movement_; 67 | Movement3DFilter movement3d_average_; 68 | }; 69 | 70 | #endif // ALGORITHM_GPU_HPP 71 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/shaders/blur_fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump int; 3 | precision mediump float; 4 | #endif 5 | 6 | uniform sampler2D u_tex_background; 7 | flat in int vtf_is_GLRED; 8 | in vec2 vtf_texcoord; 9 | 10 | out highp vec4 frag_color; 11 | 12 | void main() 13 | { 14 | vec4 tex_sample = texture(u_tex_background, vtf_texcoord); 15 | frag_color = vec4(tex_sample.r, tex_sample.r, tex_sample.r, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/shaders/passthrough_fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump int; 3 | precision mediump float; 4 | #endif 5 | 6 | uniform sampler2D u_tex_background; 7 | flat in int vtf_is_GLRED; 8 | in vec2 vtf_texcoord; 9 | 10 | out highp vec4 frag_color; 11 | 12 | void main() 13 | { 14 | vec4 tex_sample = texture(u_tex_background, vtf_texcoord); 15 | frag_color = vec4(tex_sample.r, tex_sample.r, tex_sample.r, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/shaders/passthrough_vs.glsl: -------------------------------------------------------------------------------- 1 | in vec2 position; 2 | in vec2 tex; 3 | 4 | uniform int is_GLRED; 5 | 6 | flat out int vtf_is_GLRED; 7 | out vec2 vtf_texcoord; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(position.x, -position.y, 0.0, 1.0); 12 | vtf_texcoord = tex; 13 | vtf_is_GLRED = is_GLRED; 14 | } 15 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/shaders/segment_fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump int; 3 | precision mediump float; 4 | #endif 5 | 6 | uniform sampler2D u_tex_background; 7 | uniform float u_threshold; 8 | in vec2 vtf_texcoord; 9 | 10 | out highp vec4 frag_color; 11 | 12 | void main() 13 | { 14 | vec4 tex_sample = texture(u_tex_background, vtf_texcoord); 15 | float binary_value = mix(1.0, 0.0, step(u_threshold, tex_sample.r)); 16 | frag_color = vec4(binary_value, 0.0, 0.0, 1.0); 17 | } 18 | -------------------------------------------------------------------------------- /source/vision/algorithms/gpu/shaders/vision_gpu_shaders.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | passthrough_vs.glsl 4 | passthrough_fs.glsl 5 | blur_fs.glsl 6 | segment_fs.glsl 7 | 8 | 9 | -------------------------------------------------------------------------------- /source/vision/algorithms/original/algorithm_original.cpp: -------------------------------------------------------------------------------- 1 | #include "algorithm_original.hpp" 2 | 3 | 4 | Q_LOGGING_CATEGORY (visionAlgorithmOriginalLog, "vision.algorithm.original", QtInfoMsg) 5 | 6 | void AlgorithmOriginal::SetReference () { 7 | markers_mutex_.lock (); 8 | reference_ = markers_; 9 | markers_mutex_.unlock (); 10 | } 11 | 12 | int AlgorithmOriginal::MaxDebugLevel () const { 13 | return max_debug_level_; 14 | } 15 | 16 | int AlgorithmOriginal::DebugLevel () const { 17 | return debug_level_; 18 | } 19 | 20 | void AlgorithmOriginal::SetDebugLevel (const int& new_level) { 21 | int level = new_level; 22 | level = level < 0 ? 0 : level; 23 | level = level > max_debug_level_ ? max_debug_level_ : level; 24 | debug_level_ = level; 25 | } 26 | 27 | void AlgorithmOriginal::SetBackground (image_t image) { 28 | bool is_grayscale = false; 29 | background_tex_ = frame_helper_.UploadImage (image, is_grayscale); 30 | background_is_grayscale_ = is_grayscale; 31 | } 32 | 33 | FrameData AlgorithmOriginal::Execute (const QVideoFrame& const_buffer) { 34 | bool status = true; 35 | Movement3D movement; 36 | image_t image; 37 | 38 | if (debug_level_ == 0) { 39 | auto tex = frame_helper_.FrameToTexture (const_buffer, background_is_grayscale_); 40 | if (tex.has_value ()) { 41 | background_tex_ = tex.value (); 42 | } else { 43 | qCWarning (visionAlgorithmOriginalLog) 44 | << "Could not load videoframe into texture"; 45 | } 46 | } 47 | 48 | status = frame_helper_.FrameToRam (const_buffer, image, true, background_tex_); 49 | if (!status) { 50 | qCWarning (visionAlgorithmOriginalLog) 51 | << "Could not map videoframe to RAM"; 52 | } else { 53 | status = Process (image, movement); 54 | free (image.data); 55 | } 56 | 57 | if (status) { 58 | last_movement_ = movement; 59 | } else { 60 | movement = last_movement_; 61 | } 62 | 63 | return { { "transform", movement }, { "background", background_tex_ }, 64 | { "backgroundIsGrayscale", background_is_grayscale_ } }; 65 | } 66 | 67 | 68 | bool AlgorithmOriginal::Process (image_t& image, Movement3D& movement) { 69 | // start image processing 70 | operators_.preprocessing (image); 71 | if (debug_level_ == 1) { 72 | 73 | SetBackground (image); 74 | } 75 | 76 | operators_.segmentation (image); 77 | if (debug_level_ == 2) { 78 | SetBackground (image); 79 | } 80 | 81 | markers_mutex_.lock (); 82 | markers_.clear (); 83 | operators_.extraction (image, markers_); 84 | if (debug_level_ == 3) { 85 | SetBackground (image); 86 | } 87 | 88 | bool is_clasified = operators_.classification (reference_, markers_, movement); // classify 89 | if (is_clasified) { 90 | movement = movement3d_average_.average (movement); 91 | translation_t translation = movement.translation (); 92 | movement.translation ( 93 | { movement.translation_delta_to_absolute (translation.x, image.width, -1, 1), 94 | movement.translation_delta_to_absolute (translation.y, image.height, -1, 1) }); 95 | } 96 | 97 | markers_mutex_.unlock (); 98 | 99 | return is_clasified; 100 | } 101 | -------------------------------------------------------------------------------- /source/vision/algorithms/original/algorithm_original.hpp: -------------------------------------------------------------------------------- 1 | // algorithm_original.hpp 2 | 3 | #ifndef ALGORITHM_ORIGINAL_HPP 4 | #define ALGORITHM_ORIGINAL_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../algorithm_interface.hpp" 11 | #include "../utils/frame_helper.hpp" 12 | #include "../utils/operators.hpp" 13 | #include "shared/movement3d/movement3d.hpp" 14 | #include "shared/movement3d/movement3d_filter.hpp" 15 | 16 | Q_DECLARE_LOGGING_CATEGORY (visionAlgorithmOriginalLog) 17 | 18 | class AlgorithmOriginal final : public AlgorithmInterface { 19 | public: 20 | ~AlgorithmOriginal () final = default; 21 | 22 | [[nodiscard]] int MaxDebugLevel () const final; 23 | [[nodiscard]] int DebugLevel () const final; 24 | void SetDebugLevel (const int& new_level) final; 25 | 26 | void SetReference () final; 27 | FrameData Execute (const QVideoFrame& const_buffer) final; 28 | 29 | private: 30 | bool Process (image_t& image, Movement3D& movement); 31 | void SetBackground (image_t image); 32 | 33 | FrameHelper frame_helper_; 34 | const int max_debug_level_{ 3 }; 35 | int debug_level_{ 0 }; 36 | bool background_is_grayscale_{}; 37 | GLuint background_tex_{}; 38 | points_t markers_; 39 | points_t reference_; 40 | operators operators_; 41 | QMutex markers_mutex_; 42 | Movement3D last_movement_; 43 | Movement3DFilter movement3d_average_{ 1 }; 44 | }; 45 | 46 | #endif // ALGORITHM_ORIGINAL_HPP 47 | -------------------------------------------------------------------------------- /source/vision/algorithms/random/algorithm_random.cpp: -------------------------------------------------------------------------------- 1 | #include "algorithm_random.hpp" 2 | 3 | #include 4 | 5 | Q_LOGGING_CATEGORY (visionAlgorithmRandomLog, "vision.algorithm.random", QtInfoMsg) 6 | 7 | AlgorithmRandom::AlgorithmRandom () 8 | : AlgorithmInterface () 9 | , max_debug_level_ (0) 10 | , last_movement_ () 11 | , random_movement_ () { 12 | last_movement_.scale (1.0f); 13 | last_movement_.yaw (1.0f); 14 | last_movement_.pitch (1.0f); 15 | last_movement_.roll (1.0f); 16 | } 17 | 18 | int AlgorithmRandom::MaxDebugLevel () const { 19 | return max_debug_level_; 20 | } 21 | 22 | int AlgorithmRandom::DebugLevel () const { 23 | return debug_level_; 24 | } 25 | 26 | void AlgorithmRandom::SetDebugLevel (const int& new_level) { 27 | int level = new_level; 28 | level = level < 0 ? 0 : level; 29 | level = level > max_debug_level_ ? max_debug_level_ : level; 30 | debug_level_ = level; 31 | } 32 | 33 | void AlgorithmRandom::SetReference () { 34 | movement_mutex_.lock (); 35 | translation_t trans = { 0.0f, 0.0f }; 36 | random_movement_.translation (trans); 37 | random_movement_.scale (0.1f); 38 | random_movement_.yaw (1.0f); 39 | random_movement_.pitch (1.0f); 40 | random_movement_.roll (1.0f); 41 | movement_mutex_.unlock (); 42 | } 43 | 44 | FrameData AlgorithmRandom::Execute (const QVideoFrame& const_buffer) { 45 | bool background_is_grayscale; 46 | std::optional texture = 47 | frame_helper_.FrameToTexture (const_buffer, background_is_grayscale); 48 | if (!texture) { 49 | qCWarning (visionAlgorithmRandomLog, "Could not upload frame to texture"); 50 | } 51 | 52 | movement_mutex_.lock (); 53 | Movement3D movement = last_movement_ + random_movement_; 54 | 55 | if (movement.scale () < 0.2f) { 56 | random_movement_.scale (std::fabs (random_movement_.scale ())); 57 | } else if (movement.scale () > 5.0f) { 58 | random_movement_.scale (-std::fabs (random_movement_.scale ())); 59 | } 60 | 61 | translation_t trans = movement.translation (); 62 | translation_t new_random_trans = random_movement_.translation (); 63 | if (-1 > trans.x || trans.x > 1) { 64 | new_random_trans.x = -new_random_trans.x; 65 | } 66 | if (-1 > trans.y || trans.y > 1) { 67 | new_random_trans.y = -new_random_trans.y; 68 | } 69 | random_movement_.translation (new_random_trans); 70 | 71 | movement.yaw (std::fmod (movement.yaw (), 360.0f)); 72 | movement.pitch (std::fmod (movement.pitch (), 360.0f)); 73 | movement.roll (std::fmod (movement.roll (), 360.0f)); 74 | 75 | last_movement_ = movement; 76 | movement_mutex_.unlock (); 77 | 78 | return { { "transform", movement }, { "background", texture.value_or (0) }, 79 | { "backgroundIsGrayscale", background_is_grayscale } }; 80 | } 81 | -------------------------------------------------------------------------------- /source/vision/algorithms/random/algorithm_random.hpp: -------------------------------------------------------------------------------- 1 | // algorithm_random.hpp 2 | 3 | #ifndef ALGORITHM_RANDOM_HPP 4 | #define ALGORITHM_RANDOM_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../algorithm_interface.hpp" 11 | #include "../utils/frame_helper.hpp" 12 | #include "shared/movement3d/movement3d.hpp" 13 | 14 | Q_DECLARE_LOGGING_CATEGORY (visionAlgorithmRandomLog) 15 | 16 | class AlgorithmRandom final : public AlgorithmInterface { 17 | public: 18 | AlgorithmRandom (); 19 | ~AlgorithmRandom () final = default; 20 | 21 | [[nodiscard]] int MaxDebugLevel () const final; 22 | [[nodiscard]] int DebugLevel () const final; 23 | void SetDebugLevel (const int& new_level) final; 24 | 25 | void SetReference () final; 26 | FrameData Execute (const QVideoFrame& const_buffer) final; 27 | 28 | private: 29 | FrameHelper frame_helper_; 30 | const int max_debug_level_; 31 | int debug_level_{}; 32 | QMutex movement_mutex_; 33 | Movement3D last_movement_; 34 | Movement3D random_movement_; 35 | }; 36 | 37 | #endif // ALGORITHM_RANDOM_HPP 38 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/classification.hpp: -------------------------------------------------------------------------------- 1 | // classification.hpp 2 | #ifndef CLASSIFICATION_HPP 3 | #define CLASSIFICATION_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "point.hpp" 13 | #include "shared/movement3d/movement3d.hpp" 14 | 15 | /** 16 | * A line 17 | */ 18 | typedef struct line_t { 19 | point_t p1; 20 | point_t p2; 21 | } line_t; 22 | 23 | template struct kahan_accumulation { 24 | kahan_accumulation () 25 | : sum (0) 26 | , correction (0) { 27 | } 28 | T sum; 29 | T correction; 30 | }; 31 | 32 | class Classification { 33 | public: 34 | const unsigned int _minimum_ref_points = 3; 35 | const unsigned int _maximum_ref_points = 40; 36 | 37 | Classification (); 38 | ~Classification (); 39 | 40 | 41 | /** 42 | * Quaternion 43 | * Z [↻ Yaw ψ] X [⟲ Roll φ] 44 | * ^ ^ 45 | * | / 46 | * | / 47 | * | / 48 | * | / 49 | * | / 50 | * +--------> Y [⟲ Pitch θ] 51 | */ 52 | 53 | /** 54 | * get scale from a number of keypoints to the reference 55 | * Scale (z) 56 | * @param marker_points [description] 57 | * @return [description] 58 | */ 59 | float scale (const points_t& reference_points, 60 | const points_t& data_points, 61 | unsigned int granularity = 5); 62 | 63 | /** 64 | * classify the translation from a number of keypoints to the reference 65 | * Translation (x, y) 66 | * @param marker_points [description] 67 | * @return [description] 68 | */ 69 | translation_t translation (const points_t& reference_points, const points_t& data_points); 70 | 71 | /** 72 | * classify the rotation from a number of keypoints to the reference 73 | * Rotation (yaw) 74 | * @param marker_points [description] 75 | * @return [description] 76 | */ 77 | float yaw (const points_t& reference_points, const points_t& data_points); 78 | 79 | // angle pitch 80 | float pitch (const points_t& reference_points, const points_t& data_points); 81 | 82 | // angle roll 83 | float roll (const points_t& reference_points, const points_t& data_points); 84 | 85 | /** 86 | * translate a set of points 87 | * @param points [description] 88 | * @param T [description] 89 | */ 90 | void translate (points_t& points, translation_t T); 91 | 92 | /** 93 | * scale a set of points from centroid with value "scale" 94 | * @param points a set of points 95 | * @param scale the scale factor 96 | */ 97 | void scale (points_t& points, const float scale); 98 | 99 | /** 100 | * calculates the delta angle between two vectors 101 | * the shortest angle from p1 to p2 102 | * where the origin is 0,0 using the dot product method 103 | * in range of 0 - 2PI 104 | * @param p1 [description] 105 | * @param p2 [description] 106 | * @return returns the angle in radians 107 | */ 108 | float dot_product (const point_t& p1, const point_t& p2); 109 | 110 | /** 111 | * calculates the delta angle between two vectors 112 | * the shortest angle from p1 to p2 113 | * where the origin is 0,0 using the dot product method 114 | * in range of 0 - 360 degrees 115 | * @param p1 [description] 116 | * @param p2 [description] 117 | * @return returns the angle in degrees 118 | */ 119 | float dot_product_degrees (const point_t& p1, const point_t& p2); 120 | 121 | /** 122 | * calculates the absolute delta angle 123 | * in this example 'R' and 'D' will be switched 124 | * when D>R 125 | * 126 | * R/| 127 | * / 128 | * /a 129 | * |---| 130 | * D 131 | * a: alpha (angle) 132 | * R: first value(reference) 133 | * D: second value(Data) 134 | * a (R>D): cos-1(D/R) 135 | * a (D>R): cos-1(R/D) 136 | * 137 | * @param ref t 138 | * @param data [description] 139 | * @return angle in degrees 140 | */ 141 | float projected_angle_abs (const float R, const float D); 142 | 143 | /** 144 | * calculate the centroid of a set of points 145 | * @param points are the keypoints of the "shape" 146 | * @return returns the centroid of the points 147 | */ 148 | point_t centroid (const points_t& points); 149 | 150 | /** 151 | * sums all the values using a kahan accumulation algorithm 152 | * @param values the values to sum 153 | * @return returns the sum of the values 154 | */ 155 | template T sum (std::vector values); 156 | 157 | /** 158 | * sums all the points in a map 159 | * @param points are the keypoints to sum 160 | * @return returns the sum of all values 161 | */ 162 | point_t sum (const points_t& points); 163 | 164 | /** 165 | * find closes number in a vector to a value 166 | * @param vec vector with values 167 | * @param compare value to compare to 168 | * @return returns value from vector closes to compare 169 | */ 170 | float closest (const std::vector& vec, float compare); 171 | 172 | /** 173 | * check if a point is in front of a point on a line 174 | * - there is no checking if point is on line 175 | * - returns false if p1 and p2 from line are the same 176 | * @param L Line where p1 is the front and p2 is the back 177 | * @param R the reference from which to check 178 | * @param P the point that is checked 179 | * @return returns true if in front, false if not 180 | */ 181 | bool is_in_front (line_t L, point_t R, point_t P); 182 | 183 | /** 184 | * check if value is equal to each other using error values 185 | * @param a val1 186 | * @param b val2 187 | * @param error the error 188 | * @return true if equal, false of not equal 189 | */ 190 | bool equal (float a, float b, float error = FLT_EPSILON); 191 | 192 | /** 193 | * compare floats using ULP and absolute method 194 | * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 195 | * @param A [description] 196 | * @param B [description] 197 | * @param maxDiff [description] 198 | * @param maxUlpsDiff [description] 199 | * @return [description] 200 | */ 201 | bool almost_equal_ulp_abs (float A, float B, float maxDiff, int maxUlpsDiff); 202 | 203 | /** 204 | * compare floats using relative and absolute method 205 | * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 206 | * @param A [description] 207 | * @param B [description] 208 | * @param maxDiff [description] 209 | * @param maxRelDiff [description] 210 | * @return [description] 211 | */ 212 | bool almost_equal_rel_abs (float A, float B, float maxDiff, float maxRelDiff = FLT_EPSILON); 213 | 214 | /** 215 | * calculates the X and Y intersection values 216 | * this solves these values by using the 217 | * Y = AX+B formula (line formula) 218 | * @param A point A 219 | * @param B point B 220 | * @param origin origin in these points 221 | * @return returns the intersection values 222 | * [x is intersection on x axis] 223 | * [y is intersection on y axis] 224 | * returns 0,0 by devision by zero 225 | */ 226 | point_t intersections (point_t A, point_t B, point_t origin = { 0, 0 }); 227 | 228 | /** 229 | * calculate intersection between two lines 230 | * @param l1 line 231 | * @param l2 line 232 | * @return returns intersection 233 | */ 234 | point_t intersection (line_t l1, line_t l2); 235 | 236 | /** 237 | * A of Y=AX+B 238 | * @param line 239 | * @return returns A 240 | */ 241 | float a (line_t line); 242 | /** 243 | * B of Y=AX+B 244 | * @param line 245 | * @return returns B 246 | */ 247 | float b (line_t line); 248 | 249 | /** 250 | * calculates the x value 251 | * Y = AX+B 252 | * @param y the Y parameters 253 | * @param line 254 | * @return returns X 255 | */ 256 | float x (float y, line_t line); 257 | 258 | /** 259 | * calculates the y value 260 | * X = (Y-B)/A 261 | * @param x the X parameters 262 | * @param line 263 | * @return returns Y 264 | */ 265 | float y (float x, line_t line); 266 | 267 | /** 268 | * calculate absolute distance between two points 269 | * @param A point A 270 | * @param B point B 271 | * @return returns the absolute distance 272 | */ 273 | float distance (point_t p1, point_t p2); 274 | 275 | /** 276 | * matches the points to the reference. 277 | * so that all the points in the marker_points are 278 | * available in the reference 279 | * @param marker_points are the reference marker_points 280 | */ 281 | void match_points (const points_t& reference_points, points_t& data_points); 282 | 283 | /** 284 | * find an intersection between two points from "ref" through "line" 285 | * where the intersection is no 286 | * - 0 (centroid of ref points) 287 | * - NaN 288 | * - x or y is inf (parallel) 289 | * then the matching intersection is found in the "data" points 290 | * then the distance from "ref" centroid to "ref" intersection 291 | * and the distance from "data" centroid to "data" intersection 292 | * is returned 293 | * 294 | * @param reference_points reference points 295 | * @param data_points data points 296 | * @param line [description] 297 | * @param ref_length [description] 298 | * @param data_length [description] 299 | */ 300 | void find_matching_intersection (const points_t& reference_points, 301 | const points_t& data_points, 302 | const line_t& line, 303 | float& ref_length, 304 | float& data_length); 305 | 306 | /** 307 | * convert value to an std::string 308 | * @param value to be converted 309 | * @return returns a std::string 310 | */ 311 | template std::string to_string (T value); 312 | }; 313 | 314 | #endif // CLASSIFICATION_HPP 315 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/frame_helper.cpp: -------------------------------------------------------------------------------- 1 | #include "frame_helper.hpp" 2 | 3 | #include 4 | // TODO (articated #44) Remove private include when suitable replacement API has been found 5 | #include 6 | 7 | FrameHelper::FrameHelper () 8 | : QOpenGLExtraFunctions () { 9 | initializeOpenGLFunctions (); 10 | // Generate the video frame texture 11 | glGenTextures (1, &frame_texture_); 12 | glBindTexture (GL_TEXTURE_2D, frame_texture_); 13 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 14 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 15 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 16 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 17 | glBindTexture (GL_TEXTURE_2D, 0); 18 | 19 | // Generate the download buffer 20 | glGenFramebuffers (1, &framebuffer_download_); 21 | } 22 | 23 | FrameHelper::~FrameHelper () { 24 | glDeleteTextures (1, &frame_texture_); 25 | glDeleteFramebuffers (1, &framebuffer_download_); 26 | } 27 | 28 | bool FrameHelper::FrameToRam (const QVideoFrame& const_buffer, image_t& image) { 29 | GLuint unused_text; 30 | return FrameToRam (const_buffer, image, false, unused_text); 31 | } 32 | 33 | bool FrameHelper::FrameToRam (const QVideoFrame& const_buffer, 34 | image_t& image, 35 | bool output_to_texture, 36 | GLuint& output_texture) { 37 | bool status = true; 38 | QVideoFrame frame (const_buffer); 39 | if (!frame.map (QVideoFrame::MapMode::ReadOnly)) { 40 | // Could not map frame into CPU RAM! 41 | return false; 42 | } 43 | auto mapped_bytes = frame.mappedBytes (0); 44 | image.data = (uint8_t*)malloc (mapped_bytes); 45 | memcpy (image.data, frame.bits (0), mapped_bytes); 46 | 47 | if (frame.pixelFormat () == QVideoFrameFormat::PixelFormat::Format_XRGB8888) { 48 | image.format = RGB24; 49 | } else if (frame.pixelFormat () == QVideoFrameFormat::PixelFormat::Format_XBGR8888) { 50 | image.format = BGR32; 51 | } else if (frame.pixelFormat () == QVideoFrameFormat::PixelFormat::Format_YUV420P) { 52 | image.format = YUV; 53 | } else { 54 | // Unsupported image format 55 | status = false; 56 | delete image.data; 57 | } 58 | 59 | frame.unmap (); 60 | if (status) { 61 | image.width = frame.width (); 62 | image.height = frame.height (); 63 | if (output_to_texture) { 64 | bool unused_is_grayscale; 65 | output_texture = UploadImage (image, unused_is_grayscale); 66 | } 67 | } 68 | return status; 69 | } 70 | 71 | std::optional 72 | FrameHelper::FrameToTexture (const QVideoFrame& const_buffer, bool& is_grayscale) { 73 | bool status = true; 74 | GLuint texture_handle; 75 | GLuint format; 76 | 77 | if (const_buffer.isValid ()) { 78 | // copy image into cpu memory 79 | switch (const_buffer.handleType ()) { 80 | case QVideoFrame::HandleType::NoHandle: { 81 | // if the frame can be mapped 82 | QVideoFrame frame (const_buffer); 83 | if (frame.map (QVideoFrame::MapMode::ReadOnly)) { 84 | GLuint internalformat; 85 | glBindTexture (GL_TEXTURE_2D, frame_texture_); 86 | 87 | if (frame.pixelFormat () == QVideoFrameFormat::PixelFormat::Format_XRGB8888) { 88 | is_grayscale = false; 89 | internalformat = GL_RGB; 90 | format = GL_RGB; 91 | } else if (frame.pixelFormat () == 92 | QVideoFrameFormat::PixelFormat::Format_XBGR8888) { 93 | is_grayscale = false; 94 | internalformat = GL_RGB; 95 | format = GL_ABGR_EXT; 96 | } else if (frame.pixelFormat () == 97 | QVideoFrameFormat::PixelFormat::Format_YUV420P) { 98 | is_grayscale = true; 99 | internalformat = GL_R8; 100 | format = GL_RED; 101 | } else { 102 | is_grayscale = false; 103 | status = false; 104 | } 105 | 106 | if (status) { 107 | glTexImage2D (GL_TEXTURE_2D, 0, internalformat, frame.width (), 108 | frame.height (), 0, format, GL_UNSIGNED_BYTE, frame.bits (0)); 109 | texture_handle = frame_texture_; 110 | } 111 | glBindTexture (GL_TEXTURE_2D, 0); 112 | } else { 113 | // Failed to map frame to memory 114 | status = false; 115 | } 116 | frame.unmap (); 117 | break; 118 | } 119 | case QVideoFrame::HandleType::RhiTextureHandle: { 120 | // if the frame is an OpenGL texture 121 | QVideoFrameFormat::PixelFormat frame_format = const_buffer.pixelFormat (); 122 | 123 | // TODO(articated #44) Remove usuage of private QAbstractVideoBuffer API 124 | // once a suitable way to access the native OpenGL texture handle of a QVideoFrame has been found. 125 | auto tex_name = const_buffer.videoBuffer ()->textureHandle (0); 126 | if (frame_format == QVideoFrameFormat::PixelFormat::Format_XBGR8888 || 127 | frame_format == QVideoFrameFormat::PixelFormat::Format_XRGB8888) { 128 | texture_handle = tex_name; 129 | format = GL_RGB; 130 | } else if (frame_format == QVideoFrameFormat::PixelFormat::Format_YUV420P) { 131 | texture_handle = tex_name; 132 | format = GL_RED; 133 | } else { 134 | // Unsupported frame format 135 | status = false; 136 | } 137 | break; 138 | } 139 | default: { 140 | // Unsupported handle type 141 | status = false; 142 | break; 143 | } 144 | } 145 | } else { 146 | status = false; 147 | } 148 | 149 | if (status) { 150 | return { texture_handle }; 151 | } else { 152 | return std::nullopt; 153 | } 154 | } 155 | 156 | void FrameHelper::DownloadImage (image_t& image, GLuint handle) { 157 | GLuint _previous_framebuffer; 158 | glGetIntegerv (GL_FRAMEBUFFER_BINDING, (GLint*)&_previous_framebuffer); 159 | glBindFramebuffer (GL_FRAMEBUFFER, framebuffer_download_); 160 | glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, handle, 0); 161 | glReadPixels ( 162 | 0, 0, image.width, image.height, GL_RGBA, GL_UNSIGNED_BYTE, image.data); 163 | glBindFramebuffer (GL_FRAMEBUFFER, _previous_framebuffer); 164 | } 165 | 166 | GLuint FrameHelper::UploadImage (image_t image, bool& is_grayscale) { 167 | bool status = true; 168 | GLint format_gl; 169 | GLint internalformat_gl; 170 | glBindTexture (GL_TEXTURE_2D, frame_texture_); 171 | 172 | is_grayscale = false; 173 | switch (image.format) { 174 | case RGB24: { 175 | format_gl = GL_RGB; 176 | internalformat_gl = GL_RGB; 177 | break; 178 | } 179 | case YUV: 180 | case GREY8: 181 | case BINARY8: { 182 | internalformat_gl = GL_R8; 183 | format_gl = GL_RED; 184 | is_grayscale = true; 185 | break; 186 | } 187 | case BGR32: { 188 | status = false; 189 | break; 190 | } 191 | } 192 | 193 | if (status) { 194 | glTexImage2D (GL_TEXTURE_2D, 0, internalformat_gl, image.width, 195 | image.height, 0, format_gl, GL_UNSIGNED_BYTE, image.data); 196 | } 197 | 198 | glBindTexture (GL_TEXTURE_2D, 0); 199 | 200 | return frame_texture_; 201 | } 202 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/frame_helper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FRAMEHELPER_HPP 2 | #define FRAMEHELPER_HPP 3 | 4 | #ifdef ANDROID 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "image.hpp" 16 | #include "shared/frame_data.hpp" 17 | #include "shared/movement3d/movement3d.hpp" 18 | 19 | 20 | class FrameHelper : protected QOpenGLExtraFunctions { 21 | public: 22 | FrameHelper (); 23 | ~FrameHelper (); 24 | 25 | std::optional FrameToTexture (const QVideoFrame& const_buffer, bool& is_grayscale); 26 | bool FrameToRam (const QVideoFrame& const_buffer, image_t& image); 27 | bool FrameToRam (const QVideoFrame& const_buffer, 28 | image_t& image, 29 | bool output_to_texture, 30 | GLuint& output_texture); 31 | 32 | void DownloadImage (image_t& image, GLuint handle); 33 | GLuint UploadImage (image_t image, bool& is_grayscale); 34 | 35 | void SetBackground (image_t image); 36 | 37 | private: 38 | GLuint framebuffer_download_; 39 | GLuint frame_texture_; 40 | }; 41 | 42 | 43 | #endif // FRAMEHELPER_HPP 44 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/image.hpp: -------------------------------------------------------------------------------- 1 | // image.hpp 2 | #ifndef IMAGE_HPP 3 | #define IMAGE_HPP 4 | 5 | #include 6 | 7 | typedef enum { RGB24 = 0, BGR32, YUV, GREY8, BINARY8 } format_t; 8 | 9 | typedef struct image_t { 10 | uint8_t* data; 11 | unsigned width; 12 | unsigned height; 13 | format_t format; 14 | } image_t; 15 | 16 | #endif // IMAGE_HPP 17 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/operators.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OPERATORS_HPP 2 | #define OPERATORS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "classification.hpp" 12 | #include "image.hpp" 13 | #include "point.hpp" 14 | #include "shared/movement3d/movement3d.hpp" 15 | 16 | typedef enum { BRIGHT = 0, DARK } eBrightness; 17 | 18 | typedef enum { FOUR = 4, EIGHT = 8 } eConnected; 19 | 20 | const int PREFERED_IMAGE_HEIGHT = 480; 21 | const int PREFERED_IMAGE_WIDTH = 850; 22 | const int MAX_PIXEL_COUNT = 500000; 23 | 24 | class operators { 25 | public: 26 | operators (); 27 | ~operators (); 28 | 29 | /** 30 | * apply filters on image 31 | * @param data pointer to greyscale-image buffer 32 | * @param width width of image (colum count) 33 | * @param height height of image (row count) 34 | */ 35 | void preprocessing (image_t& image); 36 | 37 | /** 38 | * segment the greyscale image to binary 39 | * @param data pointer to image buffer 40 | * @param width width of image (colum count) 41 | * @param height height of image (row count) 42 | */ 43 | void segmentation (image_t& image); 44 | 45 | 46 | /** 47 | * extract the usefull infomration from the binary-image 48 | * @param data pointer to image buffer 49 | * @param width width of image (colum count) 50 | * @param height height of image (row count) 51 | */ 52 | void extraction (image_t& image, points_t& markers); 53 | 54 | bool classification (const points_t& reference, const points_t& data, Movement3D& movement); 55 | 56 | void convert_to_grey (image_t& image); 57 | 58 | /** 59 | * preform a greyscale "average filter" on image, with window size n*n 60 | * @param image the greyscale image 61 | * @param n the window size, has to be odd 62 | */ 63 | void filter_and_scale (image_t& image, unsigned n); 64 | 65 | void histogram (image_t& image, uint16_t* hist, uint32_t& sum); 66 | 67 | void threshold (image_t& image, uint8_t low, uint8_t high); 68 | 69 | void threshold_iso_data (image_t& image, eBrightness brightness); 70 | 71 | void copy (image_t& src, image_t& dst); 72 | 73 | void set_borders (image_t& image, uint8_t value); 74 | 75 | void set_selected_to_value (image_t& image, uint8_t selected, uint8_t value); 76 | 77 | uint8_t neighbour_count (image_t& img, uint32_t x, uint32_t y, uint8_t value, eConnected connected); 78 | 79 | void remove_border_blobs (image_t& img, eConnected connected); 80 | 81 | uint32_t label_blobs (image_t& image); 82 | 83 | void analyse_blobs (image_t& img, 84 | const unsigned blob_count, 85 | const unsigned min_area, 86 | std::vector& blobs); 87 | 88 | void extract_groups (std::vector key_points, 89 | std::vector>& potential_markers); 90 | 91 | void extract_groups_link (std::map>& neighbours, 92 | std::vector& potential_marker, 93 | const keypoint_t& point); 94 | 95 | void extract_markers (std::vector>& potential_markers, 96 | points_t& markers); 97 | 98 | /** 99 | * replace all pixes of old value with new value 100 | * @param image a binary image 101 | * @param old_value old value 102 | * @param new_value new value 103 | */ 104 | void replace_value (image_t& image, uint8_t old_value, uint8_t new_value); 105 | 106 | 107 | /** 108 | * find the lowest value of any of the 8 surronding neighbours of pixel 109 | * [x,y] 110 | * @param image a binary image 111 | * @param x x coordinate 112 | * @param y y coordinate 113 | * @return lowest value in neighbourhood 114 | */ 115 | uint8_t neighbours_minimum (image_t& image, int x, int y); 116 | }; 117 | 118 | #endif // OPERATORS_HPP 119 | -------------------------------------------------------------------------------- /source/vision/algorithms/utils/point.hpp: -------------------------------------------------------------------------------- 1 | // point.hpp 2 | #ifndef POINT_HPP 3 | #define POINT_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | const float POINT_MIN = std::numeric_limits::min (); 11 | const float POINT_ZERO = 0; 12 | const float POINT_MAX = std::numeric_limits::max (); 13 | const float POINT_INF = std::numeric_limits::infinity (); 14 | 15 | /** 16 | * point is an arbitrary interesting point 17 | */ 18 | typedef struct point_t { 19 | float x; 20 | float y; 21 | } point_t; 22 | /** 23 | * keypoint is an arbitrary interesting point 24 | * that has some identifier 25 | */ 26 | typedef struct keypoint_t { 27 | unsigned int id; 28 | point_t p; 29 | } keypoint_t; 30 | 31 | typedef std::map points_t; 32 | 33 | #endif // POINT_HPP 34 | -------------------------------------------------------------------------------- /source/vision/vision.cpp: -------------------------------------------------------------------------------- 1 | #include "vision.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Q_LOGGING_CATEGORY (visionLog, "vision", QtInfoMsg) 10 | 11 | Vision::Vision () { 12 | InitializeOpenGL (); 13 | SetAlgorithm (-1); 14 | SetSourceCamera (""); 15 | SetPaused (false); 16 | } 17 | 18 | Vision::~Vision () { 19 | delete vision_algorithm_; 20 | } 21 | 22 | void Vision::InitializeOpenGL () { 23 | dummy_surface_.create (); 24 | opengl_context_.setShareContext (QOpenGLContext::globalShareContext ()); 25 | opengl_context_.create (); 26 | } 27 | 28 | int Vision::GetAndClearFailedFrameCount () { 29 | int ret = failed_frames_counter_; 30 | failed_frames_counter_ = 0; 31 | return ret; 32 | } 33 | 34 | void Vision::SetAlgorithm (int idx) { 35 | opengl_context_.makeCurrent (&dummy_surface_); 36 | if (vision_algorithm_ != NULL) { 37 | delete vision_algorithm_; 38 | } 39 | 40 | switch (idx) { 41 | case 0: { 42 | vision_algorithm_ = new AlgorithmOriginal (); 43 | selected_algorithm_ = 0; 44 | break; 45 | } 46 | default: 47 | case 1: { 48 | vision_algorithm_ = new AlgorithmGpu (); 49 | selected_algorithm_ = 1; 50 | break; 51 | } 52 | case 2: { 53 | vision_algorithm_ = new AlgorithmRandom (); 54 | selected_algorithm_ = 2; 55 | break; 56 | } 57 | } 58 | opengl_context_.doneCurrent (); 59 | 60 | emit debugLevelChanged (); 61 | emit maxDebugLevelChanged (); 62 | emit algorithmChanged (); 63 | } 64 | 65 | int Vision::MaxDebugLevel () { 66 | return vision_algorithm_->MaxDebugLevel (); 67 | } 68 | 69 | void Vision::SetDebugLevel (const int& new_level) { 70 | vision_algorithm_->SetDebugLevel (new_level); 71 | emit debugLevelChanged (); 72 | } 73 | 74 | int Vision::DebugLevel () { 75 | return vision_algorithm_->DebugLevel (); 76 | } 77 | 78 | void Vision::SetSource (const QString& source) { 79 | source_ = source; 80 | if (QResource (source).isValid ()) { 81 | SetSourceVideo (source); 82 | } else { 83 | SetSourceCamera (source); 84 | } 85 | emit sourceChanged (); 86 | } 87 | 88 | void Vision::SetSourceCamera (const QString& camera_device_id) { 89 | if (video_player_ != NULL) { 90 | delete video_player_; 91 | video_player_ = NULL; 92 | } 93 | if (camera_ != NULL) { 94 | delete camera_; 95 | camera_ = NULL; 96 | } 97 | 98 | const QList cameras = QMediaDevices::videoInputs (); 99 | const auto camera_device = 100 | std::find_if (cameras.begin (), cameras.end (), [camera_device_id] (auto element) { 101 | return element.id () == camera_device_id; 102 | }); 103 | 104 | 105 | if (camera_device != cameras.cend ()) { 106 | camera_ = new QCamera (*camera_device); 107 | } else { 108 | qCWarning (visionLog) << "Unknown camera device ID:" << camera_device_id 109 | << "Using default instead"; 110 | camera_ = new QCamera (QCameraDevice::BackFace); 111 | } 112 | 113 | QObject::connect (camera_, &QCamera::errorOccurred, 114 | [] (QCamera::Error, const QString& errorString) { 115 | qCWarning (visionLog) << errorString; 116 | }); 117 | 118 | capture_session_.setCamera (camera_); 119 | capture_session_.setVideoOutput (&video_sink_); 120 | camera_->start (); 121 | } 122 | 123 | void Vision::SetSourceVideo (const QString& resource_path) { 124 | QFile resource_file (resource_path); 125 | if (resource_file.exists ()) { 126 | auto temp_file = QTemporaryFile::createNativeFile (resource_file); 127 | QString fs_path = temp_file->fileName (); 128 | 129 | if (!fs_path.isEmpty ()) { 130 | if (camera_ != NULL) { 131 | delete camera_; 132 | camera_ = NULL; 133 | } 134 | if (video_player_ != NULL) { 135 | delete video_player_; 136 | video_player_ = NULL; 137 | } 138 | 139 | video_player_ = new QMediaPlayer (); 140 | connect (video_player_, &QMediaPlayer::mediaStatusChanged, this, 141 | &Vision::VideoPlayerStatusChanged); 142 | video_player_->setVideoOutput (&video_sink_); 143 | 144 | video_player_->setSource (QUrl::fromLocalFile (fs_path)); 145 | video_player_->play (); 146 | } 147 | } 148 | } 149 | 150 | void Vision::VideoPlayerStatusChanged (QMediaPlayer::MediaStatus new_status) { 151 | // if the video ended, restart it 152 | if (new_status == QMediaPlayer::EndOfMedia) { 153 | if (video_player_ != NULL) { 154 | video_player_->play (); 155 | } 156 | } 157 | } 158 | 159 | void Vision::SetPaused (bool paused) { 160 | if (paused) { 161 | disconnect (&video_sink_, &QVideoSink::videoFrameChanged, this, &Vision::FrameCallback); 162 | } else { 163 | connect (&video_sink_, &QVideoSink::videoFrameChanged, this, &Vision::FrameCallback); 164 | } 165 | 166 | is_paused_ = paused; 167 | emit isPausedChanged (); 168 | } 169 | 170 | void SetFocus () { 171 | ; // TODO: add focus implementation 172 | } 173 | 174 | void Vision::SetReference () { 175 | vision_mutex_.lock (); 176 | try { 177 | vision_algorithm_->SetReference (); 178 | } catch (const std::exception& e) { 179 | qCDebug (visionLog, "Error getting reference"); 180 | } 181 | vision_mutex_.unlock (); 182 | } 183 | 184 | void Vision::FrameCallback (const QVideoFrame& const_buffer) { 185 | if (vision_mutex_.tryLock ()) { 186 | try { 187 | opengl_context_.makeCurrent (&dummy_surface_); 188 | FrameData frame_data = vision_algorithm_->Execute (const_buffer); 189 | opengl_context_.doneCurrent (); 190 | emit frameProcessed (frame_data); 191 | } catch (const std::exception& e) { 192 | qCDebug (visionLog, "Error in execution"); 193 | } 194 | vision_mutex_.unlock (); 195 | } else { 196 | failed_frames_counter_++; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /source/vision/vision.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "algorithms/gpu/algorithm_gpu.hpp" 16 | #include "algorithms/original/algorithm_original.hpp" 17 | #include "algorithms/random/algorithm_random.hpp" 18 | #include "shared/movement3d/movement3d.hpp" 19 | #include "vision/algorithms/algorithm_interface.hpp" 20 | 21 | Q_DECLARE_LOGGING_CATEGORY (visionLog) 22 | 23 | class Vision : public QObject { 24 | Q_OBJECT 25 | 26 | Q_PROPERTY (QString source MEMBER source_ WRITE SetSource NOTIFY sourceChanged) 27 | Q_PROPERTY (QStringList algorithms MEMBER algorithms_ NOTIFY algorithmsChanged) 28 | Q_PROPERTY (int algorithm MEMBER selected_algorithm_ WRITE SetAlgorithm NOTIFY algorithmChanged) 29 | Q_PROPERTY (bool isPaused MEMBER is_paused_ WRITE SetPaused NOTIFY isPausedChanged) 30 | Q_PROPERTY (int debugLevel WRITE SetDebugLevel READ DebugLevel NOTIFY debugLevelChanged) 31 | Q_PROPERTY (int maxDebugLevel READ MaxDebugLevel NOTIFY maxDebugLevelChanged) 32 | 33 | public: 34 | Vision (); 35 | ~Vision (); 36 | 37 | void SetAlgorithm (int idx); 38 | int MaxDebugLevel (); 39 | void SetDebugLevel (const int& level); 40 | int DebugLevel (); 41 | void SetPaused (bool paused); 42 | void SetSource (const QString& source); 43 | void SetSourceCamera (const QString& camera_device_id); 44 | void SetSourceVideo (const QString& resource_path); 45 | 46 | public slots: 47 | void SetReference (); 48 | int GetAndClearFailedFrameCount (); 49 | void VideoPlayerStatusChanged (QMediaPlayer::MediaStatus new_status); 50 | void FrameCallback (const QVideoFrame& const_buffer); 51 | 52 | signals: 53 | void sourceChanged (); 54 | void algorithmsChanged (); 55 | void algorithmChanged (); 56 | void isPausedChanged (); 57 | void debugLevelChanged (); 58 | void maxDebugLevelChanged (); 59 | 60 | void frameProcessed (FrameData framedata); 61 | 62 | private: 63 | void InitializeOpenGL (); 64 | 65 | QOffscreenSurface dummy_surface_; 66 | QOpenGLContext opengl_context_; 67 | QVideoSink video_sink_; 68 | AlgorithmInterface* vision_algorithm_{ nullptr }; 69 | QCamera* camera_{ nullptr }; 70 | QMediaCaptureSession capture_session_; 71 | QMediaPlayer* video_player_{ nullptr }; 72 | QMutex vision_mutex_; 73 | QAtomicInteger failed_frames_counter_{ 0 }; 74 | QStringList algorithms_{ "Original (CPU)", "Original (GPU)", "Random Movement" }; 75 | int selected_algorithm_{ -1 }; 76 | bool is_paused_{ false }; 77 | QString source_{}; 78 | QString default_camera_{}; 79 | }; 80 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 17) 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | set(CMAKE_CXX_EXTENSIONS OFF) 4 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_QML_DEBUG ") 5 | add_compile_options(-Wall -Wextra -Wpedantic) 6 | 7 | add_subdirectory(movement3d) 8 | add_subdirectory(movement3d_filter) 9 | add_subdirectory(classification) 10 | add_subdirectory(augmentation) 11 | add_subdirectory(vision) 12 | -------------------------------------------------------------------------------- /tests/augmentation/AugmentationTest.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 2.12 4 | import QtQuick.Layouts 1.14 5 | import QtQml 2.12 6 | 7 | import articated.augmentation.augmentation_view 1.0 8 | import articated.tests.augmentation.mock_algorithm 1.0 9 | 10 | Window 11 | { 12 | id: root 13 | width: app_layout.childrenRect.width 14 | height: app_layout.childrenRect.height 15 | visible: true 16 | title: qsTr("AugmentationView Test") 17 | 18 | MockAlgorithm { 19 | id: mockAlgorithm 20 | rotationX: rotationX.value 21 | rotationY: rotationY.value 22 | rotationZ: rotationZ.value 23 | translationX: translationX.value 24 | translationY: translationY.value 25 | scale: scale.value 26 | } 27 | 28 | Connections { 29 | target: mockAlgorithm 30 | function onFrameReady(frame_data) { augmentation.drawFrame(frame_data) } 31 | } 32 | 33 | ColumnLayout { 34 | id: app_layout 35 | AugmentationView { 36 | id: augmentation 37 | width: 600 38 | height: width * 0.6 39 | } 40 | Rectangle { 41 | id: controls_container 42 | Layout.fillWidth: true 43 | height: childrenRect.height 44 | color: "white" 45 | RowLayout { 46 | ColumnLayout { 47 | ComboBox { 48 | model: augmentation.models 49 | onActivated: { 50 | augmentation.model = index 51 | } 52 | } 53 | GridLayout { 54 | id: rotation_controls 55 | Layout.margins: 5 56 | columns: 2 57 | Label { text: "X Rotation" } 58 | Slider { id: rotationX; from: 0; to: 360 } 59 | Label { text: "Y Rotation" } 60 | Slider { id: rotationY; from: 0; to: 360; } 61 | Label { text: "Z Rotation" } 62 | Slider { id: rotationZ; from: 0; to: 360; } 63 | } 64 | } 65 | 66 | ColumnLayout { 67 | Button { 68 | text: "Load Test Texture" 69 | onClicked: mockAlgorithm.loadTexture(); 70 | } 71 | GridLayout { 72 | id: translation_controls 73 | Layout.margins: 5 74 | columns: 2 75 | Label { text: "X Translation" } 76 | Slider { id: translationX; from: -1; to: 1 } 77 | Label { text: "Y Translation" } 78 | Slider { id: translationY; from: -1; to: 1 } 79 | Label { text: "Scale" } 80 | Slider { id: scale; from: 0; to: 1; value: 1} 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/augmentation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick OpenGL) 2 | 3 | set(CMAKE_AUTOMOC ON) 4 | 5 | qt_add_resources(augmentation_rcc 6 | augmentation_test.qrc 7 | ${CMAKE_SOURCE_DIR}/resources/3D_models/3D_models.qrc 8 | ${CMAKE_SOURCE_DIR}/resources/debug_samples/debug_samples.qrc 9 | ) 10 | 11 | add_executable(augmentation_test main.cpp mock_algorithm.cpp) 12 | target_link_libraries(augmentation_test frame_data augmentation Qt::Core Qt::Gui Qt::Quick Qt::OpenGL) 13 | -------------------------------------------------------------------------------- /tests/augmentation/augmentation_test.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | AugmentationTest.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/augmentation/main.cpp: -------------------------------------------------------------------------------- 1 | // main.cpp 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "augmentation_widget/augmentation_view.hpp" 9 | #include "mock_algorithm.hpp" 10 | #include "shared/frame_data.hpp" 11 | 12 | namespace { 13 | void configureQML () { 14 | // register Qt meta types 15 | qRegisterMetaType (); 16 | 17 | // register QML types 18 | qmlRegisterType ( 19 | "articated.augmentation.augmentation_view", 1, 0, "AugmentationView"); 20 | qmlRegisterType ( 21 | "articated.tests.augmentation.mock_algorithm", 1, 0, "MockAlgorithm"); 22 | } 23 | 24 | void configureOpengl (bool force_gles) { 25 | // set GL version 26 | QSurfaceFormat glFormat; 27 | if (QOpenGLContext::openGLModuleType () == QOpenGLContext::LibGL && !force_gles) { 28 | // on desktop, require opengl 4.1 29 | glFormat.setVersion (4, 1); 30 | } else { 31 | // on mobile, require opengles 3.0 32 | glFormat.setRenderableType (QSurfaceFormat::OpenGLES); 33 | glFormat.setVersion (3, 0); 34 | } 35 | 36 | glFormat.setProfile (QSurfaceFormat::CoreProfile); 37 | QSurfaceFormat::setDefaultFormat (glFormat); 38 | 39 | QCoreApplication::setAttribute (Qt::AA_ShareOpenGLContexts); 40 | } 41 | } // namespace 42 | 43 | int main (int argc, char* argv[]) { 44 | QCommandLineParser parser; 45 | parser.addHelpOption (); 46 | parser.setApplicationDescription ( 47 | "ARticated: an augmented reality application"); 48 | QCommandLineOption force_gles_option ("force-gles", "force usage of openGLES"); 49 | parser.addOption (force_gles_option); 50 | 51 | // parse arguments 52 | QStringList arguments; 53 | for (int i = 0; i < argc; ++i) { 54 | arguments.append (argv[i]); 55 | } 56 | parser.process (arguments); 57 | 58 | // configure opengl 59 | bool force_gles = parser.isSet (force_gles_option); 60 | configureOpengl (force_gles); 61 | 62 | // create the main app & window 63 | QGuiApplication app (argc, argv); 64 | setlocale (LC_NUMERIC, "C"); 65 | QQmlApplicationEngine engine; 66 | configureQML (); 67 | 68 | engine.load (QUrl (QStringLiteral ("qrc:/test/AugmentationTest.qml"))); 69 | 70 | return app.exec (); 71 | } 72 | -------------------------------------------------------------------------------- /tests/augmentation/mock_algorithm.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_algorithm.hpp" 2 | 3 | #include 4 | 5 | MockAlgorithm::MockAlgorithm () { 6 | opengl_context_.setShareContext (QOpenGLContext::globalShareContext ()); 7 | opengl_context_.create (); 8 | dummy_surface_.create (); 9 | } 10 | 11 | void MockAlgorithm::loadTexture () { 12 | if (!test_texture_) { 13 | opengl_context_.makeCurrent (&dummy_surface_); 14 | QImage image (":/debug_samples/textest.png"); 15 | if (image.isNull ()) { 16 | qDebug () << "image unable to load"; 17 | return; 18 | } 19 | test_texture_ = new QOpenGLTexture (image); 20 | composeFrame (); 21 | opengl_context_.doneCurrent (); 22 | } 23 | } 24 | 25 | void MockAlgorithm::setRotationX (const float value) { 26 | transform_.pitch (value); 27 | composeFrame (); 28 | } 29 | 30 | float MockAlgorithm::getRotationX () const { 31 | return transform_.pitch (); 32 | } 33 | 34 | void MockAlgorithm::setRotationY (const float value) { 35 | transform_.yaw (value); 36 | composeFrame (); 37 | } 38 | 39 | float MockAlgorithm::getRotationY () const { 40 | return transform_.yaw (); 41 | } 42 | 43 | void MockAlgorithm::setRotationZ (const float value) { 44 | transform_.roll (value); 45 | composeFrame (); 46 | } 47 | 48 | float MockAlgorithm::getRotationZ () const { 49 | return transform_.roll (); 50 | } 51 | 52 | void MockAlgorithm::setTranslationX (const float value) { 53 | transform_.translation ({ value, transform_.translation ().y }); 54 | composeFrame (); 55 | } 56 | 57 | float MockAlgorithm::getTranslationX () const { 58 | return transform_.translation ().x; 59 | } 60 | 61 | void MockAlgorithm::setTranslationY (const float value) { 62 | transform_.translation ({ transform_.translation ().x, value }); 63 | composeFrame (); 64 | } 65 | 66 | float MockAlgorithm::getTranslationY () const { 67 | return transform_.translation ().y; 68 | } 69 | 70 | void MockAlgorithm::setScale (const float value) { 71 | transform_.scale (value); 72 | composeFrame (); 73 | } 74 | 75 | float MockAlgorithm::getScale () const { 76 | return transform_.scale (); 77 | } 78 | 79 | void MockAlgorithm::composeFrame () { 80 | FrameData frame; 81 | if (test_texture_) { 82 | frame.data["background"] = static_cast (test_texture_->textureId ()); 83 | } else { 84 | frame.data["background"] = static_cast (0); 85 | } 86 | frame.data["transform"] = transform_; 87 | emit frameReady (frame); 88 | } 89 | -------------------------------------------------------------------------------- /tests/augmentation/mock_algorithm.hpp: -------------------------------------------------------------------------------- 1 | // mock_algorithm.hpp 2 | 3 | #ifndef MOCK_ALGORITHM_HPP 4 | #define MOCK_ALGORITHM_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shared/frame_data.hpp" 14 | #include "shared/movement3d/movement3d.hpp" 15 | 16 | class MockAlgorithm : public QObject { 17 | Q_OBJECT 18 | Q_PROPERTY (float rotationX READ getRotationX WRITE setRotationX) 19 | Q_PROPERTY (float rotationY READ getRotationY WRITE setRotationY) 20 | Q_PROPERTY (float rotationZ READ getRotationZ WRITE setRotationZ) 21 | Q_PROPERTY (float translationX READ getTranslationX WRITE setTranslationX) 22 | Q_PROPERTY (float translationY READ getTranslationY WRITE setTranslationY) 23 | Q_PROPERTY (float scale READ getScale WRITE setScale) 24 | public: 25 | MockAlgorithm (); 26 | ~MockAlgorithm () = default; 27 | 28 | signals: 29 | void frameReady (FrameData frame); 30 | 31 | public slots: 32 | void loadTexture (); 33 | 34 | void setRotationX (const float value); 35 | float getRotationX () const; 36 | void setRotationY (const float value); 37 | float getRotationY () const; 38 | void setRotationZ (const float value); 39 | float getRotationZ () const; 40 | void setTranslationX (const float value); 41 | float getTranslationX () const; 42 | void setTranslationY (const float value); 43 | float getTranslationY () const; 44 | void setScale (const float value); 45 | float getScale () const; 46 | 47 | private: 48 | void composeFrame (); 49 | QOpenGLContext opengl_context_; 50 | QOffscreenSurface dummy_surface_; 51 | QOpenGLTexture* test_texture_{ nullptr }; 52 | Movement3D transform_; 53 | }; 54 | 55 | #endif // MOCK_ALGORITHM_HPP 56 | -------------------------------------------------------------------------------- /tests/classification/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(classification_demo demo.cpp) 2 | target_link_libraries( 3 | classification_demo 4 | algorithms 5 | ) 6 | 7 | add_executable( 8 | classification_test 9 | test.cpp 10 | ) 11 | target_link_libraries( 12 | classification_test 13 | algorithms 14 | gtest 15 | gtest_main 16 | ) 17 | 18 | add_test( 19 | NAME classification_test 20 | COMMAND classification_test 21 | WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} 22 | ) 23 | -------------------------------------------------------------------------------- /tests/classification/demo.cpp: -------------------------------------------------------------------------------- 1 | #include "vision/algorithms/utils/classification.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | int main () { 7 | Classification test_operators; 8 | // clang-format off 9 | points_t ref = { 10 | { 1, { 0, 10 } }, { 2, { 10, 10 }}, 11 | { 3, { 0, 0 } }, { 4, { 10, 0 } } 12 | }; 13 | points_t points_scaled = { 14 | { 1, { 0, 5 } }, { 2, { 5, 5 } }, 15 | { 3, { 0, 0 } }, { 4, { 5, 0 } } 16 | }; 17 | // clang-format on 18 | std::cout << "classification demo" << std::endl; 19 | std::cout << "scale: " << test_operators.scale (ref, points_scaled, 5) << std::endl; 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/movement3d/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(movement3d_test test.cpp) 2 | target_link_libraries( 3 | movement3d_test 4 | movement3d 5 | gtest 6 | gtest_main 7 | ) 8 | 9 | add_test( 10 | NAME movement3d_test 11 | COMMAND movement3d_test 12 | WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} 13 | ) 14 | -------------------------------------------------------------------------------- /tests/movement3d/test.cpp: -------------------------------------------------------------------------------- 1 | #include "shared/movement3d/movement3d.hpp" 2 | #include 3 | 4 | TEST (movement3d_translation_delta_to_absolute_nofail, Movement3D) { 5 | Movement3D movement3d_test; 6 | // x no movement (delta is 0) 7 | // -1 0 1 8 | // | ------ | ------ | 9 | ASSERT_FLOAT_EQ (movement3d_test.translation_delta_to_absolute (0, 100, -1.0, 1.0), 0); 10 | 11 | // x -->10 10 points to right 12 | // -1 0 1 13 | // | ------ | ------ | 14 | ASSERT_FLOAT_EQ ( 15 | movement3d_test.translation_delta_to_absolute (10, 100, -1.0, 1.0), 0.2); 16 | 17 | // 10<-- x 10 points to left 18 | // -1 0 1 19 | // | ------ | ------ | 20 | ASSERT_FLOAT_EQ ( 21 | movement3d_test.translation_delta_to_absolute (-10, 100, -1.0, 1.0), -0.2); 22 | 23 | // x -->50 50 points to right 24 | // -1 0 1 25 | // | ------ | ------ | 26 | ASSERT_FLOAT_EQ ( 27 | movement3d_test.translation_delta_to_absolute (50, 100, -1.0, 1.0), 1.0); 28 | 29 | 30 | // 50<-- x 50 points to left 31 | // -1 0 1 32 | // | ------ | ------ | 33 | ASSERT_FLOAT_EQ ( 34 | movement3d_test.translation_delta_to_absolute (-50, 100, -1.0, 1.0), -1.0); 35 | } 36 | 37 | TEST (movement3d_operator_plus_no_fail, Movement3D) { 38 | Movement3D x1, x2; 39 | 40 | x1.scale (1.1); 41 | x1.translation ({ 1.2, 1.3 }); 42 | x1.yaw (1.4); 43 | x1.pitch (1.5); 44 | x1.roll (1.6); 45 | 46 | x2.scale (2.1); 47 | x2.translation ({ 2.2, 2.3 }); 48 | x2.yaw (2.4); 49 | x2.pitch (2.5); 50 | x2.roll (2.6); 51 | 52 | x1 += x2; 53 | 54 | ASSERT_FLOAT_EQ (x1.scale (), 3.2); 55 | 56 | ASSERT_FLOAT_EQ (x1.translation ().x, 3.4); 57 | ASSERT_FLOAT_EQ (x1.translation ().y, 3.6); 58 | 59 | ASSERT_FLOAT_EQ (x1.yaw (), 3.8); 60 | ASSERT_FLOAT_EQ (x1.pitch (), 4.0); 61 | ASSERT_FLOAT_EQ (x1.roll (), 4.2); 62 | } 63 | 64 | TEST (movement3d_operator_divide_equal_integer_no_fail, Movement3D) { 65 | Movement3D x1; 66 | 67 | x1.scale (1.1); 68 | x1.translation ({ 1.2, 1.3 }); 69 | x1.yaw (1.4); 70 | x1.pitch (1.5); 71 | x1.roll (1.6); 72 | 73 | x1 /= 2; 74 | ASSERT_FLOAT_EQ (x1.scale (), 0.55); 75 | 76 | ASSERT_FLOAT_EQ (x1.translation ().x, 0.6); 77 | ASSERT_FLOAT_EQ (x1.translation ().y, 0.65); 78 | 79 | ASSERT_FLOAT_EQ (x1.yaw (), 0.7); 80 | ASSERT_FLOAT_EQ (x1.pitch (), 0.75); 81 | ASSERT_FLOAT_EQ (x1.roll (), 0.8); 82 | } 83 | -------------------------------------------------------------------------------- /tests/movement3d_filter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(movement3d_filter_test test.cpp) 2 | target_link_libraries( 3 | movement3d_filter_test 4 | movement3d_filter 5 | movement3d 6 | gtest 7 | gtest_main 8 | ) 9 | add_test( 10 | NAME movement3d_filter_test 11 | COMMAND movement3d_filter_test 12 | WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} 13 | ) 14 | -------------------------------------------------------------------------------- /tests/movement3d_filter/test.cpp: -------------------------------------------------------------------------------- 1 | #include "shared/movement3d/movement3d.hpp" 2 | #include "shared/movement3d/movement3d_filter.hpp" 3 | #include 4 | 5 | TEST (movement3d_filter_average_two_nofail, movement3d_filter) { 6 | Movement3DFilter filter (2); 7 | Movement3D x1, x2, av; 8 | 9 | x1.scale (0.0); 10 | x2.scale (1.1); 11 | 12 | x1.translation ({ 0.1, 0.2 }); 13 | x2.translation ({ 1.1, 1.2 }); 14 | 15 | x1.yaw (0.3); 16 | x2.yaw (1.3); 17 | 18 | x1.pitch (0.4); 19 | x2.pitch (1.4); 20 | 21 | x1.roll (0.5); 22 | x2.roll (1.5); 23 | 24 | filter.average (x1); 25 | filter.average (x2); 26 | av = filter.average (); 27 | 28 | ASSERT_FLOAT_EQ (av.scale (), 0.55); 29 | ASSERT_FLOAT_EQ (av.translation ().x, 0.6); 30 | ASSERT_FLOAT_EQ (av.translation ().y, 0.7); 31 | ASSERT_FLOAT_EQ (av.yaw (), 0.8); 32 | ASSERT_FLOAT_EQ (av.pitch (), 0.9); 33 | ASSERT_FLOAT_EQ (av.roll (), 1.0); 34 | } 35 | 36 | TEST (movement3d_filter_average_four_nofail, movement3d_filter) { 37 | Movement3DFilter filter (4); 38 | Movement3D x1, x2, x3, x4, av; 39 | 40 | x1.scale (0.0); 41 | x2.scale (1.0); 42 | x3.scale (2.0); 43 | x4.scale (3.0); 44 | 45 | x1.translation ({ 0.1, 0.2 }); 46 | x2.translation ({ 1.1, 1.2 }); 47 | x3.translation ({ 2.1, 2.2 }); 48 | x4.translation ({ 3.1, 3.2 }); 49 | 50 | x1.yaw (0.3); 51 | x2.yaw (1.3); 52 | x3.yaw (2.3); 53 | x4.yaw (3.3); 54 | 55 | x1.pitch (0.4); 56 | x2.pitch (1.4); 57 | x3.pitch (2.4); 58 | x4.pitch (3.4); 59 | 60 | x1.roll (0.5); 61 | x2.roll (1.5); 62 | x3.roll (2.5); 63 | x4.roll (3.5); 64 | 65 | filter.average (x1); 66 | filter.average (x2); 67 | filter.average (x3); 68 | filter.average (x4); 69 | av = filter.average (); 70 | 71 | ASSERT_FLOAT_EQ (av.scale (), 1.5); 72 | ASSERT_FLOAT_EQ (av.translation ().x, 1.6); 73 | ASSERT_FLOAT_EQ (av.translation ().y, 1.7); 74 | ASSERT_FLOAT_EQ (av.yaw (), 1.8); 75 | ASSERT_FLOAT_EQ (av.pitch (), 1.9); 76 | ASSERT_FLOAT_EQ (av.roll (), 2); 77 | } 78 | -------------------------------------------------------------------------------- /tests/vision/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick OpenGL) 2 | 3 | set(CMAKE_AUTOMOC ON) 4 | 5 | qt_add_resources(vision_rcc 6 | vision_test.qrc 7 | ${CMAKE_SOURCE_DIR}/resources/3D_models/3D_models.qrc 8 | ${CMAKE_SOURCE_DIR}/resources/debug_samples/debug_samples.qrc 9 | ) 10 | 11 | add_executable(vision_test main.cpp frame_data_lister.cpp ${vision_rcc}) 12 | target_link_libraries(vision_test frame_data vision Qt::Core Qt::Gui Qt::Quick Qt::OpenGL) 13 | -------------------------------------------------------------------------------- /tests/vision/VisionTest.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 2.12 4 | import QtQuick.Layouts 1.14 5 | import QtQml 2.12 6 | 7 | import articated.vision 1.0 8 | import articated.tests.frameDataLister 1.0 9 | 10 | Window 11 | { 12 | id: root 13 | width: app_layout.childrenRect.width 14 | height: app_layout.childrenRect.height 15 | visible: true 16 | title: qsTr("Vision Test") 17 | 18 | Vision { 19 | id: vision 20 | algorithm: algorithmSelectionDropdown.currentIndex 21 | } 22 | 23 | Connections { 24 | target: vision 25 | function onFrameProcessed(frame_data) { frameDataLister.addFrameData(frame_data) } 26 | } 27 | 28 | FrameDataLister { 29 | id: frameDataLister 30 | length: 15 31 | } 32 | 33 | ColumnLayout { 34 | id: app_layout 35 | Rectangle { 36 | id: data 37 | width: 600 38 | height: width * 0.6 39 | color: "black" 40 | 41 | Column { 42 | Repeater { 43 | model: frameDataLister.data 44 | delegate: Text { 45 | text: modelData 46 | color: "white" 47 | } 48 | } 49 | } 50 | } 51 | Rectangle { 52 | id: controls_container 53 | Layout.fillWidth: true 54 | height: childrenRect.height 55 | color: "white" 56 | 57 | GridLayout { 58 | width: parent.width 59 | Layout.margins: 10 60 | columns: 2 61 | ComboBox { 62 | id: algorithmSelectionDropdown 63 | Layout.fillWidth: true 64 | model: vision.algorithms 65 | onActivated: { 66 | console.log(model[index]) 67 | } 68 | } 69 | Button { 70 | Layout.fillWidth: true 71 | text: "Set Reference" 72 | onClicked: vision.SetReference() 73 | } 74 | Button { 75 | Layout.fillWidth: true 76 | text: vision.isPaused ? "Pause" : "Unpause" 77 | onClicked: vision.isPaused = !vision.isPaused 78 | } 79 | Button { 80 | Layout.fillWidth: true 81 | text: "Load Test Video" 82 | onClicked: vision.SetInput (":/debug_samples/3_markers_good.webm") 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/vision/frame_data_lister.cpp: -------------------------------------------------------------------------------- 1 | #include "frame_data_lister.hpp" 2 | 3 | #include 4 | #include 5 | 6 | const char* type_to_console (const std::type_info& type) { 7 | std::cout.flush (); 8 | system (("echo " + std::string (type.name ()) + " | c++filt -t").c_str ()); 9 | return ""; 10 | } 11 | 12 | void FrameDataLister::addFrameData (FrameData new_data) { 13 | FrameData framecopy = FrameData (new_data); 14 | auto& data_map = new_data.data; 15 | Movement3D transform; 16 | GLuint background_texture; 17 | 18 | try { 19 | transform = std::any_cast (data_map["transform"]); 20 | background_texture = std::any_cast (data_map["background"]); 21 | } catch (const std::bad_any_cast& e) { 22 | std::cout << e.what () << std::endl; 23 | } 24 | 25 | QString data_string = 26 | QString ("tex: %0\t yaw: %1\t pitch: %2\t roll: %3") 27 | .arg (background_texture) 28 | .arg (static_cast (transform.yaw ()), 3, 10, QChar ('0')) 29 | .arg (static_cast (transform.pitch ()), 3, 10, QChar ('0')) 30 | .arg (static_cast (transform.roll ()), 3, 10, QChar ('0')); 31 | 32 | data_.append (data_string); 33 | 34 | if (data_.size () > length_) { 35 | data_.pop_front (); 36 | } 37 | 38 | emit dataChanged (); 39 | } 40 | -------------------------------------------------------------------------------- /tests/vision/frame_data_lister.hpp: -------------------------------------------------------------------------------- 1 | // frame_data_lister.hpp 2 | 3 | #ifndef FRAME_DATA_LISTER_HPP 4 | #define FRAME_DATA_LISTER_HPP 5 | 6 | #include 7 | #include 8 | 9 | #include "shared/frame_data.hpp" 10 | #include "shared/movement3d/movement3d.hpp" 11 | 12 | class FrameDataLister : public QObject { 13 | Q_OBJECT 14 | Q_PROPERTY (QStringList data MEMBER data_ NOTIFY dataChanged) 15 | Q_PROPERTY (int length MEMBER length_ NOTIFY lengthChanged) 16 | 17 | public: 18 | FrameDataLister () = default; 19 | ~FrameDataLister () = default; 20 | 21 | signals: 22 | void dataChanged (); 23 | void lengthChanged (); 24 | 25 | public slots: 26 | void addFrameData (FrameData new_data); 27 | 28 | private: 29 | QStringList data_; 30 | int length_{ 0 }; 31 | }; 32 | 33 | #endif // FRAME_DATA_LISTER_HPP 34 | -------------------------------------------------------------------------------- /tests/vision/main.cpp: -------------------------------------------------------------------------------- 1 | // main.cpp 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "frame_data_lister.hpp" 10 | #include "shared/frame_data.hpp" 11 | #include "vision/vision.hpp" 12 | 13 | namespace { 14 | void configureQML () { 15 | // register Qt meta types 16 | qRegisterMetaType (); 17 | 18 | // register QML types 19 | qmlRegisterType ("articated.vision", 1, 0, "Vision"); 20 | qmlRegisterType ("articated.tests.frameDataLister", 1, 0, "FrameDataLister"); 21 | } 22 | 23 | void configureOpengl (bool force_gles) { 24 | // set GL version 25 | QSurfaceFormat glFormat; 26 | if (QOpenGLContext::openGLModuleType () == QOpenGLContext::LibGL && !force_gles) { 27 | // on desktop, require opengl 4.1 28 | glFormat.setVersion (4, 1); 29 | } else { 30 | // on mobile, require opengles 3.0 31 | glFormat.setRenderableType (QSurfaceFormat::OpenGLES); 32 | glFormat.setVersion (3, 0); 33 | } 34 | 35 | glFormat.setProfile (QSurfaceFormat::CoreProfile); 36 | QSurfaceFormat::setDefaultFormat (glFormat); 37 | 38 | QCoreApplication::setAttribute (Qt::AA_ShareOpenGLContexts); 39 | } 40 | } // namespace 41 | 42 | int main (int argc, char* argv[]) { 43 | QCommandLineParser parser; 44 | parser.addHelpOption (); 45 | parser.setApplicationDescription ( 46 | "ARticated: an augmented reality application"); 47 | QCommandLineOption force_gles_option ("force-gles", "force usage of openGLES"); 48 | parser.addOption (force_gles_option); 49 | 50 | // parse arguments 51 | QStringList arguments; 52 | for (int i = 0; i < argc; ++i) { 53 | arguments.append (argv[i]); 54 | } 55 | parser.process (arguments); 56 | 57 | // configure opengl 58 | bool force_gles = parser.isSet (force_gles_option); 59 | configureOpengl (force_gles); 60 | 61 | // create the main app & window 62 | QGuiApplication app (argc, argv); 63 | setlocale (LC_NUMERIC, "C"); 64 | QQmlApplicationEngine engine; 65 | configureQML (); 66 | 67 | engine.load (QUrl (QStringLiteral ("qrc:/test/VisionTest.qml"))); 68 | 69 | return app.exec (); 70 | } 71 | -------------------------------------------------------------------------------- /tests/vision/vision_test.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | VisionTest.qml 4 | 5 | 6 | --------------------------------------------------------------------------------