1793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/core/utility.hpp"
2793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/core/ocl.hpp"
3793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/video/tracking.hpp"
4793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/imgproc/imgproc.hpp"
5793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/videoio/videoio.hpp"
6793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/highgui/highgui.hpp"
7793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
8793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include <iostream>
9793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include <cctype>
10793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
11793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic cv::UMat image;
12793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic bool backprojMode = false;
13793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic bool selectObject = false;
14793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic int trackObject = 0;
15793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic bool showHist = true;
16793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic cv::Rect selection;
17793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic int vmin = 10, vmax = 256, smin = 30;
18793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
19793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic void onMouse(int event, int x, int y, int, void*)
20793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
21793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    static cv::Point origin;
22793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
23793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    if (selectObject)
24793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
25793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection.x = std::min(x, origin.x);
26793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection.y = std::min(y, origin.y);
27793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection.width = std::abs(x - origin.x);
28793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection.height = std::abs(y - origin.y);
29793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
30793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection &= cv::Rect(0, 0, image.cols, image.rows);
31793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
32793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
33793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    switch (event)
34793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
35793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    case cv::EVENT_LBUTTONDOWN:
36793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        origin = cv::Point(x, y);
37793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selection = cv::Rect(x, y, 0, 0);
38793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selectObject = true;
39793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        break;
40793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    case cv::EVENT_LBUTTONUP:
41793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        selectObject = false;
42793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (selection.width > 0 && selection.height > 0)
43793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            trackObject = -1;
44793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        break;
45793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    default:
46793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        break;
47793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
48793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
49793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
50793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic void help()
51793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
52793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    std::cout << "\nThis is a demo that shows mean-shift based tracking using Transparent API\n"
53793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "You select a color objects such as your face and it tracks it.\n"
54793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "This reads from video camera (0 by default, or the camera number the user enters\n"
55793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "Usage: \n"
56793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "   ./camshiftdemo [camera number]\n";
57793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
58793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    std::cout << "\n\nHot keys: \n"
59793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\tESC - quit the program\n"
60793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\ts - stop the tracking\n"
61793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\tb - switch to/from backprojection view\n"
62793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\th - show/hide object histogram\n"
63793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\tp - pause video\n"
64793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "\tc - use OpenCL or not\n"
65793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            "To initialize tracking, select the object with mouse\n";
66793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
67793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
68793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerint main(int argc, const char ** argv)
69793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
70793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    help();
71793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
72793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::VideoCapture cap;
73793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::Rect trackWindow;
74793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    int hsize = 16;
75793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    float hranges[2] = { 0, 180 };
76793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
77793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    const char * const keys = { "{@camera_number| 0 | camera number}" };
78793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::CommandLineParser parser(argc, argv, keys);
79793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    int camNum = parser.get<int>(0);
80793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
81793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cap.open(camNum);
82793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
83793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    if (!cap.isOpened())
84793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
85793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        help();
86793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
87793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        std::cout << "***Could not initialize capturing...***\n";
88793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        std::cout << "Current parameter's value: \n";
89793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        parser.printMessage();
90793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
91793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        return EXIT_FAILURE;
92793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
93793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
94793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::namedWindow("Histogram", cv::WINDOW_NORMAL);
95793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::namedWindow("CamShift Demo", cv::WINDOW_NORMAL);
96793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::setMouseCallback("CamShift Demo", onMouse);
97793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::createTrackbar("Vmin", "CamShift Demo", &vmin, 256);
98793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::createTrackbar("Vmax", "CamShift Demo", &vmax, 256);
99793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::createTrackbar("Smin", "CamShift Demo", &smin, 256);
100793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
101793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::Mat frame, histimg(200, 320, CV_8UC3, cv::Scalar::all(0));
102793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cv::UMat hsv, hist, hue, mask, backproj;
103793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    bool paused = false;
104793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
105793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    for ( ; ; )
106793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
107793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (!paused)
108793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
109793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cap >> frame;
110793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            if (frame.empty())
111793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                break;
112793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
113793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
114793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        frame.copyTo(image);
115793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
116793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (!paused)
117793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
118793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
119793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
120793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            if (trackObject)
121793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            {
122793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                int _vmin = vmin, _vmax = vmax;
123793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
124793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::inRange(hsv, cv::Scalar(0, smin, std::min(_vmin, _vmax)),
125793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        cv::Scalar(180, 256, std::max(_vmin, _vmax)), mask);
126793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
127793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                int fromTo[2] = { 0,0 };
128793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                hue.create(hsv.size(), hsv.depth());
129793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::mixChannels(std::vector<cv::UMat>(1, hsv), std::vector<cv::UMat>(1, hue), fromTo, 1);
130793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
131793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                if (trackObject < 0)
132793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                {
133793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::UMat roi(hue, selection), maskroi(mask, selection);
134793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::calcHist(std::vector<cv::Mat>(1, roi.getMat(cv::ACCESS_READ)), std::vector<int>(1, 0),
135793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                 maskroi, hist, std::vector<int>(1, hsize), std::vector<float>(hranges, hranges + 2));
136793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::normalize(hist, hist, 0, 255, cv::NORM_MINMAX);
137793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
138793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    trackWindow = selection;
139793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    trackObject = 1;
140793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
141793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    histimg = cv::Scalar::all(0);
142793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    int binW = histimg.cols / hsize;
143793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::Mat buf (1, hsize, CV_8UC3);
144793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    for (int i = 0; i < hsize; i++)
145793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        buf.at<cv::Vec3b>(i) = cv::Vec3b(cv::saturate_cast<uchar>(i*180./hsize), 255, 255);
146793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::cvtColor(buf, buf, cv::COLOR_HSV2BGR);
147793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
148793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    {
149793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        cv::Mat _hist = hist.getMat(cv::ACCESS_READ);
150793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        for (int i = 0; i < hsize; i++)
151793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        {
152793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                            int val = cv::saturate_cast<int>(_hist.at<float>(i)*histimg.rows/255);
153793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                            cv::rectangle(histimg, cv::Point(i*binW, histimg.rows),
154793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                       cv::Point((i+1)*binW, histimg.rows - val),
155793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                       cv::Scalar(buf.at<cv::Vec3b>(i)), -1, 8);
156793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        }
157793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    }
158793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                }
159793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
160793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::calcBackProject(std::vector<cv::UMat>(1, hue), std::vector<int>(1, 0), hist, backproj,
161793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                    std::vector<float>(hranges, hranges + 2), 1.0);
162793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::bitwise_and(backproj, mask, backproj);
163793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
164793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::RotatedRect trackBox = cv::CamShift(backproj, trackWindow,
165793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                    cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 10, 1));
166793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                if (trackWindow.area() <= 1)
167793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                {
168793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    int cols = backproj.cols, rows = backproj.rows, r = (std::min(cols, rows) + 5)/6;
169793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
170793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                       trackWindow.x + r, trackWindow.y + r) &
171793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                                  cv::Rect(0, 0, cols, rows);
172793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                }
173793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
174793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                if (backprojMode)
175793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::cvtColor(backproj, image, cv::COLOR_GRAY2BGR);
176793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
177793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                {
178793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::Mat _image = image.getMat(cv::ACCESS_RW);
179793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    cv::ellipse(_image, trackBox, cv::Scalar(0, 0, 255), 3, cv::LINE_AA);
180793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                }
181793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            }
182793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
183793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        else if (trackObject < 0)
184793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            paused = false;
185793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
186793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (selectObject && selection.width > 0 && selection.height > 0)
187793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
188793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cv::UMat roi(image, selection);
189793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cv::bitwise_not(roi, roi);
190793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
191793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
192793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        cv::imshow("CamShift Demo", image);
193793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (showHist)
194793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cv::imshow("Histogram", histimg);
195793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
196793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        char c = (char)cv::waitKey(10);
197793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if (c == 27)
198793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
199793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
200793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        switch(c)
201793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
202793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        case 'b':
203793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            backprojMode = !backprojMode;
204793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
205793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        case 't':
206793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            trackObject = 0;
207793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            histimg = cv::Scalar::all(0);
208793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
209793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        case 'h':
210793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            showHist = !showHist;
211793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            if (!showHist)
212793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::destroyWindow("Histogram");
213793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            else
214793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                cv::namedWindow("Histogram", cv::WINDOW_AUTOSIZE);
215793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
216793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        case 'p':
217793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            paused = !paused;
218793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
219793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        case 'c':
220793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cv::ocl::setUseOpenCL(!cv::ocl::useOpenCL());
221793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        default:
222793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
223793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
224793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
225793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
226793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    return EXIT_SUCCESS;
227793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
228