From efdd66306bb47190ee3a2940eecc9b62e9ed3565 Mon Sep 17 00:00:00 2001 From: Howard Huang Date: Sun, 11 Apr 2021 14:55:58 -0400 Subject: [PATCH 1/3] Adding video processing capability to Tesseract component. --- cpp/TesseractOCRTextDetection/README.md | 7 +- .../TesseractOCRTextDetection.cpp | 460 +++++++++++------- .../TesseractOCRTextDetection.h | 18 +- .../plugin-files/config/mpfOCR.ini | 6 + .../plugin-files/descriptor/descriptor.json | 6 + .../sample_tesseract_ocr_detector.cpp | 43 +- .../test/data/NOTICE | 13 + .../test/data/test-video-detection.avi | Bin 0 -> 110960 bytes .../test/test_tesseract_ocr_detection.cpp | 76 ++- 9 files changed, 429 insertions(+), 200 deletions(-) create mode 100755 cpp/TesseractOCRTextDetection/test/data/test-video-detection.avi diff --git a/cpp/TesseractOCRTextDetection/README.md b/cpp/TesseractOCRTextDetection/README.md index e27637d7..77f4409e 100755 --- a/cpp/TesseractOCRTextDetection/README.md +++ b/cpp/TesseractOCRTextDetection/README.md @@ -3,8 +3,11 @@ This repository contains source code and model data for the OpenMPF Tesseract OCR text detection component. -The component extracts text found in an image, reported as a single track -detection. PDF documents can also be processed with one track detection per +The component extracts text found in an image, video, or generic document. +Image results are reported as a single track +detection [per specified language setting](#detecting-multiple-languages). +Video results are reported as single track detections per frame and language setting. +PDF documents can also be processed with one track detection per page. The first page corresponds to the detection property `PAGE_NUM=1`. For debugging purposes, images converted from documents are stored in a temporary job directory under `plugin/TesseractOCR/tmp-[job-id]-[random tag]`. This diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp index e4ffe231..7fe36d09 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp @@ -50,6 +50,7 @@ #include #include +#include #include @@ -409,7 +410,7 @@ string random_string(size_t length) { /* * Preprocess image before running OSD and OCR. */ -void TesseractOCRTextDetection::preprocess_image(const MPFImageJob &job, cv::Mat &image_data, +void TesseractOCRTextDetection::preprocess_image(const MPFJob &job, cv::Mat &image_data, const OCR_filter_settings &ocr_fset) { if (image_data.empty()) { throw MPFDetectionException(MPF_IMAGE_READ_ERROR, @@ -463,7 +464,7 @@ void TesseractOCRTextDetection::preprocess_image(const MPFImageJob &job, cv::Mat /* * Rescales image before running OSD and OCR. */ -void TesseractOCRTextDetection::rescale_image(const MPFImageJob &job, cv::Mat &image_data, +void TesseractOCRTextDetection::rescale_image(const MPFJob &job, cv::Mat &image_data, const OCR_filter_settings &ocr_fset) { int im_width = image_data.size().width; int im_height = image_data.size().height; @@ -721,7 +722,7 @@ void TesseractOCRTextDetection::process_serial_image_runs(OCR_job_inputs &inputs } } -bool TesseractOCRTextDetection::process_ocr_text(Properties &detection_properties, const MPFImageJob &job, +bool TesseractOCRTextDetection::process_ocr_text(Properties &detection_properties, const MPFJob &job, const TesseractOCRTextDetection::OCR_output &ocr_out, const TesseractOCRTextDetection::OCR_filter_settings &ocr_fset, int page_num) { @@ -885,7 +886,7 @@ string TesseractOCRTextDetection::process_osd_lang(const string &script_type, co // Both the scaled imi image and unscaled imi_original image are also needed since vertical rotations may result in // changes to the original image rescaling. The final rescaled image will be stored in imi_scaled. bool TesseractOCRTextDetection::get_OSD_rotation(OSResults *results, cv::Mat &imi_scaled, cv::Mat &imi_original, - int &rotation, const MPFImageJob &job, OCR_filter_settings &ocr_fset) { + int &rotation, const MPFJob &job, OCR_filter_settings &ocr_fset) { switch (results->best_result.orientation_id) { case 0: @@ -932,7 +933,7 @@ bool TesseractOCRTextDetection::get_OSD_rotation(OSResults *results, cv::Mat &im return true; } -void TesseractOCRTextDetection::get_OSD(OSBestResult &best_result, cv::Mat &imi, const MPFImageJob &job, +void TesseractOCRTextDetection::get_OSD(OSBestResult &best_result, cv::Mat &imi, const MPFJob &job, OCR_filter_settings &ocr_fset, Properties &detection_properties, string &tessdata_script_dir, set &missing_languages) { @@ -1372,220 +1373,311 @@ void TesseractOCRTextDetection::check_default_languages(const OCR_filter_setting } } +vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob &job) +{ -vector TesseractOCRTextDetection::GetDetections(const MPFImageJob &job) { - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Processing \"" + job.data_uri + "\"."); - try{ - OCR_filter_settings ocr_fset; - Text_type text_type = Unknown; + int frame_interval = parameters.contains("FRAME_INTERVAL") ? parameters["FRAME_INTERVAL"].toInt() : 1; + frame_interval = DetectionComponentUtils::GetProperty(job.job_properties, "FRAME_INTERVAL", frame_interval); + frame_interval = frame_interval <= 0 ? 1 : frame_interval; + + MPFVideoCapture cap(job); + + int total_frames = cap.GetFrameCount(); + + // Make sure the start and stop frames defined for this segment are + // not beyond the end of the video. + if (job.start_frame >= total_frames) { + throw MPFDetectionException(MPF_INVALID_START_FRAME, + "Requested start_frame is greater than the number of frames in the video"); + } + + if (job.stop_frame >= total_frames) { + throw MPFDetectionException(MPF_INVALID_STOP_FRAME, + "Requested stop_frame is greater than the number of frames in the video"); + } + + // Here, we first do some sanity checking to make sure that the + // start and stop frame positions requested in the job are within + // the bounds of the video being captured. We then set the + // position of the next frame to be captured to the start frame. + int frame_index = 0; + // Try to set start frame if start_frame != 0 + if (job.start_frame > 0 && job.stop_frame < total_frames) { + cap.SetFramePosition(job.start_frame); + frame_index = job.start_frame; + } + + MPFDetectionError job_status = MPF_DETECTION_SUCCESS; + OCR_filter_settings ocr_fset; - if (job.has_feed_forward_location && job.feed_forward_location.detection_properties.count("TEXT_TYPE")) { - if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "UNSTRUCTURED") { + Text_type text_type = Unknown; + + string run_dir = GetRunDirectory(); + cv::Mat frame; + bool wasRead = false; + std::vector tracks; + + while (frame_index <= job.stop_frame) { + if (job.has_feed_forward_track && + job.feed_forward_track.frame_locations.count(frame_index) && + job.feed_forward_track.frame_locations.at(frame_index).detection_properties.count("TEXT_TYPE")) { + string job_text_type = job.feed_forward_track.frame_locations.at(frame_index).detection_properties.at("TEXT_TYPE"); + if ( job_text_type == "UNSTRUCTURED") { text_type = Unstructured; - } else if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "STRUCTURED") { + } else if (job_text_type == "STRUCTURED") { text_type = Structured; } LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + - job.feed_forward_location.detection_properties.at("TEXT_TYPE") + "\"."); + job_text_type + "\"."); } + load_settings(job, ocr_fset, text_type); - MPFDetectionError job_status = MPF_DETECTION_SUCCESS; - map>> json_kvs_regex; - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] About to run tesseract"); - vector ocr_outputs; + cap.SetFramePosition(frame_index); + wasRead = cap.Read(frame); + if (wasRead && !frame.empty()) { + check_default_languages(ocr_fset, job.job_name, run_dir, job_status); + vector locations = process_image_job(job, ocr_fset, frame, run_dir, job_status); + for (auto location: locations) { + MPFVideoTrack video_track(frame_index, frame_index); + video_track.confidence = location.confidence; + video_track.frame_locations[frame_index] = location; + cap.ReverseTransform(video_track); + tracks.push_back(video_track); + } + } + frame_index += frame_interval; + } + std::cout << "[" << job.job_name << "] Processing complete. Generated " << tracks.size() << " tracks." << std::endl; + return tracks; +} - MPFImageReader image_reader(job); - cv::Mat image_data = image_reader.GetImage(); - cv::Mat image_data_rotated; - cv::Size input_size = image_data.size(); - string run_dir = GetRunDirectory(); +vector TesseractOCRTextDetection::GetDetections(const MPFImageJob &job) { + LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Processing \"" + job.data_uri + "\"."); + try{ + MPFDetectionError job_status = MPF_DETECTION_SUCCESS; + OCR_filter_settings ocr_fset; + Text_type text_type = Unknown; + + if (job.has_feed_forward_location && job.feed_forward_location.detection_properties.count("TEXT_TYPE")) { + if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "UNSTRUCTURED") { + text_type = Unstructured; + } else if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "STRUCTURED") { + text_type = Structured; + } - check_default_languages(ocr_fset, job.job_name, run_dir, job_status); - preprocess_image(job, image_data, ocr_fset); + LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + + job.feed_forward_location.detection_properties.at("TEXT_TYPE") + "\"."); + } + load_settings(job, ocr_fset, text_type); - Properties osd_detection_properties; - string tessdata_script_dir = ""; - set missing_languages; - int xLeftUpper = 0; - int yLeftUpper = 0; - int width = input_size.width; - int height = input_size.height; - int orientation_result = 0; - vector locations; - if (ocr_fset.psm == 0 || ocr_fset.enable_osd) { + MPFImageReader image_reader(job); + cv::Mat image_data = image_reader.GetImage(); + string run_dir = GetRunDirectory(); - OSBestResult os_best_result; - get_OSD(os_best_result, image_data, job, ocr_fset, osd_detection_properties, - tessdata_script_dir, missing_languages); - // Rotate upper left coordinates based on OSD results. - if (ocr_fset.min_orientation_confidence <= os_best_result.oconfidence) { - orientation_result = os_best_result.orientation_id; - set_coordinates(xLeftUpper, yLeftUpper, width, height, input_size, os_best_result.orientation_id); - } + check_default_languages(ocr_fset, job.job_name, run_dir, job_status); + vector locations = process_image_job(job, ocr_fset, image_data, run_dir, job_status); - // When PSM is set to 0, there is no need to process any further. - if (ocr_fset.psm == 0) { - LOG4CXX_INFO(hw_logger_, - "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + - " tracks."); - osd_detection_properties["MISSING_LANGUAGE_MODELS"] = boost::algorithm::join(missing_languages, ", "); - MPFImageLocation osd_detection(xLeftUpper, yLeftUpper, width, height, -1, osd_detection_properties); - locations.push_back(osd_detection); - return locations; + for (auto &location : locations) { + image_reader.ReverseTransform(location); } - } else { - // If OSD is not run, the image won't be rescaled yet. - rescale_image(job, image_data, ocr_fset); - } - osd_detection_properties["MISSING_LANGUAGE_MODELS"] = boost::algorithm::join(missing_languages, ", "); - set remaining_languages; - string first_pass_rotation, second_pass_rotation; - double min_ocr_conf = ocr_fset.rotate_and_detect_min_confidence; - int corrected_orientation; - int corrected_X, corrected_Y, corrected_width, corrected_height; + return locations; + } + catch (...) { + Utils::LogAndReThrowException(job, hw_logger_); + } +} - if (ocr_fset.rotate_and_detect) { - cv::rotate(image_data, image_data_rotated, cv::ROTATE_180); - remaining_languages = generate_lang_set(ocr_fset.tesseract_lang); - double rotation_val = 0.0; - if (osd_detection_properties.count("ROTATION")) { - rotation_val = boost::lexical_cast(osd_detection_properties["ROTATION"]); - } - first_pass_rotation = to_string(rotation_val); - second_pass_rotation = to_string(180.0 + rotation_val); - corrected_orientation = (orientation_result + 2) % 4; - set_coordinates(corrected_X, corrected_Y, corrected_width, corrected_height, input_size, corrected_orientation); - } - // Run initial get_tesseract_detections. When autorotate is set, for any languages that fall below initial pass - // create a second round of extractions with a 180 degree rotation applied on top of the original setting. - // Second rotation only triggers if ROTATE_AND_DETECT is set. +vector TesseractOCRTextDetection::process_image_job(const MPFJob &job, + OCR_filter_settings &ocr_fset, + cv::Mat &image_data, + const std::string &run_dir, + MPFDetectionError &job_status) { - OCR_job_inputs ocr_job_inputs; - ocr_job_inputs.job_name = &job.job_name; - ocr_job_inputs.lang = &ocr_fset.tesseract_lang; - ocr_job_inputs.tessdata_script_dir = &tessdata_script_dir; - ocr_job_inputs.run_dir = &run_dir; - ocr_job_inputs.imi = &image_data; - ocr_job_inputs.ocr_fset = &ocr_fset; - ocr_job_inputs.process_pdf = false; - ocr_job_inputs.hw_logger_ = hw_logger_; - ocr_job_inputs.tess_api_map = &tess_api_map; + LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] About to run tesseract"); + vector ocr_outputs; + cv::Mat image_data_rotated; + cv::Size input_size = image_data.size(); - Image_results image_results; - image_results.job_status = job_status; - get_tesseract_detections(ocr_job_inputs, image_results); - ocr_outputs = image_results.detections_by_lang; - job_status = image_results.job_status; + preprocess_image(job, image_data, ocr_fset); - vector all_results; - - for (const OCR_output &ocr_out: ocr_outputs) { - OCR_output final_out = ocr_out; - if (ocr_fset.rotate_and_detect) { - remaining_languages.erase(ocr_out.language); - final_out.two_pass_rotation = first_pass_rotation; - final_out.two_pass_correction = false; - - // Perform second pass OCR if min threshold is disabled (negative) or first pass confidence too low. - if (min_ocr_conf <= 0 || ocr_out.confidence < min_ocr_conf) { - // Perform second pass OCR and provide best result to output. - vector ocr_outputs_rotated; - ocr_fset.tesseract_lang = ocr_out.language; - ocr_job_inputs.lang = &ocr_fset.tesseract_lang; - ocr_job_inputs.imi = &image_data_rotated; - image_results.detections_by_lang.clear(); - image_results.job_status = job_status; - - get_tesseract_detections(ocr_job_inputs, image_results); - ocr_outputs_rotated = image_results.detections_by_lang; - job_status = image_results.job_status; - - OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); - if (ocr_out_rotated.confidence > ocr_out.confidence) { - final_out = ocr_out_rotated; - final_out.two_pass_rotation = second_pass_rotation; - final_out.two_pass_correction = true; - } - } - } - all_results.push_back(final_out); - } + Properties osd_detection_properties; + string tessdata_script_dir = ""; + set missing_languages; + int xLeftUpper = 0; + int yLeftUpper = 0; + int width = input_size.width; + int height = input_size.height; + int orientation_result = 0; + vector locations; - // If two stage OCR is enabled, run the second pass of OCR on any remaining languages where the first pass failed - // to generate an output. - for (const string &rem_lang: remaining_languages) { - // Perform second pass OCR and provide best result to output. - vector ocr_outputs_rotated; - ocr_fset.tesseract_lang = rem_lang; - ocr_job_inputs.lang = &ocr_fset.tesseract_lang; - ocr_job_inputs.imi = &image_data_rotated; - image_results.detections_by_lang.clear(); - image_results.job_status = job_status; + if (ocr_fset.psm == 0 || ocr_fset.enable_osd) { - get_tesseract_detections(ocr_job_inputs, image_results); - ocr_outputs_rotated = image_results.detections_by_lang; - job_status = image_results.job_status; + OSBestResult os_best_result; + get_OSD(os_best_result, image_data, job, ocr_fset, osd_detection_properties, + tessdata_script_dir, missing_languages); - OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); - ocr_out_rotated.two_pass_rotation = second_pass_rotation; - ocr_out_rotated.two_pass_correction = true; - all_results.push_back(ocr_out_rotated); + // Rotate upper left coordinates based on OSD results. + if (ocr_fset.min_orientation_confidence <= os_best_result.oconfidence) { + orientation_result = os_best_result.orientation_id; + set_coordinates(xLeftUpper, yLeftUpper, width, height, input_size, os_best_result.orientation_id); } - // If max_text_tracks is set, filter out to return only the top specified tracks. - if (ocr_fset.max_text_tracks > 0) { - sort(all_results.begin(), all_results.end(), greater()); - all_results.resize(ocr_fset.max_text_tracks); - } - - for (const OCR_output &final_out : all_results) { - MPFImageLocation image_location(xLeftUpper, yLeftUpper, width, height, final_out.confidence); - // Copy over OSD detection results into OCR output. - image_location.detection_properties = osd_detection_properties; - - // Mark two-pass OCR final selected rotation. - if (ocr_fset.rotate_and_detect) { - image_location.detection_properties["ROTATION"] = final_out.two_pass_rotation; - if (final_out.two_pass_correction) { - image_location.detection_properties["ROTATE_AND_DETECT_PASS"] = "180"; - // Also correct top left corner designation: - image_location.x_left_upper = corrected_X; - image_location.y_left_upper = corrected_Y; - image_location.width = corrected_width; - image_location.height = corrected_height; - } else { - image_location.detection_properties["ROTATE_AND_DETECT_PASS"] = "0"; + // When PSM is set to 0, there is no need to process any further. + if (ocr_fset.psm == 0) { + LOG4CXX_INFO(hw_logger_, + "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + + " tracks."); + osd_detection_properties["MISSING_LANGUAGE_MODELS"] = boost::algorithm::join(missing_languages, ", "); + MPFImageLocation osd_detection(xLeftUpper, yLeftUpper, width, height, -1, osd_detection_properties); + locations.push_back(osd_detection); + return locations; + } + } else { + // If OSD is not run, the image won't be rescaled yet. + rescale_image(job, image_data, ocr_fset); + } + osd_detection_properties["MISSING_LANGUAGE_MODELS"] = boost::algorithm::join(missing_languages, ", "); + + set remaining_languages; + string first_pass_rotation, second_pass_rotation; + double min_ocr_conf = ocr_fset.rotate_and_detect_min_confidence; + int corrected_orientation; + int corrected_X, corrected_Y, corrected_width, corrected_height; + + if (ocr_fset.rotate_and_detect) { + cv::rotate(image_data, image_data_rotated, cv::ROTATE_180); + remaining_languages = generate_lang_set(ocr_fset.tesseract_lang); + double rotation_val = 0.0; + if (osd_detection_properties.count("ROTATION")) { + rotation_val = boost::lexical_cast(osd_detection_properties["ROTATION"]); + } + first_pass_rotation = to_string(rotation_val); + second_pass_rotation = to_string(180.0 + rotation_val); + corrected_orientation = (orientation_result + 2) % 4; + set_coordinates(corrected_X, corrected_Y, corrected_width, corrected_height, input_size, corrected_orientation); + } + + // Run initial get_tesseract_detections. When autorotate is set, for any languages that fall below initial pass + // create a second round of extractions with a 180 degree rotation applied on top of the original setting. + // Second rotation only triggers if ROTATE_AND_DETECT is set. + + OCR_job_inputs ocr_job_inputs; + ocr_job_inputs.job_name = &job.job_name; + ocr_job_inputs.lang = &ocr_fset.tesseract_lang; + ocr_job_inputs.tessdata_script_dir = &tessdata_script_dir; + ocr_job_inputs.run_dir = &run_dir; + ocr_job_inputs.imi = &image_data; + ocr_job_inputs.ocr_fset = &ocr_fset; + ocr_job_inputs.process_pdf = false; + ocr_job_inputs.hw_logger_ = hw_logger_; + ocr_job_inputs.tess_api_map = &tess_api_map; + + Image_results image_results; + image_results.job_status = job_status; + get_tesseract_detections(ocr_job_inputs, image_results); + ocr_outputs = image_results.detections_by_lang; + job_status = image_results.job_status; + + vector all_results; + + for (const OCR_output &ocr_out: ocr_outputs) { + OCR_output final_out = ocr_out; + if (ocr_fset.rotate_and_detect) { + remaining_languages.erase(ocr_out.language); + final_out.two_pass_rotation = first_pass_rotation; + final_out.two_pass_correction = false; + + // Perform second pass OCR if min threshold is disabled (negative) or first pass confidence too low. + if (min_ocr_conf <= 0 || ocr_out.confidence < min_ocr_conf) { + // Perform second pass OCR and provide best result to output. + vector ocr_outputs_rotated; + ocr_fset.tesseract_lang = ocr_out.language; + ocr_job_inputs.lang = &ocr_fset.tesseract_lang; + ocr_job_inputs.imi = &image_data_rotated; + image_results.detections_by_lang.clear(); + image_results.job_status = job_status; + + get_tesseract_detections(ocr_job_inputs, image_results); + ocr_outputs_rotated = image_results.detections_by_lang; + job_status = image_results.job_status; + + OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); + if (ocr_out_rotated.confidence > ocr_out.confidence) { + final_out = ocr_out_rotated; + final_out.two_pass_rotation = second_pass_rotation; + final_out.two_pass_correction = true; } - - } - bool process_text = process_ocr_text(image_location.detection_properties, job, final_out, - ocr_fset); - if (process_text) { - locations.push_back(image_location); } } + all_results.push_back(final_out); + } - for (auto &location : locations) { - image_reader.ReverseTransform(location); - } + // If two stage OCR is enabled, run the second pass of OCR on any remaining languages where the first pass failed + // to generate an output. + for (const string &rem_lang: remaining_languages) { + // Perform second pass OCR and provide best result to output. + vector ocr_outputs_rotated; + ocr_fset.tesseract_lang = rem_lang; + ocr_job_inputs.lang = &ocr_fset.tesseract_lang; + ocr_job_inputs.imi = &image_data_rotated; + image_results.detections_by_lang.clear(); + image_results.job_status = job_status; - LOG4CXX_INFO(hw_logger_, - "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + " tracks."); - return locations; + get_tesseract_detections(ocr_job_inputs, image_results); + ocr_outputs_rotated = image_results.detections_by_lang; + job_status = image_results.job_status; + + OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); + ocr_out_rotated.two_pass_rotation = second_pass_rotation; + ocr_out_rotated.two_pass_correction = true; + all_results.push_back(ocr_out_rotated); } - catch (...) { - Utils::LogAndReThrowException(job, hw_logger_); + + // If max_text_tracks is set, filter out to return only the top specified tracks. + if (ocr_fset.max_text_tracks > 0) { + sort(all_results.begin(), all_results.end(), greater()); + all_results.resize(ocr_fset.max_text_tracks); } -} + for (const OCR_output &final_out : all_results) { + MPFImageLocation image_location(xLeftUpper, yLeftUpper, width, height, final_out.confidence); + // Copy over OSD detection results into OCR output. + image_location.detection_properties = osd_detection_properties; + + // Mark two-pass OCR final selected rotation. + if (ocr_fset.rotate_and_detect) { + image_location.detection_properties["ROTATION"] = final_out.two_pass_rotation; + if (final_out.two_pass_correction) { + image_location.detection_properties["ROTATE_AND_DETECT_PASS"] = "180"; + // Also correct top left corner designation: + image_location.x_left_upper = corrected_X; + image_location.y_left_upper = corrected_Y; + image_location.width = corrected_width; + image_location.height = corrected_height; + } else { + image_location.detection_properties["ROTATE_AND_DETECT_PASS"] = "0"; + } + + } + bool process_text = process_ocr_text(image_location.detection_properties, job, final_out, + ocr_fset); + if (process_text) { + locations.push_back(image_location); + } + } + + LOG4CXX_INFO(hw_logger_, + "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + " tracks."); + return locations; +} void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page_inputs, PDF_page_results &page_results) { @@ -1658,7 +1750,6 @@ void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page } for (index = 0; index < page_inputs.filelist.size(); index++ ) { - MPFImageJob c_job((*page_inputs.job).job_name, "", (*page_inputs.job).job_properties, (*page_inputs.job).media_properties); // If max_text_tracks is set, filter out to return only the top specified tracks. if (page_inputs.ocr_fset.max_text_tracks > 0) { @@ -1674,7 +1765,7 @@ void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page // Copy over OSD results into OCR tracks. generic_track.detection_properties = thread_var[index].osd_track_results.detection_properties; - bool process_text = process_ocr_text(generic_track.detection_properties, c_job, ocr_out, + bool process_text = process_ocr_text(generic_track.detection_properties, *page_inputs.job, ocr_out, page_inputs.ocr_fset, index); if (process_text) { (*page_results.tracks).push_back(generic_track); @@ -1687,7 +1778,10 @@ void TesseractOCRTextDetection::process_serial_pdf_pages(PDF_page_inputs &page_i PDF_page_results &page_results) { int page_num = 0; for (const string &filename : page_inputs.filelist) { - MPFImageJob c_job((*page_inputs.job).job_name, filename, (*page_inputs.job).job_properties, (*page_inputs.job).media_properties); + MPFImageJob c_job((*page_inputs.job).job_name, + filename, + (*page_inputs.job).job_properties, + (*page_inputs.job).media_properties); MPFImageReader image_reader(c_job); cv::Mat image_data = image_reader.GetImage(); preprocess_image(c_job, image_data, page_inputs.ocr_fset); diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h index 3be9a4c6..efea22a9 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h @@ -71,6 +71,8 @@ namespace MPF { std::vector GetDetections(const MPFGenericJob &job) override; + std::vector GetDetections(const MPFVideoJob &job) override; + std::string GetDetectionType() override; bool Supports(MPFDetectionDataType data_type) override; @@ -201,7 +203,13 @@ namespace MPF { } }; - bool process_ocr_text(Properties &detection_properties, const MPFImageJob &job, const OCR_output &ocr_out, + std::vector process_image_job(const MPFJob &job, + TesseractOCRTextDetection::OCR_filter_settings &ocr_fset, + cv::Mat &image_data, + const std::string &run_dir, + MPFDetectionError &job_status); + + bool process_ocr_text(Properties &detection_properties, const MPFJob &job, const OCR_output &ocr_out, const TesseractOCRTextDetection::OCR_filter_settings &ocr_fset, int page_num = -1); @@ -222,8 +230,8 @@ namespace MPF { static void process_parallel_image_runs(OCR_job_inputs &inputs, Image_results &results); static void process_serial_image_runs(OCR_job_inputs &inputs, Image_results &results); - void preprocess_image(const MPFImageJob &job, cv::Mat &input_image, const OCR_filter_settings &ocr_fset); - void rescale_image(const MPFImageJob &job, cv::Mat &input_image, const OCR_filter_settings &ocr_fset); + void preprocess_image(const MPFJob &job, cv::Mat &input_image, const OCR_filter_settings &ocr_fset); + void rescale_image(const MPFJob &job, cv::Mat &input_image, const OCR_filter_settings &ocr_fset); static void process_tesseract_lang_model(OCR_job_inputs &input, OCR_results &result); @@ -238,13 +246,13 @@ namespace MPF { static std::string process_osd_lang(const std::string &script_type, const OCR_filter_settings &ocr_fset); - void get_OSD(OSBestResult &best_result, cv::Mat &imi, const MPFImageJob &job, + void get_OSD(OSBestResult &best_result, cv::Mat &imi, const MPFJob &job, OCR_filter_settings &ocr_fset, Properties &detection_properties, std::string &tessdata_script_dir, std::set &missing_languages); bool get_OSD_rotation(OSResults *results, cv::Mat &imi_scaled, cv::Mat &imi_original, - int &rotation, const MPFImageJob &job, OCR_filter_settings &ocr_fset); + int &rotation, const MPFJob &job, OCR_filter_settings &ocr_fset); static std::string return_valid_tessdir(const std::string &job_name, const std::string &lang_str, diff --git a/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini b/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini index 43c443aa..10d80903 100644 --- a/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini +++ b/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini @@ -247,3 +247,9 @@ FULL_REGEX_SEARCH: 1 # falls below the minimum specified threshold. OSD is then run on multiple copies of input text image to get an improved # prediction score. The results are then kept depending on the minimum OSD threshold. ENABLE_OSD_FALLBACK: 1 + +# (int) - Controls whether the component performs detection on every frame in the video segment, +# or skips some frames at a regular interval. Must be greater than or equal to 0. +# If the frame_interval is set to a value less than 1, a frame_interval of 1 will be used, +# so that detections are performed on every frame. For a frame interval N > 1, every N-1 frames will be skipped. +FRAME_INTERVAL: 1 \ No newline at end of file diff --git a/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json b/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json index 131f33c1..0566dd78 100755 --- a/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json +++ b/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json @@ -31,6 +31,12 @@ "type": "DOUBLE", "propertiesKey": "detection.confidence.threshold" }, + { + "name": "FRAME_INTERVAL", + "description": "Controls whether the component performs detection on every frame in the video segment, or skips some frames at a regular interval. Must be greater than or equal to 0. If the frame_interval is set to a value less than 1, a frame_interval of 1 will be used, so that detections are performed on every frame. For a frame interval N > 1, every N-1 frames will be skipped.", + "type": "INT", + "defaultValue": "1" + }, { "name": "MODELS_DIR_PATH", "description": "Path to models directory", diff --git a/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp b/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp index 1f32de55..1d49ceac 100755 --- a/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp +++ b/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp @@ -45,10 +45,10 @@ using std::to_string; void print_usage(char *argv[]) { std::cout << "Usage: " << argv[0] << - " <-i | -g> [--osd] [--oem TESSERACT_OEM] [TESSERACT_LANGUAGE]" << + " <-i | -v | -g> [--osd] [--oem TESSERACT_OEM] | GENERIC_URI> [TESSERACT_LANGUAGE]" << std::endl << std::endl; std::cout << "Notes: " << std::endl << std::endl; - std::cout << " -i | -g : Specifies whether to process an image (-i ) or generic document (-g )." << + std::cout << " <-i | -v | -g> : Specifies whether to process an image (-i ), video (-v ), or generic document (-g )." << std::endl << std::endl; std::cout << " --osd : When provided, runs the job with automatic orientation and script detection (OSD). " << std::endl; @@ -102,8 +102,8 @@ bool check_options(const std::string &next_option, const int &argc, char *argv[ if (next_option == "--osd") { algorithm_properties["ENABLE_OSD_AUTOMATION"] = "true"; uri_index++; - } else if (next_option == "--oem" || argc - uri_index > 2) { - std::cout << "Updating OEM MODE " << argv[uri_index + 1]; + } else if (next_option == "--oem" && argc - uri_index > 2) { + std::cout << "Updating OEM MODE " << argv[uri_index + 1] << std::endl; algorithm_properties["TESSERACT_OEM"] = argv[uri_index + 1]; uri_index += 2; } else { @@ -131,18 +131,30 @@ int main(int argc, char *argv[]) { algorithm_properties["SHARPEN"] = "1.0"; algorithm_properties["ENABLE_OSD_AUTOMATION"] = "false"; - int uri_index = 2; + int uri_index = 2, video_params = 0, start_frame = 0, end_frame = 1; + std::string next_option = std::string(argv[uri_index]); if (check_options(next_option, argc, argv, algorithm_properties, uri_index)) { next_option = std::string(argv[uri_index]); check_options(next_option, argc, argv, algorithm_properties, uri_index); } - if (argc - uri_index == 1) { + if (media_option == "-v") { + video_params = 2; + if (argc - uri_index < 3) { + print_usage(argv); + return 0; + + } + start_frame = std::stoi(argv[uri_index+1]); + end_frame = std::stoi(argv[uri_index+2]); + } + + if (argc - uri_index - video_params == 1) { uri = argv[uri_index]; - } else if (argc - uri_index == 2) { + } else if (argc - uri_index - video_params == 2) { uri = argv[uri_index]; - algorithm_properties["TESSERACT_LANGUAGE"] = argv[uri_index + 1]; + algorithm_properties["TESSERACT_LANGUAGE"] = argv[uri_index + video_params + 1]; } else { print_usage(argv); return 0; @@ -176,6 +188,21 @@ int main(int argc, char *argv[]) { print_detection_properties(locations[i].detection_properties, locations[i].confidence); } } + else if (media_option == "-v") { + // Run uri as an image data file. + std::cout << "Running job on video data uri: " << uri << std::endl; + MPFVideoJob job(job_name, uri, start_frame, end_frame, algorithm_properties, media_properties); + int count = 0; + for (auto track: im.GetDetections(job)) { + std::cout << "Track number : " << count << std::endl; + std::map locations = track.frame_locations; + std::cout << "Number of image locations: " << locations.size() << std::endl << std::endl; + for (const auto &location: locations) { + print_detection_properties(location.second.detection_properties, location.second.confidence); + } + count ++; + } + } else { print_usage(argv); } diff --git a/cpp/TesseractOCRTextDetection/test/data/NOTICE b/cpp/TesseractOCRTextDetection/test/data/NOTICE index a392aa1a..d3a35d85 100644 --- a/cpp/TesseractOCRTextDetection/test/data/NOTICE +++ b/cpp/TesseractOCRTextDetection/test/data/NOTICE @@ -82,6 +82,19 @@ Custom generated pdf for testing document text extraction. # test-backslash.png Custom generated image for testing escaped backslash tagging. +# test-video-detection.avi +Short clip of three separate image frames for testing video detection capability. +Contains public domain text from the following sources: + + https://en.wikipedia.org/wiki/Diazepam + (Japanese Translation) + Public Domain + + http://www.un.org/en/universal-declaration-human-rights/ + English text from the Universal + Declaration of Human Rights. + Public Domain + # text-demo.png Text extracted from open source project https://github.com/tesseract-ocr/tesseract. diff --git a/cpp/TesseractOCRTextDetection/test/data/test-video-detection.avi b/cpp/TesseractOCRTextDetection/test/data/test-video-detection.avi new file mode 100755 index 0000000000000000000000000000000000000000..4ec7244bfe7f2956958364a1cffcc12f6d233f4f GIT binary patch literal 110960 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 Y6kDL!9>s1bhM@QzilhJeKe>JX1M{m6%m4rY literal 0 HcmV?d00001 diff --git a/cpp/TesseractOCRTextDetection/test/test_tesseract_ocr_detection.cpp b/cpp/TesseractOCRTextDetection/test/test_tesseract_ocr_detection.cpp index 4f6bcbfe..de4c3c8a 100644 --- a/cpp/TesseractOCRTextDetection/test/test_tesseract_ocr_detection.cpp +++ b/cpp/TesseractOCRTextDetection/test/test_tesseract_ocr_detection.cpp @@ -100,6 +100,25 @@ MPFGenericJob createPDFJob(const std::string &uri, const std::map &custom = {}) { + Properties algorithm_properties; + Properties media_properties; + std::string job_name("OCR_test"); + setAlgorithmProperties(algorithm_properties, custom); + MPFVideoJob job(job_name, uri, start, end, algorithm_properties, media_properties); + return job; +} + /** * Helper function for running given image job. Checks if job results is not empty. * @@ -136,6 +155,27 @@ void runDocumentDetection(const std::string &doc_path, TesseractOCRTextDetection ASSERT_FALSE(generic_tracks.empty()); } + +/** + * Helper function for running given video job. Checks if job results is not empty. + * + * @param vid_path - Path of given video. + * @param ocr - TesseractOCRTextDetection component for running given job. + * @param video_tracks - Output vector of video detection tracks for given job. + * @param start - Video start frame. + * @param end - Video end frame. + * @param custom - Mapping of input job properties. + */ +void runVideoDetection(const std::string &vid_path, TesseractOCRTextDetection &ocr, + std::vector &video_tracks, + const int &start, const int &end, + const std::map &custom = {}) { + MPFVideoJob job = createVideoJob(vid_path, start, end, custom); + video_tracks = ocr.GetDetections(job); + ASSERT_FALSE(video_tracks.empty()); +} + + /** * Helper function for checking if running given image job will return no results. * @@ -485,6 +525,40 @@ TEST(TESSERACTOCR, CustomModelTest) { ASSERT_TRUE(ocr.Close()); } +TEST(TESSERACTOCR, VideoProcessingTest) { + + // Ensure video processing works as expected. + + TesseractOCRTextDetection ocr; + ocr.SetRunDirectory("../plugin"); + std::vector track_results; + std::vector results; + ASSERT_TRUE(ocr.Init()); + + std::map custom_properties = {{"TESSERACT_LANGUAGE", "eng"}, + {"ENABLE_OSD_AUTOMATION","TRUE"}}; + + ASSERT_NO_FATAL_FAILURE(runVideoDetection("data/test-video-detection.avi", ocr, track_results, 0, 2, custom_properties)); + + for (auto track_result: track_results) { + for (auto result: track_result.frame_locations) { + results.push_back(result.second); + } + } + + assertInImage("data/test-video-detection.avi", "Testing Text Detection", results, "TEXT", 0); + assertInImage("data/test-video-detection.avi", "eng", results, "TEXT_LANGUAGE", 0); + + assertInImage("data/test-video-detection.avi", "Japanese", results, "OSD_PRIMARY_SCRIPT", 1); + assertInImage("data/test-video-detection.avi", "Japanese", results, "MISSING_LANGUAGE_MODELS", 1); + + assertInImage("data/test-video-detection.avi", "All human beings", results, "TEXT", 2); + assertInImage("data/test-video-detection.avi", "Latin", results, "TEXT_LANGUAGE", 2); + + + ASSERT_TRUE(ocr.Close()); +} + TEST(TESSERACTOCR, ImageProcessingTest) { // Ensure contrast and unstructured image processing settings are enabled. @@ -546,8 +620,6 @@ TEST(TESSERACTOCR, ImageProcessingTest) { ASSERT_TRUE(ocr.Close()); } - - TEST(TESSERACTOCR, ModelTest) { // Ensure user can specify custom model directory locations. From b2f5402c091e90b339b94a42e48f2d65b64fe979 Mon Sep 17 00:00:00 2001 From: Jeff Robble Date: Mon, 26 Apr 2021 17:17:18 -0400 Subject: [PATCH 2/3] Addess PR issues. --- .../TesseractOCRTextDetection.cpp | 253 ++++++++---------- .../TesseractOCRTextDetection.h | 7 +- .../plugin-files/config/mpfOCR.ini | 6 - .../plugin-files/descriptor/descriptor.json | 6 - .../sample_tesseract_ocr_detector.cpp | 3 +- 5 files changed, 124 insertions(+), 151 deletions(-) diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp index 7fe36d09..a964cd6e 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include @@ -57,7 +56,9 @@ using namespace MPF; using namespace COMPONENT; using namespace std; + using log4cxx::Logger; + typedef boost::multi_index::multi_index_container, @@ -67,9 +68,9 @@ typedef boost::multi_index::multi_index_container TesseractOCRTextDetection::GetDetections(const MPFAudioJob &job) { + throw MPFDetectionException(MPF_UNSUPPORTED_DATA_TYPE); } /* @@ -454,10 +460,10 @@ void TesseractOCRTextDetection::preprocess_image(const MPFJob &job, cv::Mat &ima cv::subtract(tmp_imb, image_data, image_data); } } - catch (const std::exception& ex) { + catch (const exception& ex) { throw MPFDetectionException( MPF_OTHER_DETECTION_ERROR_TYPE, - std::string("Error during image preprocessing: ") + ex.what()); + string("Error during image preprocessing: ") + ex.what()); } } @@ -482,12 +488,12 @@ void TesseractOCRTextDetection::rescale_image(const MPFJob &job, cv::Mat &image_ if (im_height < ocr_fset.invalid_min_image_size) { throw MPFDetectionException(MPF_BAD_FRAME_SIZE, - "Invalid image height, image too short: " + std::to_string(im_height)); + "Invalid image height, image too short: " + to_string(im_height)); } if (im_width < ocr_fset.invalid_min_image_size) { throw MPFDetectionException(MPF_BAD_FRAME_SIZE, - "Invalid image width, image too narrow: " + std::to_string(im_width)); + "Invalid image width, image too narrow: " + to_string(im_width)); } int min_dim = im_width; @@ -531,7 +537,7 @@ void TesseractOCRTextDetection::rescale_image(const MPFJob &job, cv::Mat &image_ // If rescale still exceeds pixel limits, decrease further. if (ocr_fset.max_pixels > 0 && (default_rescale * im_width) * (im_height * default_rescale) > ocr_fset.max_pixels) { - default_rescale = std::sqrt((double)ocr_fset.max_pixels / (double)(im_height * im_width)); + default_rescale = sqrt((double)ocr_fset.max_pixels / (double)(im_height * im_width)); } if (min_dim * default_rescale < ocr_fset.invalid_min_image_size) { @@ -578,9 +584,9 @@ void TesseractOCRTextDetection::rescale_image(const MPFJob &job, cv::Mat &image_ if (ocr_fset.sharpen > 0) { sharpen(image_data, ocr_fset.sharpen); } - } catch (const std::exception& ex) { + } catch (const exception& ex) { throw MPFDetectionException(MPF_OTHER_DETECTION_ERROR_TYPE, - std::string("Error during image rescaling: ") + ex.what()); + string("Error during image rescaling: ") + ex.what()); } } @@ -595,7 +601,7 @@ void TesseractOCRTextDetection::process_tesseract_lang_model(OCR_job_inputs &inp bool tess_api_in_map = inputs.tess_api_map->find(tess_api_key) != inputs.tess_api_map->end(); string tessdata_dir; - std::unique_ptr tess_api_for_parallel; + unique_ptr tess_api_for_parallel; set languages_found, missing_languages; // If running OSD scripts, set tessdata_dir to the location found during OSD processing. // Otherwise, for each individual language setting, locate the appropriate tessdata directory. @@ -625,9 +631,9 @@ void TesseractOCRTextDetection::process_tesseract_lang_model(OCR_job_inputs &inp } else if (!tess_api_in_map) { inputs.tess_api_map->emplace( - std::piecewise_construct, - std::forward_as_tuple(tess_api_key), - std::forward_as_tuple(tessdata_dir, results.lang, (tesseract::OcrEngineMode) inputs.ocr_fset->oem)); + piecewise_construct, + forward_as_tuple(tess_api_key), + forward_as_tuple(tessdata_dir, results.lang, (tesseract::OcrEngineMode) inputs.ocr_fset->oem)); } } else if (!missing_languages.empty()) { LOG4CXX_WARN(inputs.hw_logger_, "[" + *inputs.job_name + "] Tesseract language models no longer found in " + @@ -641,14 +647,14 @@ void TesseractOCRTextDetection::process_tesseract_lang_model(OCR_job_inputs &inp tess_api.SetPageSegMode((tesseract::PageSegMode) inputs.ocr_fset->psm); tess_api.SetImage(*inputs.imi); - std::string text = tess_api.GetUTF8Text(); + string text = tess_api.GetUTF8Text(); results.confidence = tess_api.MeanTextConf(); // Free up recognition results and any stored image data. tess_api.Clear(); if (!text.empty()) { - results.text_result = std::move(text); + results.text_result = move(text); } else { LOG4CXX_WARN(inputs.hw_logger_, "[" + *inputs.job_name + "] OCR processing unsuccessful, no outputs."); } @@ -658,26 +664,26 @@ void TesseractOCRTextDetection::process_parallel_image_runs(OCR_job_inputs &inpu Image_results &results) { OCR_results ocr_results[inputs.ocr_lang_inputs.size()]; - std::future ocr_threads[inputs.ocr_lang_inputs.size()]; + future ocr_threads[inputs.ocr_lang_inputs.size()]; int index = 0; - std::set active_threads; + set active_threads; // Initialize a new track for each language specified. for (const string &lang: inputs.ocr_lang_inputs) { ocr_results[index].job_status = results.job_status; ocr_results[index].lang = lang; - ocr_threads[index] = std::async(launch::async, + ocr_threads[index] = async(launch::async, &process_tesseract_lang_model, - std::ref(inputs), - std::ref(ocr_results[index])); + ref(inputs), + ref(ocr_results[index])); active_threads.insert(index); index++; while (active_threads.size() >= inputs.ocr_fset->max_parallel_ocr_threads) { - std::this_thread::sleep_for(std::chrono::seconds(1)); + this_thread::sleep_for(chrono::seconds(1)); for (auto active_thread_iter = active_threads.begin(); active_thread_iter != active_threads.end(); /* Intentionally no ++ to support erasing. */) { int i = *active_thread_iter; - if (ocr_threads[i].wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { + if (ocr_threads[i].wait_for(chrono::milliseconds(0)) == future_status::ready) { // Will re-throw exception from thread. ocr_threads[i].get(); active_thread_iter = active_threads.erase(active_thread_iter); @@ -972,9 +978,9 @@ void TesseractOCRTextDetection::get_OSD(OSBestResult &best_result, cv::Mat &imi, } tess_api_map.emplace( - std::piecewise_construct, - std::forward_as_tuple(tess_api_key), - std::forward_as_tuple(tessdata_dir, "osd", (tesseract::OcrEngineMode) oem)); + piecewise_construct, + forward_as_tuple(tess_api_key), + forward_as_tuple(tessdata_dir, "osd", (tesseract::OcrEngineMode) oem)); LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] OSD model ready."); } @@ -1262,7 +1268,7 @@ TesseractOCRTextDetection::load_settings(const MPFJob &job, OCR_filter_settings ocr_fset.adaptive_thrs_pixel = DetectionComponentUtils::GetProperty(job.job_properties,"ADAPTIVE_THRS_BLOCKSIZE", default_ocr_fset.adaptive_thrs_pixel); // OCR and OSD Engine Settings. - ocr_fset.tesseract_lang = DetectionComponentUtils::GetProperty(job.job_properties,"TESSERACT_LANGUAGE", default_ocr_fset.tesseract_lang); + ocr_fset.tesseract_lang = DetectionComponentUtils::GetProperty(job.job_properties,"TESSERACT_LANGUAGE", default_ocr_fset.tesseract_lang); ocr_fset.psm = DetectionComponentUtils::GetProperty(job.job_properties,"TESSERACT_PSM", default_ocr_fset.psm); ocr_fset.oem = DetectionComponentUtils::GetProperty(job.job_properties,"TESSERACT_OEM", default_ocr_fset.oem); @@ -1283,8 +1289,8 @@ TesseractOCRTextDetection::load_settings(const MPFJob &job, OCR_filter_settings ocr_fset.max_parallel_pdf_threads = DetectionComponentUtils::GetProperty(job.job_properties, "MAX_PARALLEL_PAGE_THREADS", default_ocr_fset.max_parallel_pdf_threads); // Tessdata setup - ocr_fset.model_dir = DetectionComponentUtils::GetProperty(job.job_properties, "MODELS_DIR_PATH", default_ocr_fset.model_dir); - ocr_fset.tessdata_models_subdir = DetectionComponentUtils::GetProperty(job.job_properties, "TESSDATA_MODELS_SUBDIRECTORY", default_ocr_fset.tessdata_models_subdir); + ocr_fset.model_dir = DetectionComponentUtils::GetProperty(job.job_properties, "MODELS_DIR_PATH", default_ocr_fset.model_dir); + ocr_fset.tessdata_models_subdir = DetectionComponentUtils::GetProperty(job.job_properties, "TESSDATA_MODELS_SUBDIRECTORY", default_ocr_fset.tessdata_models_subdir); if (ocr_fset.model_dir != "") { ocr_fset.model_dir = ocr_fset.model_dir + "/" + ocr_fset.tessdata_models_subdir; } else { @@ -1343,9 +1349,7 @@ inline void set_coordinates(int &xLeftUpper, int &yLeftUpper, int &width, int &h */ void TesseractOCRTextDetection::check_default_languages(const OCR_filter_settings &ocr_fset, const string &job_name, - const string &run_dir, - MPFDetectionError &job_status) { - + const string &run_dir) { set languages_found, missing_languages; set lang_list = generate_lang_set(ocr_fset.tesseract_lang); bool tess_api_in_map = true; @@ -1373,119 +1377,101 @@ void TesseractOCRTextDetection::check_default_languages(const OCR_filter_setting } } -vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob &job) -{ - - int frame_interval = parameters.contains("FRAME_INTERVAL") ? parameters["FRAME_INTERVAL"].toInt() : 1; - frame_interval = DetectionComponentUtils::GetProperty(job.job_properties, "FRAME_INTERVAL", frame_interval); - frame_interval = frame_interval <= 0 ? 1 : frame_interval; - - MPFVideoCapture cap(job); - - int total_frames = cap.GetFrameCount(); +vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob &job) { + try { + LOG4CXX_INFO(hw_logger_, "[" + job.job_name + "] Starting job."); - // Make sure the start and stop frames defined for this segment are - // not beyond the end of the video. - if (job.start_frame >= total_frames) { - throw MPFDetectionException(MPF_INVALID_START_FRAME, - "Requested start_frame is greater than the number of frames in the video"); - } + MPFVideoCapture cap(job); - if (job.stop_frame >= total_frames) { - throw MPFDetectionException(MPF_INVALID_STOP_FRAME, - "Requested stop_frame is greater than the number of frames in the video"); - } + MPFDetectionError job_status = MPF_DETECTION_SUCCESS; + OCR_filter_settings ocr_fset; - // Here, we first do some sanity checking to make sure that the - // start and stop frame positions requested in the job are within - // the bounds of the video being captured. We then set the - // position of the next frame to be captured to the start frame. - int frame_index = 0; - // Try to set start frame if start_frame != 0 - if (job.start_frame > 0 && job.stop_frame < total_frames) { - cap.SetFramePosition(job.start_frame); - frame_index = job.start_frame; - } - - MPFDetectionError job_status = MPF_DETECTION_SUCCESS; - OCR_filter_settings ocr_fset; + string run_dir = GetRunDirectory(); + cv::Mat frame; + vector tracks; - Text_type text_type = Unknown; + int frame_index = 0; + while (cap.Read(frame)) { + Text_type text_type = Unknown; + if (job.has_feed_forward_track && + job.feed_forward_track.frame_locations.count(frame_index) && + job.feed_forward_track.frame_locations.at(frame_index).detection_properties.count("TEXT_TYPE")) { + string job_text_type = job.feed_forward_track.frame_locations.at(frame_index).detection_properties.at( + "TEXT_TYPE"); + if (job_text_type == "UNSTRUCTURED") { + text_type = Unstructured; + } else if (job_text_type == "STRUCTURED") { + text_type = Structured; + } - string run_dir = GetRunDirectory(); - cv::Mat frame; - bool wasRead = false; - std::vector tracks; - - while (frame_index <= job.stop_frame) { - if (job.has_feed_forward_track && - job.feed_forward_track.frame_locations.count(frame_index) && - job.feed_forward_track.frame_locations.at(frame_index).detection_properties.count("TEXT_TYPE")) { - string job_text_type = job.feed_forward_track.frame_locations.at(frame_index).detection_properties.at("TEXT_TYPE"); - if ( job_text_type == "UNSTRUCTURED") { - text_type = Unstructured; - } else if (job_text_type == "STRUCTURED") { - text_type = Structured; + LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + + job_text_type + "\"."); } - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + - job_text_type + "\"."); - } - - load_settings(job, ocr_fset, text_type); - + load_settings(job, ocr_fset, text_type); - cap.SetFramePosition(frame_index); - wasRead = cap.Read(frame); - if (wasRead && !frame.empty()) { - check_default_languages(ocr_fset, job.job_name, run_dir, job_status); + check_default_languages(ocr_fset, job.job_name, run_dir); vector locations = process_image_job(job, ocr_fset, frame, run_dir, job_status); for (auto location: locations) { MPFVideoTrack video_track(frame_index, frame_index); video_track.confidence = location.confidence; video_track.frame_locations[frame_index] = location; - cap.ReverseTransform(video_track); tracks.push_back(video_track); } + + frame_index++; } - frame_index += frame_interval; + + for (MPFVideoTrack &track : tracks) { + cap.ReverseTransform(track); + } + + LOG4CXX_INFO(hw_logger_, + "[" + job.job_name + "] Processing complete. Found " + to_string(tracks.size()) + " tracks."); + + return tracks; + } + catch (...) { + Utils::LogAndReThrowException(job, hw_logger_); } - std::cout << "[" << job.job_name << "] Processing complete. Generated " << tracks.size() << " tracks." << std::endl; - return tracks; } vector TesseractOCRTextDetection::GetDetections(const MPFImageJob &job) { - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Processing \"" + job.data_uri + "\"."); try{ - MPFDetectionError job_status = MPF_DETECTION_SUCCESS; - OCR_filter_settings ocr_fset; - Text_type text_type = Unknown; + LOG4CXX_INFO(hw_logger_, "[" + job.job_name + "] Starting job."); - if (job.has_feed_forward_location && job.feed_forward_location.detection_properties.count("TEXT_TYPE")) { - if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "UNSTRUCTURED") { - text_type = Unstructured; - } else if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "STRUCTURED") { - text_type = Structured; - } + MPFDetectionError job_status = MPF_DETECTION_SUCCESS; + OCR_filter_settings ocr_fset; + Text_type text_type = Unknown; - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + - job.feed_forward_location.detection_properties.at("TEXT_TYPE") + "\"."); + if (job.has_feed_forward_location && job.feed_forward_location.detection_properties.count("TEXT_TYPE")) { + if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "UNSTRUCTURED") { + text_type = Unstructured; + } else if (job.feed_forward_location.detection_properties.at("TEXT_TYPE") == "STRUCTURED") { + text_type = Structured; } - load_settings(job, ocr_fset, text_type); + + LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + + job.feed_forward_location.detection_properties.at("TEXT_TYPE") + "\"."); + } + load_settings(job, ocr_fset, text_type); - MPFImageReader image_reader(job); - cv::Mat image_data = image_reader.GetImage(); - string run_dir = GetRunDirectory(); + MPFImageReader image_reader(job); + cv::Mat image_data = image_reader.GetImage(); + string run_dir = GetRunDirectory(); - check_default_languages(ocr_fset, job.job_name, run_dir, job_status); - vector locations = process_image_job(job, ocr_fset, image_data, run_dir, job_status); + check_default_languages(ocr_fset, job.job_name, run_dir); + vector locations = process_image_job(job, ocr_fset, image_data, run_dir, job_status); - for (auto &location : locations) { - image_reader.ReverseTransform(location); - } + for (auto &location : locations) { + image_reader.ReverseTransform(location); + } + + LOG4CXX_INFO(hw_logger_, + "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + " locations."); return locations; } @@ -1498,7 +1484,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFImage vector TesseractOCRTextDetection::process_image_job(const MPFJob &job, OCR_filter_settings &ocr_fset, cv::Mat &image_data, - const std::string &run_dir, + const string &run_dir, MPFDetectionError &job_status) { LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] About to run tesseract"); @@ -1674,15 +1660,13 @@ vector TesseractOCRTextDetection::process_image_job(const MPFJ } } - LOG4CXX_INFO(hw_logger_, - "[" + job.job_name + "] Processing complete. Found " + to_string(locations.size()) + " tracks."); return locations; } void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page_inputs, PDF_page_results &page_results) { PDF_thread_variables thread_var[page_inputs.filelist.size()]; - std::future pdf_threads[page_inputs.filelist.size()]; + future pdf_threads[page_inputs.filelist.size()]; int index = 0; set active_threads; @@ -1720,18 +1704,18 @@ void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page thread_var[index].ocr_input.process_pdf = true; thread_var[index].ocr_input.hw_logger_ = hw_logger_; thread_var[index].ocr_input.tess_api_map = &tess_api_map; - pdf_threads[index] = std::async(launch::async, + pdf_threads[index] = async(launch::async, &get_tesseract_detections, - std::ref(thread_var[index].ocr_input), - std::ref(thread_var[index].page_thread_res)); + ref(thread_var[index].ocr_input), + ref(thread_var[index].page_thread_res)); active_threads.insert(index); index ++; while (active_threads.size() >= page_inputs.ocr_fset.max_parallel_pdf_threads) { - std::this_thread::sleep_for(std::chrono::seconds(1)); + this_thread::sleep_for(chrono::seconds(1)); for (auto active_thread_iter = active_threads.begin(); active_thread_iter != active_threads.end(); /* Intentionally no ++ to support erasing. */) { int i = *active_thread_iter; - if (pdf_threads[i].wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { + if (pdf_threads[i].wait_for(chrono::milliseconds(0)) == future_status::ready) { // Will re-throw exception from thread. pdf_threads[i].get(); active_thread_iter = active_threads.erase(active_thread_iter); @@ -1811,7 +1795,6 @@ void TesseractOCRTextDetection::process_serial_pdf_pages(PDF_page_inputs &page_i rescale_image(c_job, image_data, page_inputs.ocr_fset); } - OCR_job_inputs ocr_job_inputs; ocr_job_inputs.job_name = &c_job.job_name; ocr_job_inputs.lang = &page_inputs.ocr_fset.tesseract_lang; @@ -1854,12 +1837,12 @@ void TesseractOCRTextDetection::process_serial_pdf_pages(PDF_page_inputs &page_i vector TesseractOCRTextDetection::GetDetections(const MPFGenericJob &job) { try { - LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Processing \"" + job.data_uri + "\"."); + LOG4CXX_INFO(hw_logger_, "[" + job.job_name + "] Starting job."); PDF_page_inputs page_inputs; PDF_page_results page_results; - std::vector tracks; + vector tracks; page_results.tracks = &tracks; page_inputs.job = &job; @@ -1877,7 +1860,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFGeneri page_inputs.run_dir = "."; } - check_default_languages(page_inputs.ocr_fset, job.job_name, page_inputs.run_dir, page_results.job_status); + check_default_languages(page_inputs.ocr_fset, job.job_name, page_inputs.run_dir); string plugin_path = page_inputs.run_dir + "/TesseractOCRTextDetection"; TempDirectory temp_im_directory(plugin_path + "/tmp-" + job_name + "-" + random_string(20)); @@ -1925,12 +1908,12 @@ vector TesseractOCRTextDetection::GetDetections(const MPFGeneri } -TessApiWrapper::TessApiWrapper(const std::string& data_path, const std::string& language, tesseract::OcrEngineMode oem) { +TessApiWrapper::TessApiWrapper(const string& data_path, const string& language, tesseract::OcrEngineMode oem) { int rc = tess_api_.Init(data_path.c_str(), language.c_str(), oem); if (rc != 0) { throw MPFDetectionException( MPF_DETECTION_NOT_INITIALIZED, - "Failed to initialize Tesseract! Error code: " + std::to_string(rc)); + "Failed to initialize Tesseract! Error code: " + to_string(rc)); } } @@ -1943,12 +1926,12 @@ void TessApiWrapper::SetImage(const cv::Mat &image) { static_cast(image.step)); } -std::string TessApiWrapper::GetUTF8Text() { - std::unique_ptr text{tess_api_.GetUTF8Text()}; +string TessApiWrapper::GetUTF8Text() { + unique_ptr text{tess_api_.GetUTF8Text()}; if (text == nullptr) { return ""; } - return std::string(text.get()); + return string(text.get()); } int TessApiWrapper::MeanTextConf() { diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h index efea22a9..bfdff7db 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h @@ -60,7 +60,7 @@ namespace MPF { class TessApiWrapper; - class TesseractOCRTextDetection : public MPFImageDetectionComponentAdapter { + class TesseractOCRTextDetection : public MPFDetectionComponent { public: bool Init() override; @@ -73,6 +73,8 @@ namespace MPF { std::vector GetDetections(const MPFVideoJob &job) override; + std::vector GetDetections(const MPFAudioJob &job) override; + std::string GetDetectionType() override; bool Supports(MPFDetectionDataType data_type) override; @@ -273,8 +275,7 @@ namespace MPF { void check_default_languages(const OCR_filter_settings &ocr_fset, const std::string &job_name, - const std::string &run_dir, - MPFDetectionError &job_status); + const std::string &run_dir); }; // The primary reason this class exists is that tesseract::TessBaseAPI segfaults when copying or moving. diff --git a/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini b/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini index 10d80903..43c443aa 100644 --- a/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini +++ b/cpp/TesseractOCRTextDetection/plugin-files/config/mpfOCR.ini @@ -247,9 +247,3 @@ FULL_REGEX_SEARCH: 1 # falls below the minimum specified threshold. OSD is then run on multiple copies of input text image to get an improved # prediction score. The results are then kept depending on the minimum OSD threshold. ENABLE_OSD_FALLBACK: 1 - -# (int) - Controls whether the component performs detection on every frame in the video segment, -# or skips some frames at a regular interval. Must be greater than or equal to 0. -# If the frame_interval is set to a value less than 1, a frame_interval of 1 will be used, -# so that detections are performed on every frame. For a frame interval N > 1, every N-1 frames will be skipped. -FRAME_INTERVAL: 1 \ No newline at end of file diff --git a/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json b/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json index 0566dd78..131f33c1 100755 --- a/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json +++ b/cpp/TesseractOCRTextDetection/plugin-files/descriptor/descriptor.json @@ -31,12 +31,6 @@ "type": "DOUBLE", "propertiesKey": "detection.confidence.threshold" }, - { - "name": "FRAME_INTERVAL", - "description": "Controls whether the component performs detection on every frame in the video segment, or skips some frames at a regular interval. Must be greater than or equal to 0. If the frame_interval is set to a value less than 1, a frame_interval of 1 will be used, so that detections are performed on every frame. For a frame interval N > 1, every N-1 frames will be skipped.", - "type": "INT", - "defaultValue": "1" - }, { "name": "MODELS_DIR_PATH", "description": "Path to models directory", diff --git a/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp b/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp index 1d49ceac..d177bd51 100755 --- a/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp +++ b/cpp/TesseractOCRTextDetection/sample_tesseract_ocr_detector.cpp @@ -194,10 +194,11 @@ int main(int argc, char *argv[]) { MPFVideoJob job(job_name, uri, start_frame, end_frame, algorithm_properties, media_properties); int count = 0; for (auto track: im.GetDetections(job)) { - std::cout << "Track number : " << count << std::endl; + std::cout << "Track number: " << count << std::endl; std::map locations = track.frame_locations; std::cout << "Number of image locations: " << locations.size() << std::endl << std::endl; for (const auto &location: locations) { + std::cout << "Frame number: " << location.first << std::endl; print_detection_properties(location.second.detection_properties, location.second.confidence); } count ++; From 6bdb765923a8228cc3682f946b8183c9785f768b Mon Sep 17 00:00:00 2001 From: Howard Huang Date: Tue, 4 May 2021 03:41:10 -0400 Subject: [PATCH 3/3] Removing used job status variable. --- .../TesseractOCRTextDetection.cpp | 57 ++++++++----------- .../TesseractOCRTextDetection.h | 11 ++-- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp index a964cd6e..45dc838b 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.cpp @@ -619,7 +619,6 @@ void TesseractOCRTextDetection::process_tesseract_lang_model(OCR_job_inputs &inp // Fail if user specified language is missing. // This error should not occur as both OSD and user specified languages have been already checked. if (!missing_languages.empty()) { - results.job_status = MPF_COULD_NOT_OPEN_DATAFILE; throw MPFDetectionException(MPF_COULD_NOT_OPEN_DATAFILE, "Tesseract language models not found."); } @@ -670,7 +669,6 @@ void TesseractOCRTextDetection::process_parallel_image_runs(OCR_job_inputs &inpu // Initialize a new track for each language specified. for (const string &lang: inputs.ocr_lang_inputs) { - ocr_results[index].job_status = results.job_status; ocr_results[index].lang = lang; ocr_threads[index] = async(launch::async, &process_tesseract_lang_model, @@ -715,8 +713,6 @@ void TesseractOCRTextDetection::process_serial_image_runs(OCR_job_inputs &inputs Image_results &results) { for (const string &lang: inputs.ocr_lang_inputs) { OCR_results ocr_results; - - ocr_results.job_status = results.job_status; ocr_results.lang = lang; process_tesseract_lang_model(inputs, ocr_results); @@ -1234,13 +1230,11 @@ void TesseractOCRTextDetection::get_OSD(OSBestResult &best_result, cv::Mat &imi, } - void -TesseractOCRTextDetection::load_settings(const MPFJob &job, OCR_filter_settings &ocr_fset, - const Text_type &text_type) { - // Load in settings specified from job_properties and default configuration. - - // Image preprocessing +TesseractOCRTextDetection::load_image_preprocessing_settings(const MPFJob &job, + OCR_filter_settings &ocr_fset, + const Text_type &text_type) { + // Image preprocessing bool default_processing_wild = DetectionComponentUtils::GetProperty(job.job_properties, "UNSTRUCTURED_TEXT_ENABLE_PREPROCESSING", default_ocr_fset.processing_wild_text); if ((text_type == Unstructured) || (text_type == Unknown && default_processing_wild)) { @@ -1258,7 +1252,12 @@ TesseractOCRTextDetection::load_settings(const MPFJob &job, OCR_filter_settings ocr_fset.enable_otsu_thrs = DetectionComponentUtils::GetProperty(job.job_properties,"STRUCTURED_TEXT_ENABLE_OTSU_THRS", default_ocr_fset.enable_otsu_thrs); ocr_fset.sharpen = DetectionComponentUtils::GetProperty(job.job_properties,"STRUCTURED_TEXT_SHARPEN", default_ocr_fset.sharpen); } +} +void +TesseractOCRTextDetection::load_settings(const MPFJob &job, OCR_filter_settings &ocr_fset) { + + // Load in settings specified from job_properties and default configuration ocr_fset.invert = DetectionComponentUtils::GetProperty(job.job_properties,"INVERT", default_ocr_fset.invert); ocr_fset.min_height = DetectionComponentUtils::GetProperty(job.job_properties, "MIN_HEIGHT", default_ocr_fset.min_height); ocr_fset.invalid_min_image_size = DetectionComponentUtils::GetProperty(job.job_properties, "INVALID_MIN_IMAGE_SIZE", default_ocr_fset.invalid_min_image_size); @@ -1383,7 +1382,6 @@ vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob MPFVideoCapture cap(job); - MPFDetectionError job_status = MPF_DETECTION_SUCCESS; OCR_filter_settings ocr_fset; string run_dir = GetRunDirectory(); @@ -1391,6 +1389,13 @@ vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob vector tracks; int frame_index = 0; + + load_settings(job, ocr_fset); + check_default_languages(ocr_fset, job.job_name, run_dir); + + // Depending on the frame, the default language will be replaced by script detection results. + string default_lang = ocr_fset.tesseract_lang; + while (cap.Read(frame)) { Text_type text_type = Unknown; if (job.has_feed_forward_track && @@ -1408,10 +1413,8 @@ vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob job_text_type + "\"."); } - load_settings(job, ocr_fset, text_type); - - check_default_languages(ocr_fset, job.job_name, run_dir); - vector locations = process_image_job(job, ocr_fset, frame, run_dir, job_status); + load_image_preprocessing_settings(job, ocr_fset, text_type); + vector locations = process_image_job(job, ocr_fset, frame, run_dir); for (auto location: locations) { MPFVideoTrack video_track(frame_index, frame_index); video_track.confidence = location.confidence; @@ -1419,6 +1422,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob tracks.push_back(video_track); } + ocr_fset.tesseract_lang = default_lang; frame_index++; } @@ -1440,8 +1444,6 @@ vector TesseractOCRTextDetection::GetDetections(const MPFVideoJob vector TesseractOCRTextDetection::GetDetections(const MPFImageJob &job) { try{ LOG4CXX_INFO(hw_logger_, "[" + job.job_name + "] Starting job."); - - MPFDetectionError job_status = MPF_DETECTION_SUCCESS; OCR_filter_settings ocr_fset; Text_type text_type = Unknown; @@ -1455,7 +1457,8 @@ vector TesseractOCRTextDetection::GetDetections(const MPFImage LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] Identified text type: \"" + job.feed_forward_location.detection_properties.at("TEXT_TYPE") + "\"."); } - load_settings(job, ocr_fset, text_type); + load_settings(job, ocr_fset); + load_image_preprocessing_settings(job, ocr_fset, text_type); MPFImageReader image_reader(job); @@ -1464,7 +1467,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFImage check_default_languages(ocr_fset, job.job_name, run_dir); - vector locations = process_image_job(job, ocr_fset, image_data, run_dir, job_status); + vector locations = process_image_job(job, ocr_fset, image_data, run_dir); for (auto &location : locations) { image_reader.ReverseTransform(location); @@ -1484,8 +1487,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFImage vector TesseractOCRTextDetection::process_image_job(const MPFJob &job, OCR_filter_settings &ocr_fset, cv::Mat &image_data, - const string &run_dir, - MPFDetectionError &job_status) { + const string &run_dir) { LOG4CXX_DEBUG(hw_logger_, "[" + job.job_name + "] About to run tesseract"); vector ocr_outputs; @@ -1567,10 +1569,8 @@ vector TesseractOCRTextDetection::process_image_job(const MPFJ ocr_job_inputs.tess_api_map = &tess_api_map; Image_results image_results; - image_results.job_status = job_status; get_tesseract_detections(ocr_job_inputs, image_results); ocr_outputs = image_results.detections_by_lang; - job_status = image_results.job_status; vector all_results; @@ -1589,11 +1589,9 @@ vector TesseractOCRTextDetection::process_image_job(const MPFJ ocr_job_inputs.lang = &ocr_fset.tesseract_lang; ocr_job_inputs.imi = &image_data_rotated; image_results.detections_by_lang.clear(); - image_results.job_status = job_status; get_tesseract_detections(ocr_job_inputs, image_results); ocr_outputs_rotated = image_results.detections_by_lang; - job_status = image_results.job_status; OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); if (ocr_out_rotated.confidence > ocr_out.confidence) { @@ -1615,11 +1613,9 @@ vector TesseractOCRTextDetection::process_image_job(const MPFJ ocr_job_inputs.lang = &ocr_fset.tesseract_lang; ocr_job_inputs.imi = &image_data_rotated; image_results.detections_by_lang.clear(); - image_results.job_status = job_status; get_tesseract_detections(ocr_job_inputs, image_results); ocr_outputs_rotated = image_results.detections_by_lang; - job_status = image_results.job_status; OCR_output ocr_out_rotated = ocr_outputs_rotated.front(); ocr_out_rotated.two_pass_rotation = second_pass_rotation; @@ -1671,7 +1667,6 @@ void TesseractOCRTextDetection::process_parallel_pdf_pages(PDF_page_inputs &page set active_threads; for (const string &filename : page_inputs.filelist) { - thread_var[index].page_thread_res.job_status = page_results.job_status; MPFImageJob c_job((*page_inputs.job).job_name, filename, (*page_inputs.job).job_properties, @@ -1807,10 +1802,7 @@ void TesseractOCRTextDetection::process_serial_pdf_pages(PDF_page_inputs &page_i ocr_job_inputs.tess_api_map = &tess_api_map; Image_results image_results; - image_results.job_status = page_results.job_status; - get_tesseract_detections(ocr_job_inputs, image_results); - page_results.job_status = image_results.job_status; // If max_text_tracks is set, filter out to return only the top specified tracks. if (page_inputs.ocr_fset.max_text_tracks > 0) { @@ -1847,8 +1839,7 @@ vector TesseractOCRTextDetection::GetDetections(const MPFGeneri page_inputs.job = &job; load_settings(job, page_inputs.ocr_fset); - - page_results.job_status = MPF_DETECTION_SUCCESS; + load_image_preprocessing_settings(job, page_inputs.ocr_fset); vector job_names; boost::split(job_names, job.job_name, boost::is_any_of(":")); diff --git a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h index bfdff7db..33212afa 100755 --- a/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h +++ b/cpp/TesseractOCRTextDetection/TesseractOCRTextDetection.h @@ -162,13 +162,11 @@ namespace MPF { struct Image_results{ std::vector detections_by_lang; - MPFDetectionError job_status; }; struct OCR_results { std::string text_result; std::string lang; - MPFDetectionError job_status; double confidence; }; @@ -184,7 +182,6 @@ namespace MPF { struct PDF_page_results { std::set all_missing_languages; - MPFDetectionError job_status; std::vector *tracks; }; @@ -208,8 +205,7 @@ namespace MPF { std::vector process_image_job(const MPFJob &job, TesseractOCRTextDetection::OCR_filter_settings &ocr_fset, cv::Mat &image_data, - const std::string &run_dir, - MPFDetectionError &job_status); + const std::string &run_dir); bool process_ocr_text(Properties &detection_properties, const MPFJob &job, const OCR_output &ocr_out, const TesseractOCRTextDetection::OCR_filter_settings &ocr_fset, @@ -241,7 +237,10 @@ namespace MPF { void set_read_config_parameters(); - void load_settings(const MPFJob &job, OCR_filter_settings &ocr_fset, const Text_type &text_type = Unknown); + void load_settings(const MPFJob &job, OCR_filter_settings &ocr_fset); + void load_image_preprocessing_settings(const MPFJob &job, + OCR_filter_settings &ocr_fset, + const Text_type &text_type = Unknown); void sharpen(cv::Mat &image, double weight);