/**------------------------------------------------------------------------ * All Rights Reserved * Author: Diego Macrini *-----------------------------------------------------------------------*/ #include #include "ImageProcessor.h" #include "VSCGraph.h" #include #include #include #include using namespace vpl; extern UserArguments g_userArgs; void ImageProcessor::ReadParamsFromUserArguments() { VisSysComponent::ReadParamsFromUserArguments(); g_userArgs.ReadArg(Name(), "bufferSize", "Number of " "previous frames stored. Set >= 1 for motion detection", 8u, &m_params.maxBufferSize); g_userArgs.ReadArg(Name(), "darkPixelValue", "Pixel value that is considered the begining of darkness", 50.0f, &m_params.darkPixelValue); g_userArgs.ReadArg(Name(), "darknessThreshold", "Level of darkness (1 == complete black) at which tracking is suspended", 0.7, &m_params.darknessThreshold); g_userArgs.ReadArg(Name(), "pixelDiffThreshold", "Minimum intensity difference to consider that a pixel is noisy " "(for anti-noise mode only)", 20.0, &m_params.pixelDiffThreshold); g_userArgs.ReadArg(Name(), "maxTolerableNoiseLevel", "Maximum image noise level [0,1] that does not activate noise mode " "(for anti-noise mode only)", 0.01, &m_params.maxTolerableNoiseLevel); } void ImageProcessor::Initialize(graph::node v) { VisSysComponent::Initialize(v); m_stats.Clear(); // Make sure that this component runs in all modes //SetRunningMode(ALL_RUNNING_MODES); } //! double ImageProcessor::ComputeDarknessLevel(FloatImg img) const { unsigned level = 0; for (auto it = img.begin(); it != img.end(); ++it) { if (*it <= m_params.darkPixelValue) level++; } return double(level) / img.size(); } //! double ImageProcessor::ComputeNoiseLevel(FloatImg img, FloatImg meanImg) const { unsigned similarPixelCount = 0; // Compare the mean image with the true current image for (auto it0 = meanImg.begin(), it1 = img.begin(); it0 != meanImg.end(); ++it0, ++it1) { if (fabs(*it0 - *it1) <= m_params.pixelDiffThreshold) similarPixelCount++; } return 1 - double(similarPixelCount) / meanImg.size(); } void ImageProcessor::Run() { // Delete elements from the end if list is too long if (m_imgBuffer.size() > m_params.maxBufferSize) m_imgBuffer.pop_back(); // Get the current images from the container graph const VSCGraph* pG = static_cast(ContainerGraph()); const InputImageInfo& iii = pG->GetInputImageInfo(); std::cout << "Frame number: " << iii.frameNumber << std::endl; m_imgBuffer.push_front(iii); // See if the current frame is too dark or not m_stats.darknessLevel = ComputeDarknessLevel(iii.greyFrame); // When is offtime the image might be noisy when is not too dark. So, // remove noise and enhance the info by averaging the last frames. if (!IsTooDark() && IsOfftime() && m_imgBuffer.size() >= 2) SetAntiNoiseMode(true); // Note: the anti-noise mode is set above but also can be set by other components // in previous passes, because it's a shared variable of the vision system. if (AntiNoiseMode()) { // Create a new "mean" image and keep the original current one, // so that both can be compared. FloatImg meanImg(iii.greyFrame.ni(), iii.greyFrame.nj()); meanImg.fill(0); for (auto it = m_imgBuffer.begin(); it != m_imgBuffer.end(); ++it) { auto srcIt = it->greyFrame.begin(); auto tgtIt = meanImg.begin(); for (; tgtIt != meanImg.end(); ++tgtIt, ++srcIt) *tgtIt += *srcIt; } for (auto it = meanImg.begin(); it != meanImg.end(); ++it) *it /= m_imgBuffer.size(); // Compute the noise level based on the mean image m_stats.noiseLevel = ComputeNoiseLevel(iii.greyFrame, meanImg); if (IsNoisy()) { // Recompute the darkness level m_stats.darknessLevel = ComputeDarknessLevel(meanImg); m_imgBuffer.front().greyFrame = meanImg; } else { SetAntiNoiseMode(false); } } else { // By default, the noise level is zero m_stats.noiseLevel = 0; } } /*! This function is called when the left mouse botton and the mouse pointer was on the component's view. @return zero if the even was not dealt with and 1 otherwise. */ bool ImageProcessor::OnGUIEvent(const UserEventInfo& uei) { unsigned x = (unsigned)uei.coord.x; unsigned y = (unsigned)uei.coord.y; if (x >= GetRGBImage().ni()) x = (GetRGBImage().ni() > 0) ? GetRGBImage().ni() - 1 : 0; if (y >= GetRGBImage().nj()) y = (GetRGBImage().nj() > 0) ? GetRGBImage().nj() - 1 : 0; if (uei.id == EVENT_MOUSE_PUSH) { UIBoundingBox bbox(x, x, y, y, false); // coords are not regularized GetROISequence().push_back(bbox); } else if (uei.id == EVENT_MOUSE_DRAG) { GetROISequence().back().xmax = x; GetROISequence().back().ymax = y; } return true; } void ImageProcessor::GetDisplayInfo(const DisplayInfoIn& dii, DisplayInfoOut& dio) const { if (m_imgBuffer.empty()) return; std::ostringstream oss; if (dii.outputIdx == 0) { dio.imageType = RGB_IMAGE; dio.imagePtr = ConvertToBaseImgPtr(m_imgBuffer.front().rgbFrame); if (IsTooDark()) oss << "The image is too dark."; } else if (dii.outputIdx == 1) { dio.imageType = FLOAT_IMAGE; dio.imagePtr = ConvertToBaseImgPtr(m_imgBuffer.front().greyFrame); oss << "Image noise level: " << m_stats.noiseLevel << "\n"; oss << "Darkness level: " << m_stats.darknessLevel; } else if (dii.outputIdx == 2 && !GetROISequence().empty()) { unsigned roiIdx = (unsigned)dii.params[0]; RGBImg img = m_imgBuffer.front().rgbFrame; ByteImg mask(img.ni(), img.nj()); mask.fill(0); GetROISequence().FillMask(roiIdx, mask); dio.imageType = BYTE_IMAGE; dio.imagePtr = ConvertToBaseImgPtr(mask); } if (!GetROISequence().empty()) { auto ra = GetROISequence(); for (auto it = ra.begin(); it != ra.end(); ++it) oss << Tuple(it->xmin, it->xmax, it->ymin, it->ymax) << ' '; } // Set the draggable property to fase so that the FL_DRAG event is sent to the component dio.specs.draggableContent = false; // Images should not be zoomed dio.specs.zoomableContent = false; dio.message = oss.str(); } /*! Draws the output of the component. This function is called from a "drawing" thread, which is different from the "processing" thread that calls Run(). In order to protect the shared resources between the threads, a mutex is used. */ void ImageProcessor::Draw(const DisplayInfoIn& dii) const { // Don't draw ROI when when showing masks if (dii.outputIdx == 2 || m_imgBuffer.empty()) return; const ROISequence& roiSequence = m_imgBuffer.front().roiSequence; for (auto it = roiSequence.begin(); it != roiSequence.end(); ++it) { DrawSelection(Point(it->xmin, it->ymin), Point(it->xmax, it->ymax)); } }