// // ZoneMinder Benchmark, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include #include #include #include #include "zm_config.h" #include "zm_image.h" #include "zm_monitor.h" #include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" // // This allows you to feed in a set of columns and timing rows, and print it // out in a nice-looking table. // class TimingsTable { public: TimingsTable(const std::vector &inColumns) : columns(inColumns) {} // // Add a row to the end of the table. // // Args: // label: The name of the row (printed in the first column). // timings: The values for all the other columns in this row. void AddRow(const std::string &label, const std::vector &timings) { assert(timings.size() == columns.size()); Row row; row.label = label; row.timings = timings; rows.push_back(row); } // // Print out the table. // // Args: // columnPad: # characters between table columns // void Print(const int columnPad = 5) { // Figure out column widths. std::vector widths(columns.size() + 1); // The first width is the max of the row labels. auto result = std::max_element(rows.begin(), rows.end(), [](const Row &a, const Row &b) -> bool { return a.label.length() < b.label.length(); }); widths[0] = result->label.length() + columnPad; // Calculate the rest of the column widths. for (size_t i=0; i < columns.size(); i++) widths[i+1] = columns[i].length() + columnPad; auto PrintColStr = [&](size_t icol, const std::string &str) { printf("%s", str.c_str()); PrintPadding(widths[icol] - str.length()); }; // Print the header. PrintColStr(0, ""); for (size_t i=0; i < columns.size(); i++) { PrintColStr(i+1, columns[i]); } printf("\n"); // Print the timings rows. for (const auto &row : rows) { PrintColStr(0, row.label); for (size_t i=0; i < row.timings.size(); i++) { char num[128]; sprintf(num, "%.2f", row.timings[i].count() / 1000.0); PrintColStr(i+1, num); } printf("\n"); } } private: void PrintPadding(int count) { std::string str(count, ' '); printf("%s", str.c_str()); } class Row { public: std::string label; std::vector timings; }; std::vector columns; std::vector rows; }; // // Generate a greyscale image that simulates a delta that can be fed to // Zone::CheckAlarms. This first creates a black image, and then it fills // a box of a certain size inside the image with random data. This is to simulate // a typical scene where most of the scene doesn't change except a specific region. // // Args: // changeBoxPercent: 0-100 value telling how large the box with random data should be. // Set to 0 to leave the whole thing black. // width: The width of the new image. // height: The height of the new image. // // Return: // An image with all pixels initialized to values in the [minVal,maxVal] range. // std::shared_ptr GenerateRandomImage( const int changeBoxPercent, const int width = 3840, const int height = 2160) { // Create the image. Image *image = new Image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); // Set it to black initially. memset((void*)image->Buffer(0, 0), 0, image->LineSize() * image->Height()); // Now randomize the pixels inside a box. const int boxWidth = (width * changeBoxPercent) / 100; const int boxHeight = (height * changeBoxPercent) / 100; const int boxX = (int)((uint64_t)rand() * (width - boxWidth) / RAND_MAX); const int boxY = (int)((uint64_t)rand() * (height - boxHeight) / RAND_MAX); for (int y=0; y < boxHeight; y++) { uint8_t *row = (uint8_t*)image->Buffer(boxX, boxY + y); for (int x=0; x < boxWidth; x++) { row[x] = (uint8_t)rand(); } } return std::shared_ptr(image); } // // This is used to help rig up Monitor benchmarks. // class TestMonitor : public Monitor { public: TestMonitor(int width, int height) { cur_zone_id = 111; this->width = width; this->height = height; // Create a dummy ref_image. std::shared_ptr tempImage = GenerateRandomImage(0, width, height); ref_image = *tempImage.get(); shared_data = &temp_shared_data; } // // Add a new zone to this monitor. // // Args: // checkMethod: This controls how this zone will actually do motion detection. // // p_filter_box: The size of the filter to use. // void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box=Vector2(5,5)) { const int p_min_pixel_threshold = 50; const int p_max_pixel_threshold = 255; const int p_min_alarm_pixels = 1000; const int p_max_alarm_pixels = 10000000; const int zone_id = cur_zone_id++; const std::string zone_label = std::string("zone_") + std::to_string(zone_id); const Zone::ZoneType zoneType = Zone::ZoneType::ACTIVE; const Polygon poly({ Vector2(0, 0), Vector2(width-1, 0), Vector2(width-1, height-1), Vector2(0, height-1)}); Zone zone( this, zone_id, zone_label.c_str(), zoneType, poly, kRGBGreen, Zone::CheckMethod::FILTERED_PIXELS, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box ); zones.push_back(zone); } void SetRefImage(const Image *image) { ref_image = *image; } private: SharedData temp_shared_data; int cur_zone_id; }; // // Run zone benchmarks on the given image. // // Args: // label: A label to be printed before the output. // // image: The image to run the tests on. // // p_filter_box: The size of the filter to use for alarm detection. // // Return: // The average time taken for each DetectMotion call. // Microseconds RunDetectMotionBenchmark( const std::string &label, std::shared_ptr image, const Vector2 &p_filter_box) { // Create a monitor to use for the benchmark. Give it 1 zone that uses // a 5x5 filter. TestMonitor testMonitor(image->Width(), image->Height()); testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box); // Generate a black image to use as the reference image. std::shared_ptr blackImage = GenerateRandomImage( 0, image->Width(), image->Height()); testMonitor.SetRefImage(blackImage.get()); Microseconds totalTimeTaken(0); // Run a series of passes over DetectMotion. const int numPasses = 10; for (int i=0; i < numPasses; i++) { printf("\r%s - pass %2d / %2d ", label.c_str(), i+1, numPasses); fflush(stdout); TimeSegmentAdder adder(totalTimeTaken); Event::StringSet zoneSet; testMonitor.DetectMotion(*image.get(), zoneSet); } printf("\n"); return totalTimeTaken / numPasses; } // // This runs a set of Monitor::DetectMotion benchmarks, one for each of the // "delta box percents" that are passed in. This adds one row to the // TimingsTable specified. // // Args: // table: The table to add timings into. // // deltaBoxPercents: Each of these defines a box size in the delta images // passed to DetectMotion (larger boxes make it slower, sometimes significantly so). // // p_filter_box: Defines the filter size used in DetectMotion. // void RunDetectMotionBenchmarks( TimingsTable &table, const std::vector &deltaBoxPercents, const Vector2 &p_filter_box) { std::vector timings; for (int percent : deltaBoxPercents) { Microseconds timing = RunDetectMotionBenchmark( std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " box, " + std::to_string(percent) + "% delta", GenerateRandomImage(percent), p_filter_box); timings.push_back(timing); } table.AddRow( std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", timings); } int main(int argc, char *argv[]) { srand(111); // Init global stuff that we need. config.font_file_location = "../fonts/default.zmfnt"; config.event_close_mode = "time"; config.cpu_extensions = 1; // Detect SSE version. HwCapsDetect(); // Setup the column titles for the TimingsTable we'll generate. // Each column represents how large the box in the image is with delta pixels. // Each row represents a different filter size. const std::vector percents = {0, 10, 50, 100}; std::vector columns(percents.size()); std::transform(percents.begin(), percents.end(), columns.begin(), [](const int percent) {return std::to_string(percent) + "% delta (ms)";}); TimingsTable table(columns); std::vector filterSizes = {Vector2(3,3), Vector2(5,5), Vector2(13,13)}; for (const auto filterSize : filterSizes) { RunDetectMotionBenchmarks(table, percents, filterSize); } table.Print(); return 0; }