diff --git a/examples/contour/Resource.h b/examples/contour/Resource.h new file mode 100644 index 00000000..29170872 --- /dev/null +++ b/examples/contour/Resource.h @@ -0,0 +1,30 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ 生成的包含文件。 +// 使用者 contour.rc + +#define IDS_APP_TITLE 103 + +#define IDR_MAINFRAME 128 +#define IDD_CONTOUR_DIALOG 102 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_CONTOUR 107 +#define IDI_SMALL 108 +#define IDC_CONTOUR 109 +#define IDC_MYICON 2 +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif +// 新对象的下一组默认值 +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS + +#define _APS_NO_MFC 130 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/examples/contour/basic_form.cpp b/examples/contour/basic_form.cpp new file mode 100644 index 00000000..84c15198 --- /dev/null +++ b/examples/contour/basic_form.cpp @@ -0,0 +1,301 @@ +#include "basic_form.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opencv2/opencv.hpp" +#include "blob/blob.h" +#include "blob/BlobDetector.h" +#include "blob/BlobGroup.h" +#include "blob/BlobContour.h" +#include "i_contours_extractor.hpp" +#include "contours_extractor_factory.hpp" + + + +const std::wstring BasicForm::kClassName = L"Basic"; + +BasicForm::BasicForm() +{ +} + +BasicForm::~BasicForm() +{ +} + +std::wstring BasicForm::GetSkinFolder() +{ + return L"basic"; +} + +std::wstring BasicForm::GetSkinFile() +{ + return L"contour.xml"; +} + +std::wstring BasicForm::GetWindowClassName() const +{ + return kClassName; +} + + +LRESULT BasicForm::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + return WindowImplBase::OnNcHitTest(uMsg, wParam, lParam, bHandled); +} + +inline std::string to_byte_string(const std::wstring& input) +{ + std::wstring_convert> converter; + return converter.to_bytes(input); +} + + +static int bz2_read( struct bspatch_stream* stream, void* buffer, int length) +{ + + return 0; +} + + +std::vector* ReadFile(std::string path) { + std::vector* ret = nullptr; + std::ifstream file(path.c_str(), std::ios::in | std::ios::binary); + if (file) + { + // ���ն����Ƹ�ʽ��ȡ���� + file.seekg(0, std::ios::end); + long long fileSize = file.tellg(); + printf("size of firm: %lld\n", fileSize); + + // ����дλ���ƶ����ļ���ͷ�������ڴ棬���̼����ݴ���buffer + file.seekg(0, std::ios::beg); + char* buffer = new char[fileSize]; + file.read(buffer, sizeof(char) * fileSize); + + ret = new std::vector(buffer, buffer + sizeof(char) * fileSize); + file.close(); + } + else + { + std::cout << "Failed to open file." << std::endl; + } + return ret; +} + +double smin_contour = 200.0; +double soffset_controu = 11; +double sepsilon = 2; +double ssmooth_step = 0.001; +double sangel_threshold = 20.0; + +void BasicForm::InitWindow() { + + ui::Button* btn_open1 = dynamic_cast(FindControl(L"btn_do_open1")); + ui::Button* btn_set_config = dynamic_cast(FindControl(L"btn_set_config")); + + ui::RichEdit* min_contour = dynamic_cast(FindControl(L"min_contour")); + ui::RichEdit* offset_controu = dynamic_cast(FindControl(L"offset_controu")); + ui::RichEdit* epsilon = dynamic_cast(FindControl(L"epsilon")); + ui::RichEdit* angel_threshold = dynamic_cast(FindControl(L"angel_threshold")); + ui::RichEdit* smooth_step = dynamic_cast(FindControl(L"smooth_step")); + ui::RichEdit* file1_dir = dynamic_cast(FindControl(L"file1_dir")); + + ui::Control* img1 = dynamic_cast(FindControl(L"img1")); + ui::Control* img2 = dynamic_cast(FindControl(L"img2")); + ui::Label* cnt = dynamic_cast(FindControl(L"concnt")); + + min_contour->SetText(L"200.0"); // 最小轮廓阀值 + offset_controu->SetText(L"11"); + epsilon->SetText(L"2"); + smooth_step->SetText(L"0.001"); + angel_threshold->SetText(L"20.0"); + + btn_set_config->AttachClick([this, min_contour, offset_controu, + epsilon, smooth_step, angel_threshold, + file1_dir, img1, img2, cnt](ui::EventArgs*) { + size_t len = wcstombs(nullptr, min_contour->GetText().c_str(), 0) + 1; + char* buffer = new char[len]; + wcstombs(buffer, min_contour->GetText().c_str(), len); + std::string str(buffer); + smin_contour = atof(str.c_str()); + + len = wcstombs(nullptr, offset_controu->GetText().c_str(), 0) + 1; + buffer = new char[len]; + wcstombs(buffer, offset_controu->GetText().c_str(), len); + std::string str1(buffer); + soffset_controu = atof(str1.c_str()); + + len = wcstombs(nullptr, epsilon->GetText().c_str(), 0) + 1; + buffer = new char[len]; + wcstombs(buffer, epsilon->GetText().c_str(), len); + std::string str2(buffer); + sepsilon = atof(str2.c_str()); + + + len = wcstombs(nullptr, smooth_step->GetText().c_str(), 0) + 1; + buffer = new char[len]; + wcstombs(buffer, smooth_step->GetText().c_str(), len); + std::string str3(buffer); + ssmooth_step = atof(str3.c_str()); + + len = wcstombs(nullptr, angel_threshold->GetText().c_str(), 0) + 1; + buffer = new char[len]; + wcstombs(buffer, angel_threshold->GetText().c_str(), len); + std::string str4(buffer); + sangel_threshold = atof(str4.c_str()); + + std::cout<SetBkImage(file1_dir->GetText()); + + len = wcstombs(nullptr, file1_dir->GetText().c_str(), 0) + 1; + char* buffer1 = new char[len]; + wcstombs(buffer1, file1_dir->GetText().c_str(), len); + std::string str5(buffer1); + + cv::Mat color = cv::imread(str5.c_str(), cv::ImreadModes::IMREAD_UNCHANGED); + + if (color.channels() < 3) { + std::cout << "" << std::endl; + return false; + } + + std::vector channels; + cv::split(color, channels); + + cv::Mat alpha = channels[3]; + + auto extractor = cvpr::ContoursExtractorFactory::create(); + + extractor->init(); + + extractor->setContourOffset(soffset_controu); + + extractor->setApproxPolyEpsilon(sepsilon); + + extractor->setSmoothMethod(cvpr::SMOOTH_METHOD::BSPLINE); + + extractor->setDefectThreshold(smin_contour); + + extractor->setSmoothStep(ssmooth_step); + + std::vector> contours; + try + { + extractor->extract(alpha, contours); + + } + catch (const std::exception& e) + { + std::cout << e.what() << std::endl; + } + cv::Mat img = color.clone(); + //cv::namedWindow("camera", 0);//CV_WINDOW_NORMAL就是0 + img = img.clone(); + cv::Mat empty_mask = cv::Mat::ones(img.rows, img.cols, CV_8UC3) * 255; + cv::drawContours(img, contours, -1, cv::Scalar(0, 0, 255, 255), 3); + + cv::imwrite("d://res2222.png", img); + img2->SetBkImage(L"d://res2222.png"); + wchar_t t[200]; + wsprintf(t, L"count %d", contours[0].size()); + cnt->SetText(std::wstring(t)); + extractor->destroy(); + return true; + }); + + btn_open1->AttachClick([this, file1_dir, img1, img2, cnt](ui::EventArgs*) { + CString strFilter; + CString m_strTmpFile; + CStdioFile cfLogFile; + + strFilter = "dat file (*.*)|*.*"; + CFileDialog TmpDlg(true, 0, 0, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strFilter.GetBuffer(), + CWnd::FromHandle(this->m_hWnd)); + + if (TmpDlg.DoModal() == IDOK) + { + m_strTmpFile = TmpDlg.GetPathName(); + + file1_dir->SetText(std::wstring(m_strTmpFile)); + } + img1->SetBkImage(file1_dir->GetText()); + + size_t len = wcstombs(nullptr, file1_dir->GetText().c_str(), 0) + 1; + char* buffer = new char[len]; + wcstombs(buffer, file1_dir->GetText().c_str(), len); + std::string str2(buffer); + + cv::Mat color = cv::imread(str2.c_str(), cv::ImreadModes::IMREAD_UNCHANGED); + + if (color.channels() < 3) { + std::cout << "" << std::endl; + return false; + } + + std::vector channels; + cv::split(color, channels); + + cv::Mat alpha = channels[3]; + + auto extractor = cvpr::ContoursExtractorFactory::create(); + + extractor->init(); + + extractor->setContourOffset(soffset_controu); + + extractor->setApproxPolyEpsilon(sepsilon); + + extractor->setSmoothMethod(cvpr::SMOOTH_METHOD::BSPLINE); + + extractor->setDefectThreshold(smin_contour); + + extractor->setSmoothStep(ssmooth_step); + + std::vector> contours; + try + { + extractor->extract(alpha, contours); + + } + catch (const std::exception& e) + { + std::cout << e.what()<SetBkImage(L"d://res2222.png"); + wchar_t t[200]; + + wsprintf(t, L"cnt: %d", contours[0].size()); + cnt->SetText(L"shit"); + extractor->destroy(); + return true; + }); +} + +LRESULT BasicForm::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + PostQuitMessage(0L); + return __super::OnClose(uMsg, wParam, lParam, bHandled); +} \ No newline at end of file diff --git a/examples/contour/basic_form.h b/examples/contour/basic_form.h new file mode 100644 index 00000000..6c25580b --- /dev/null +++ b/examples/contour/basic_form.h @@ -0,0 +1,53 @@ +#pragma once + + +#include + + +#include "targetver.h" + +// C runtime header +#include +#include +#include +#include +#include + +// base header +#include "base/base.h" + +// duilib +#include "duilib/UIlib.h" + +class BasicForm : public ui::WindowImplBase +{ +public: + BasicForm(); + ~BasicForm(); + + /** + * һӿDZҪдĽӿڣӿ + * GetSkinFolder ӿҪƵĴƤԴ· + * GetSkinFile ӿҪƵĴڵ xml ļ + * GetWindowClassName ӿôΨһ + */ + virtual std::wstring GetSkinFolder() override; + virtual std::wstring GetSkinFile() override; + virtual std::wstring GetWindowClassName() const override; + virtual LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + + /** + * յ WM_CREATE ϢʱúᱻãͨһЩؼʼIJ + */ + virtual void InitWindow() override; + + /** + * յ WM_CLOSE Ϣʱúᱻ + */ + virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + + static const std::wstring kClassName; + + ui::RichEdit* mLabel1; + ui::RichEdit* mLabel2; +}; diff --git a/examples/contour/blob/BlobContour.cpp b/examples/contour/blob/BlobContour.cpp new file mode 100644 index 00000000..a5a5e0a5 --- /dev/null +++ b/examples/contour/blob/BlobContour.cpp @@ -0,0 +1,275 @@ +#include "BlobContour.h" + +BlobContour::BlobContour() +{ + m_startPoint.x = 0; + m_startPoint.y = 0; + m_area = -1; + m_perimeter = -1; + m_moments.m00 = -1; + m_parent = NULL; +} + +BlobContour::BlobContour(const cv::Point& startPoint, const cv::Size& imageRes) : m_contour(1) +{ + + m_startPoint.x = startPoint.x; + m_startPoint.y = startPoint.y; + m_area = -1; + m_perimeter = -1; + m_moments.m00 = -1; + m_parent = NULL; + //Empirical calculations + if (imageRes.width == -1 || imageRes.width*imageRes.height > 62500) { + m_contour[0].reserve(600); + } + else { + //I reserve a portion of the image's area + m_contour[0].reserve(imageRes.height*imageRes.width/4); + } +} + +//! Copy constructor +BlobContour::BlobContour(BlobContour* source) +{ + if (source != NULL) { + *this = *source; + } +} + +BlobContour::BlobContour(const BlobContour& source) +{ + m_area = source.m_area; + m_contour = source.m_contour; + m_contourPoints = source.m_contourPoints; + m_moments = source.m_moments; + m_perimeter = source.m_perimeter; + m_startPoint = source.m_startPoint; + m_parent = NULL; +} + +BlobContour::~BlobContour() +{ +} + +//! Copy operator +BlobContour& BlobContour::operator=(const BlobContour& source) +{ + if (this != &source) { + m_startPoint = source.m_startPoint; + m_area = source.m_area; + m_perimeter = source.m_perimeter; + m_moments = source.m_moments; + m_contour = source.m_contour; + m_contourPoints = source.m_contourPoints; + } + m_parent = NULL; + return *this; +} + +/** +- FUNCIÓ: addChainCode +- FUNCIONALITAT: Add chain code to contour +- PARÀMETRES: + - +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATA DE CREACIÓ: 2008/05/06 +- MODIFICACIÓ: Data. Autor. Descripció. +*/ +void BlobContour::addChainCode(ChainCode chaincode) +{ + m_contour[0].push_back(chaincode); +} + +//! Clears chain code contour and points +void BlobContour::reset() +{ + m_contour.clear(); + m_contourPoints.clear(); +} + +/** +- FUNCIÓ: getPerimeter +- FUNCIONALITAT: Get perimeter from chain code. Diagonals sum sqrt(2) and horizontal and vertical codes 1 +- PARÀMETRES: + - +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATA DE CREACIÓ: 2008/04/30 +- MODIFICACIÓ: Data. Autor. Descripció. +- NOTA: Algorithm derived from "Methods to estimate area and perimeters of blob-like objects: A comparison", L.Yang +*/ +double BlobContour::getPerimeter() +{ + // is calculated? + if (m_perimeter != -1) { + return m_perimeter; + } + + if (isEmpty()) { + return 0; + } + + m_perimeter = cv::arcLength(getContourPoints(), true); + return m_perimeter; +} + +/** +- FUNCIÓ: getArea +- FUNCIONALITAT: Computes area from chain code +- PARÀMETRES: + - +- RESULTAT: + - May give negative areas for clock wise contours +- RESTRICCIONS: + - +- AUTOR: rborras +- DATA DE CREACIÓ: 2008/04/30 +- MODIFICACIÓ: Data. Autor. Descripció. +- NOTA: Algorithm derived from "Properties of contour codes", G.R. Wilson +*/ +double BlobContour::getArea() +{ + // is calculated? + if (m_area != -1) { + return m_area; + } + + if (isEmpty()) { + return 0; + } + m_area = std::fabs(contourArea(getContourPoints(), false)); + + return m_area; +} + +//! Get contour moment (p,q up to MAX_CALCULATED_MOMENTS) +double BlobContour::getMoment(int p, int q) +{ + // is a valid moment? + if (p < 0 || q < 0 || p > MAX_MOMENTS_ORDER || q > MAX_MOMENTS_ORDER) { + return -1; + } + + if (isEmpty()) { + return 0; + } + + // it is calculated? + if (m_moments.m00 == -1) { + //cvMoments(getContourPoints(), &m_moments); + m_moments = cvMoments(moments(getContourPoints(), true)); + } + + return cvGetSpatialMoment(&m_moments, p, q); + +} + +const PointList BlobContour::EMPTY_LIST = PointList(); + +//! Calculate contour points from crack codes +const PointList& BlobContour::getContourPoints() +{ + if (m_contour.size() == 0) { + return EMPTY_LIST; + } + if (m_contourPoints.size() != 0) { + return m_contourPoints[0]; + } + m_contourPoints.push_back(PointList()); + m_contourPoints[0].reserve(m_contour[0].size() + 1); + m_contourPoints[0].push_back(m_startPoint); + ChainCodeList::iterator it,en; + it = m_contour[0].begin(); + en = m_contour[0].end(); + cv::Point pt = m_contourPoints[0][m_contourPoints.size() - 1]; + for (; it != en; ++it) { + pt = chainCode2Point(pt, *it); + m_contourPoints[0].push_back(pt); + } + return m_contourPoints[0]; +} + +Contours& BlobContour::getContours() +{ + getContourPoints(); + return m_contourPoints; +} + +void BlobContour::shiftBlobContour(int x, int y) +{ + m_startPoint.x += x; + m_startPoint.y += y; + + for (unsigned int j = 0; j < m_contourPoints.size(); ++j) { + for (unsigned int i = 0; i < m_contourPoints[j].size(); ++i) { + m_contourPoints[j][i] += cv::Point(x, y); + } + } +} + +ChainCode points2ChainCode(const cv::Point& p1, const cv::Point& p2) +{ + // /* Luca Nardelli & Saverio Murgia + // Freeman Chain Code: + // 321 Values indicate the chain code used to identify next pixel location. + // 4-0 If I join 2 blobs I can't just append the 2nd blob chain codes, since they will still start + // 567 from the 1st blob start point + // */ + cv::Point diff = cv::Point(p2.x-p1.x, p2.y-p1.y); + if (diff.x == 1 && diff.y == 0) { + return 0; + } + else if (diff.x == 1 && diff.y == -1) { + return 1; + } + else if (diff.x == 0 && diff.y == -1) { + return 2; + } + else if (diff.x == -1 && diff.y == -1) { + return 3; + } + else if (diff.x == -1 && diff.y == 0) { + return 4; + } + else if (diff.x == -1 && diff.y == 1) { + return 5; + } + else if (diff.x == 0 && diff.y == 1) { + return 6; + } + else if (diff.x == 1 && diff.y == 1) { + return 7; + } + else { + return 200; + } +} + +cv::Point chainCode2Point(const cv::Point& origin, ChainCode code) { + // /* Luca Nardelli & Saverio Murgia + // Freeman Chain Code: + // 321 Values indicate the chain code used to identify next pixel location. + // 4-0 If I join 2 blobs I can't just append the 2nd blob chain codes, since they will still start + // 567 from the 1st blob start point + // */ + cv::Point pt = origin; + switch (code) { + case 0: pt.x++; break; + case 1: pt.x++; pt.y--; break; + case 2: pt.y--; break; + case 3: pt.x--; pt.y--; break; + case 4: pt.x--; break; + case 5: pt.x--; pt.y++; break; + case 6: pt.y++; break; + case 7: pt.x++; pt.y++; break; + } + return pt; +} diff --git a/examples/contour/blob/BlobContour.h b/examples/contour/blob/BlobContour.h new file mode 100644 index 00000000..1b3a694e --- /dev/null +++ b/examples/contour/blob/BlobContour.h @@ -0,0 +1,122 @@ +#ifndef BLOBCONTOUR_H_INCLUDED +#define BLOBCONTOUR_H_INCLUDED + +#include +#include "opencv2/imgproc/imgproc_c.h" +#include +#include + +//! Forward declaration in order to enable the "parent" field +class Blob; + +//! Type of chain codes +typedef unsigned char ChainCode; + +//! Type of list of chain codes +typedef std::vector ChainCodeList; + +//! In order to emulate CvSeq objects and to comply with opencv 2.0 interface +typedef std::vector ChainCodeContours; + +//! Type of list of points +typedef std::vector PointList; + +typedef std::vector Contours; + + +//! Max order of calculated moments +#define MAX_MOMENTS_ORDER 3 + +//! Blob contour class (in crack code) +class BlobContour +{ +public: + //! Constructors + BlobContour(); + + //! Size is used to empirically reserve internal vectors for contour points. + //! This can be a help for very small images, where the vector would be too large. + BlobContour(const cv::Point& startPoint, const cv::Size& imageRes = cv::Size(-1, -1)); + + //! Copy constructor + BlobContour(BlobContour* source); + + BlobContour(const BlobContour& source); + + ~BlobContour(); + + //! Assigment operator + BlobContour& operator=(const BlobContour& source); + + //! Add point to end of contour, according to chain code. + void addChainCode(ChainCode code); + + //! Return freeman chain coded contour + ChainCodeList& getChainCodeList() { + return m_contour[0]; + } + + bool isEmpty() { + return m_contour.size() == 0; + } + + //! Returns first contour + const PointList& getContourPoints(); + + //! Returns all contours (compatible with drawContours structure) + Contours& getContours(); + + void shiftBlobContour(int x, int y); + + const cv::Point& getStartPoint() const { + return m_startPoint; + } + +protected: + //! Clears chain code contour + void reset(); + + //! Computes area from contour + double getArea(); + + //! Computes perimeter from contour + double getPerimeter(); + + //! Get contour moment (p,q up to MAX_CALCULATED_MOMENTS) + double getMoment(int p, int q); + + //! Crack code list + ChainCodeContours m_contour; + +private: + friend class Blob; + friend class CompLabeler; + + //! Starting point of the contour + cv::Point m_startPoint; + + //! All points from the contour + Contours m_contourPoints; + + //! Computed area from contour + double m_area; + + //! Computed perimeter from contour + double m_perimeter; + + //! Computed moments from contour + CvMoments m_moments; + + static const PointList EMPTY_LIST; + + //! This value is actually used mainly in the detection part, for the labels. + Blob* m_parent; +}; + +ChainCode points2ChainCode(const cv::Point& p1, const cv::Point& p2); + +cv::Point chainCode2Point(const cv::Point& origin, ChainCode code); + +#endif //!BLOBCONTOUR_H_INCLUDED + + diff --git a/examples/contour/blob/BlobDetector.cpp b/examples/contour/blob/BlobDetector.cpp new file mode 100644 index 00000000..24996970 --- /dev/null +++ b/examples/contour/blob/BlobDetector.cpp @@ -0,0 +1,171 @@ +#include "BlobDetector.h" + +BlobDetector::BlobDetector() +{ + +} + +/** +- FUNCTION: Blob +- FUNCTIONALITY: Constructor from an image. Fills an object with all the blobs in + the image +- PARAMETERS: + - source: image to extract the blobs from + - mask: optional mask to apply. The blobs will be extracted where the mask is not 0. + - numThreads: number of labelling threads. +- RESULT: + - object with all the blobs in the image. +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Oct-2013. Luca Nardelli and Saverio Murgia. Changed to comply with reimplemented labelling algorithm +*/ +BlobDetector::BlobDetector(IplImage* source, IplImage* mask, int numThreads) +{ + if (mask != NULL) { + cv::Mat temp = cv::Mat::zeros(cv::Size(source->width, source->height), CV_8UC1); + cv::cvarrToMat(source).copyTo(temp, cv::cvarrToMat(mask)); + m_compLabeler.set(numThreads, temp); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } + else { + m_compLabeler.set(numThreads, cv::cvarrToMat(source)); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } +} + +/** +- FUNCTION: BlobDetector +- FUNCTIONALITY: Copy constructor +- PARAMETERS: + - source: object to copy +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobDetector::BlobDetector(const BlobDetector& source) +{ + // create the new from the past as a parameter + //m_blobs = BlobVector(source.getNumBlobs()); + m_blobGroup.m_blobs.reserve(source.m_blobGroup.getNumBlobs()); + // copy the blobs from the origin to the current one + BlobVector::const_iterator pBlobsSrc = source.m_blobGroup.m_blobs.begin(); + //BlobVector::iterator pBlobsDst = m_blobs.begin(); + while (pBlobsSrc != source.m_blobGroup.m_blobs.end()) { + // can't call the operator = since BlobVector is a + // Blob vector *. So create a new blob from the + // original blob + m_blobGroup.m_blobs.push_back(new Blob(**pBlobsSrc)); + pBlobsSrc++; + } +} + +BlobDetector& BlobDetector::operator=(const BlobDetector& source) +{ + if (this != &source) { + m_blobGroup.m_blobs.reserve(source.m_blobGroup.getNumBlobs()); + BlobVector::const_iterator pBlobsSrc = source.m_blobGroup.m_blobs.begin(); + while (pBlobsSrc != source.m_blobGroup.m_blobs.end()) { + m_blobGroup.m_blobs.push_back(new Blob(**pBlobsSrc)); + pBlobsSrc++; + } + } + return *this; +} + +BlobDetector::BlobDetector(BlobDetector&& source) noexcept +{ + std::exchange(m_blobGroup, std::move(source.m_blobGroup)); +} + +BlobDetector& BlobDetector::operator=(BlobDetector&& source) noexcept +{ + if (this != &source) { + std::exchange(m_blobGroup, std::move(source.m_blobGroup)); + } + return *this; +} + +/** +- FUNCTION: ~BlobDetector +- FUNCTIONALITY: Destructor +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobDetector::~BlobDetector() +{ + m_blobGroup.clearBlobs(); +} + +/** +- FUNCTION: BlobDetector +- FUNCTIONALITY: Constructor from an image. Fills an object with all the blobs in + the image, OPENCV 2 interface +- PARAMETERS: + - source: Mat to extract the blobs from, CV_8UC1 + - mask: optional mask to apply. The blobs will be extracted where the mask is + not 0. All the neighbouring blobs where the mask is 0 will be external blobs + - numThreads: number of labelling threads. +- RESULT: + - object with all the blobs in the image. +- RESTRICTIONS: +- AUTHOR: Saverio Murgia & Luca Nardelli +- CREATION DATE: 06-04-2013. +- MODIFICATION: Date. Author. Description. +*/ +BlobDetector::BlobDetector(cv::Mat& source, const cv::Mat& mask, int numThreads) { + if (mask.data) { + cv::Mat temp = cv::Mat::zeros(source.size(), source.type()); + source.copyTo(temp, mask); + m_compLabeler.set(numThreads, temp); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } + else { + m_compLabeler.set(numThreads, source); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } +} + +/** +- FUNCTION: detect +- FUNCTIONALITY: detects blob in the image +- PARAMETERS: + - source: Mat to extract the blobs from, CV_8UC1 + - mask: optional mask to apply. The blobs will be extracted where the mask is + not 0. All the neighbouring blobs where the mask is 0 will be external blobs + - numThreads: number of labelling threads. +- RESULT: + - the object will contain the detected blobs. +- RESTRICTIONS: +- AUTHOR: Saverio Murgia & Luca Nardelli +- CREATION DATE: 10-04-2014. +- MODIFICATION: Date. Author. Description. +*/ +const BlobGroup& BlobDetector::detect(cv::Mat& source, const cv::Mat& mask /*= cv::Mat()*/, int numThreads/*=1*/) +{ + m_blobGroup.clearBlobs(); + if (mask.data) { + cv::Mat temp = cv::Mat::zeros(source.size(), source.type()); + source.copyTo(temp, mask); + m_compLabeler.set(numThreads, temp); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } + else { + m_compLabeler.set(numThreads, source); + m_compLabeler.doLabeling(m_blobGroup.m_blobs); + } + + return m_blobGroup; +} + +const BlobGroup& BlobDetector::getBlobGroup() +{ + return m_blobGroup; +} + diff --git a/examples/contour/blob/BlobDetector.h b/examples/contour/blob/BlobDetector.h new file mode 100644 index 00000000..b67dbb61 --- /dev/null +++ b/examples/contour/blob/BlobDetector.h @@ -0,0 +1,45 @@ +#ifndef BLOBDETECTOR_H +#define BLOBDETECTOR_H + +#include +#include +#include "opencv2/core/core_c.h" +#include +#include "blob.h" +#include "BlobOperators.h" +#include "ComponentLabeling.h" +#include "BlobGroup.h" + +class BlobDetector +{ +public: + BlobDetector(); + + BlobDetector(IplImage* source, IplImage* mask = NULL, int numThreads = 1); + + BlobDetector(cv::Mat& source, const cv::Mat& mask = cv::Mat(), int numThreads = 1); + + BlobDetector(const BlobDetector& source); + + BlobDetector& operator=(const BlobDetector& source); + + BlobDetector(BlobDetector&& source) noexcept; + + BlobDetector& operator=(BlobDetector&& source) noexcept; + + //! Destructor + virtual ~BlobDetector(); + + //! Function to detect blobs in a new image + const BlobGroup& detect(cv::Mat& source, const cv::Mat& mask = cv::Mat(), int numThreads = 1); + + const BlobGroup& getBlobGroup(); + +private: + CompLabelerGroup m_compLabeler; + + BlobGroup m_blobGroup; +}; + + +#endif // BLOBDETECTOR_H diff --git a/examples/contour/blob/BlobGroup.cpp b/examples/contour/blob/BlobGroup.cpp new file mode 100644 index 00000000..f928600b --- /dev/null +++ b/examples/contour/blob/BlobGroup.cpp @@ -0,0 +1,663 @@ +/************************************************************************ +BlobGroup.cpp + +FUNCIONALITAT: Implementation de la classe BlobGroup +AUTOR: Inspecta S.L. +MODIFICACIONS (Modificacition, Autor, Data): + +**************************************************************************/ + +#include "BlobGroup.h" +//! Show errors functions: only works for windows releases +#ifdef _SHOW_ERRORS +#include //suport per a CStrings +#include //suport per a AfxMessageBox +#endif + +/************************************************************************** + Constructors / Destructors +**************************************************************************/ + +/** +- FUNCTION: BlobGroup +- FUNCTIONALITY: Standard constructor +- PARAMETERS: +- RESULT: + - creates an empty set of blobs +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobGroup::BlobGroup() +{ + m_blobs = BlobVector(); +} + +/** +- FUNCTION: BlobGroup +- FUNCTIONALITY: Copy constructor +- PARAMETERS: + - source: object to copy +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobGroup::BlobGroup(const BlobGroup& source) +{ + // create the new from the past as a parameter + //m_blobs = BlobVector(source.getNumBlobs()); + m_blobs.reserve(source.getNumBlobs()); + // copy the blobs from the origin to the current one + BlobVector::const_iterator pBlobsSrc = source.m_blobs.begin(); + //BlobVector::iterator pBlobsDst = m_blobs.begin(); + while (pBlobsSrc != source.m_blobs.end()) { + // can't call the operator = since BlobVector is a + // Blob vector *. So create a new blob from the + // original blob + m_blobs.push_back(new Blob(**pBlobsSrc)); + pBlobsSrc++; + } +} + +/** +- FUNCTION: ~BlobGroup +- FUNCTIONALITY: Destructor +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobGroup::~BlobGroup() +{ + clearBlobs(); +} + +/************************************************************************** + Operadors / Operators +**************************************************************************/ + +/** +- FUNCTION: Assigment operator +- FUNCTIONALITY: +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobGroup& BlobGroup::operator=(const BlobGroup& source) +{ + // if they are already the same, nothing needs to be done + if (this != &source) { + // release the old set of blobs + for (int i = 0; i < getNumBlobs(); ++i) { + delete m_blobs[i]; + } + m_blobs.clear(); + // create the new from the past as a parameter + m_blobs = BlobVector(source.getNumBlobs()); + // copy the blobs from the origin to the current one + BlobVector::const_iterator pBlobsSrc = source.m_blobs.begin(); + BlobVector::iterator pBlobsDst = m_blobs.begin(); + + while (pBlobsSrc != source.m_blobs.end()) { + // can't call the operator = since BlobVector is a + // Blob vector *. So create a new blob from the + // original blob + *pBlobsDst = new Blob(**pBlobsSrc); + pBlobsSrc++; + pBlobsDst++; + } + } + return *this; +} + +/** +- FUNCTION: + operator +- FUNCTIONALITY: Joins the blobs in source with the current ones +- PARAMETERS: + - source: object to copy the blobs +- RESULT: + - object with the actual blobs and the source blobs +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +BlobGroup BlobGroup::operator+(const BlobGroup& source) const +{ + // create the result from the current blobs + BlobGroup result(*this); + + // reserve memory for the new blobs + result.m_blobs.resize(result.getNumBlobs() + source.getNumBlobs()); + + // declare the iterators to cross the origin and destination blobs + BlobVector::const_iterator pBlobsSrc = source.m_blobs.begin(); + BlobVector::iterator pBlobsDst = result.m_blobs.end(); + + // insert the blobs from the origin to the current one + while (pBlobsSrc != source.m_blobs.end()) { + pBlobsDst--; + *pBlobsDst = new Blob(**pBlobsSrc); + pBlobsSrc++; + } + + return result; +} + +/************************************************************************** + Operacions / Operations +**************************************************************************/ + +/** +- FUNCTIONS: addBlob +- FUNCIONALITAT: Afegeix un blob al conjunt +- PARAMETERS: + - blob: blob a afegir +- RESULTAT: + - modifica el conjunt de blobs actual +- RESTRICCIONS: +- AUTOR: Ricard Borràs +- DATE OF CREATION: 2006/03/01 +- MODIFICATION: Data. Autor. Description. +*/ +void BlobGroup::addBlob(Blob* blob) +{ + if (blob != NULL) { + Blob* tp = new Blob(blob); + m_blobs.push_back(tp); + } +} + +#ifdef MATRIXCV_ACTIU +/** +- FUNCTION: getResult +- FUNCTIONALITY: Computes the function evaluator on all the blobs of the class + and returns a vector with the result +- PARAMETERS: + - evaluator: function to apply to each blob (any object derived from the + OperatorBlob class) +- RESULT: + - vector with all the results in the same order as the blobs +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +DoubleVector BlobGroup::getResult(BlobOperator* evaluator) const +{ + if (getNumBlobs() <= 0) { + return DoubleVector(); + } + + // define the result + DoubleVector result = DoubleVector(getNumBlobs()); + // and iterators on the blobs and the result + DoubleVector::iterator itResult = result.GetIterator(); + BlobVector::const_iterator itBlobs = m_blobs.begin(); + + // evaluate the function on all blobs + while (itBlobs != m_blobs.end()) { + *itResult = (*evaluator)(**itBlobs); + itBlobs++; + itResult++; + } + return result; +} +#endif + +/** +- FUNCTION: getResult +- FUNCTIONALITY: Computes the function evaluator on all the blobs of the class + and returns a vector with the result +- PARAMETERS: + - evaluator: function to apply to each blob (any object derived from the + OperatorBlob class) +- RESULT: + - vector with all the results in the same order as the blobs +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +DoubleStlVector BlobGroup::getStlResult(BlobOperator* evaluator) const +{ + if (getNumBlobs() <= 0) { + return DoubleStlVector(); + } + + // define the result + DoubleStlVector result = DoubleStlVector(getNumBlobs()); + // and iterators on the blobs and the result + DoubleStlVector::iterator itResult = result.begin(); + BlobVector::const_iterator itBlobs = m_blobs.begin(); + + // evaluate the function on all blobs + while (itBlobs != m_blobs.end()) { + *itResult = (*evaluator)(**itBlobs); + itBlobs++; + itResult++; + } + return result; +} + +/** +- FUNCTION: getNumber +- FUNCTIONALITY: Computes the function evaluator on a blob of the class +- PARAMETERS: + - indexBlob: index of the blob to compute the function + - evaluator: function to apply to each blob (any object derived from the + OperatorBlob class) +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGroup::getNumber(int indexBlob, BlobOperator* evaluator) const +{ + if (indexBlob < 0 || indexBlob >= getNumBlobs()) { + raiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + } + return (*evaluator)(*m_blobs[indexBlob]); +} + +/** +- FUNCTION: filter (const version) +- FUNCTIONALITY: Get some blobs from the class based on conditions on measures + of the blobs. +- PARAMETERS: + - dst: where to store the selected blobs + - filterAction: B_INCLUDE: include the blobs which pass the filter in the result + B_EXCLUDE: exclude the blobs which pass the filter in the result + - evaluator: Object to evaluate the blob + - Condition: How to decide if the result returned by evaluator on each blob + is included or not. It can be: + B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, + B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE + - LowLimit: numerical value to evaluate the Condition on evaluator(blob) + - HighLimit: numerical value to evaluate the Condition on evaluator(blob). + Only useful for B_INSIDE and B_OUTSIDE +- RESULT: + - It returns on dst the blobs that accomplish (B_INCLUDE) or discards (B_EXCLUDE) + the Condition on the result returned by evaluator on each blob +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +/////////////////////////// FILTRAT DE BLOBS //////////////////////////////////// +void BlobGroup::filter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit /*=0*/) const + +{ + // do the job + doFilter(dst, filterAction, evaluator, condition, lowLimit, highLimit); +} + +/** +- FUNCTION: filter +- FUNCTIONALITY: Get some blobs from the class based on conditions on measures + of the blobs. +- PARAMETERS: + - dst: where to store the selected blobs + - filterAction: B_INCLUDE: include the blobs which pass the filter in the result + B_EXCLUDE: exclude the blobs which pass the filter in the result + - evaluator: Object to evaluate the blob + - Condition: How to decide if the result returned by evaluator on each blob + is included or not. It can be: + B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL, + B_LESS_OR_EQUAL,B_INSIDE,B_OUTSIDE + - LowLimit: numerical value to evaluate the Condition on evaluator(blob) + - HighLimit: numerical value to evaluate the Condition on evaluator(blob). + Only useful for B_INSIDE and B_OUTSIDE +- RESULT: + - It returns on dst the blobs that accomplish (B_INCLUDE) or discards (B_EXCLUDE) + the Condition on the result returned by evaluator on each blob +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void BlobGroup::filter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit /*=0*/) + +{ + int numBlobs = getNumBlobs(); + + // do the job + doFilter(dst, filterAction, evaluator, condition, lowLimit, highLimit); + + // inline operation: remove previous blobs + if (&dst == this) { + // delete the first blobs (which are the originals) + // since we will have them replicated at the end if they pass the filter + BlobVector::iterator itBlobs = m_blobs.begin(); + for (int i = 0; i < numBlobs; i++) { + delete *itBlobs; + itBlobs++; + } + m_blobs.erase(m_blobs.begin(), itBlobs); + } +} + +void BlobGroup::filter(BlobGroup& dst, FilterAction filterAction, BlobOperator* evaluator, FilterCondition condition, double lowLimit, double highLimit /*= 0 */) +{ + filter(dst, (int)filterAction, evaluator, (int)condition, lowLimit, highLimit); +} + + +//! Does the filter method job +void BlobGroup::doFilter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit/* = 0*/) const +{ + int i, numBlobs; + bool evaluationResult; + DoubleStlVector evaluationBlobs; + DoubleStlVector::iterator itEvaluationBlobs; + + if (getNumBlobs() <= 0) { return; } + if (!evaluator) { return; } + // evaluate the blobs with the relevant function + evaluationBlobs = getStlResult(evaluator); + itEvaluationBlobs = evaluationBlobs.begin(); + numBlobs = getNumBlobs(); + switch(condition) + { + case B_EQUAL: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult= *itEvaluationBlobs == lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_NOT_EQUAL: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = *itEvaluationBlobs != lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_GREATER: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = *itEvaluationBlobs > lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_LESS: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = *itEvaluationBlobs < lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_GREATER_OR_EQUAL: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = *itEvaluationBlobs>= lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_LESS_OR_EQUAL: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = *itEvaluationBlobs <= lowLimit; + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_INSIDE: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = (*itEvaluationBlobs >= lowLimit) && (*itEvaluationBlobs <= highLimit); + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + case B_OUTSIDE: + for (i = 0; i < numBlobs; ++i, ++itEvaluationBlobs) { + evaluationResult = (*itEvaluationBlobs < lowLimit) || (*itEvaluationBlobs > highLimit); + if ((evaluationResult && filterAction == B_INCLUDE) || + (!evaluationResult && filterAction == B_EXCLUDE)) { + dst.m_blobs.push_back(new Blob(getBlob(i))); + } + } + break; + } +} + +/** +- FUNCTION: getBlob +- FUNCTIONALITY: Gets the n-th blob (without ordering the blobs) +- PARAMETERS: + - index: index in the blob array +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +Blob BlobGroup::getBlob(int index) const +{ + if (index < 0 || index >= getNumBlobs()) { + raiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + } + + return *m_blobs[index]; +} + +Blob *BlobGroup::getBlob(int index) +{ + if (index < 0 || index >= getNumBlobs()) { + raiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + } + return m_blobs[index]; +} + +Blob BlobGroup::getBlobByID(LabelID id) const{ + for (int i = 0; i < getNumBlobs(); ++i) { + if (getBlob(i).getID() == id) { + return m_blobs[i]; + } + } + raiseError(EXCEPTION_EXECUTE_FAULT); + return Blob(); +} + +Blob *BlobGroup::getBlobByID(LabelID id) { + for (int i = 0; i < getNumBlobs(); ++i) { + if (getBlob(i)->getID() == id) { + return m_blobs[i]; + } + } + raiseError(EXCEPTION_EXECUTE_FAULT); + return (new Blob()); +} + +/** +- FUNCTION: getNthBlob +- FUNCTIONALITY: Gets the n-th blob ordering first the blobs with some criteria +- PARAMETERS: + - criteria: criteria to order the blob array + - nBlob: index of the returned blob in the ordered blob array + - dst: where to store the result +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void BlobGroup::getNthBlob(BlobOperator* criteria, int nBlob, Blob& dst) const +{ + // verify that we are not accessing out the blobs vector + if(nBlob < 0 || nBlob >= getNumBlobs()) { + //raiseError(EXCEPTION_BLOB_OUT_OF_BOUNDS); + dst = Blob(); + return; + } + + DoubleStlVector evaluationBlobs, evaluationBlobsOrdered; + double value; + + // evaluate the blobs with the relevant function + evaluationBlobs = getStlResult(criteria); + + evaluationBlobsOrdered = DoubleStlVector(getNumBlobs()); + + // get the nBlob first results (in descending order) + std::partial_sort_copy(evaluationBlobs.begin(), + evaluationBlobs.end(), + evaluationBlobsOrdered.begin(), + evaluationBlobsOrdered.end(), + std::greater()); + + value = evaluationBlobsOrdered[nBlob]; + + // look for the first blob that has the value n-ssim + DoubleStlVector::const_iterator itEvaluation = evaluationBlobs.begin(); + + bool foundBlob = false; + int indexBlob = 0; + while (itEvaluation != evaluationBlobs.end() && !foundBlob) { + if (*itEvaluation == value) { + foundBlob = true; + dst = Blob(getBlob(indexBlob)); + } + itEvaluation++; + indexBlob++; + } +} + +/** +- FUNCTION: clearBlobs +- FUNCTIONALITY: Clears all the blobs from the object and releases all its memory +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void BlobGroup::clearBlobs() +{ + BlobVector::iterator itBlobs = m_blobs.begin(); + while (itBlobs != m_blobs.end()) { + delete *itBlobs; + itBlobs++; + } + + m_blobs.clear(); +} + +/** +- FUNCTION: raiseError +- FUNCTIONALITY: Error handling function +- PARAMETERS: + - errorCode: reason of the error +- RESULT: + - in _SHOW_ERRORS version, shows a message box with the error. In release is silent. + In both cases throws an exception with the error. +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void BlobGroup::raiseError(int errorCode) const +{ + //! Do we need to show errors? +#ifdef _SHOW_ERRORS + CString msg, format = "Error en BlobGroup: %s"; + + switch (errorCode) + { + case EXCEPTION_BLOB_OUT_OF_BOUNDS: + msg.Format(format, "Intentant accedir a un blob no existent"); + break; + default: + msg.Format(format, "Codi d'error desconegut"); + break; + } + + AfxMessageBox(msg); + +#endif + throw errorCode; +} + +/************************************************************************** + Auxiliars / Auxiliary functions +**************************************************************************/ + +/** +- FUNCTION: printBlobs +- FUNCTIONALITY: Prints some blob features in an ASCII file +- PARAMETERS: + - fileName: full path + filename to generate +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void BlobGroup::printBlobs(char* fileName) const +{ + DoubleStlVector area, /*perimeter,*/ exterior, compactness, length, + externalPerimeter, perimeterConvex, perimeter; + int i; + FILE* fp; + + area = getStlResult(BlobGetArea()); + perimeter = getStlResult(BlobGetPerimeter()); + exterior = getStlResult(BlobGetExterior()); + compactness = getStlResult(BlobGetCompactness()); + length = getStlResult(BlobGetLength()); + externalPerimeter = getStlResult(BlobGetExternalPerimeter()); + perimeterConvex = getStlResult(BlobGetHullPerimeter()); + + fp = fopen(fileName, "w"); + + for (i = 0; i < getNumBlobs(); ++i) { + fprintf(fp, "blob %d ->\t a=%7.0f\t p=%8.2f (%8.2f external)\t pconvex=%8.2f\t ext=%.0f\t c=%3.2f\t l=%8.2f\n", + i, area[i], perimeter[i], externalPerimeter[i], perimeterConvex[i], exterior[i], compactness[i], length[i]); + } + fclose(fp); + +} + +Blob* BlobGroup::getBlobNearestTo(const cv::Point& pt) +{ + float minD = FLT_MAX, d = 0; + int numBlobs = m_blobs.size(); + int idxNearest = -1; + for (int i = 0; i < numBlobs; ++i) { + cv::Point diff = m_blobs[i]->getCenter() - pt; + d = diff.x * diff.x + diff.y * diff.y; + if (minD > d) { + idxNearest = i; + minD = d; + } + } + if (idxNearest != -1) { + return m_blobs[idxNearest]; + } + else { + return NULL; + } +} diff --git a/examples/contour/blob/BlobGroup.h b/examples/contour/blob/BlobGroup.h new file mode 100644 index 00000000..eea63371 --- /dev/null +++ b/examples/contour/blob/BlobGroup.h @@ -0,0 +1,116 @@ +/************************************************************************ +BlobGroup.h + +FUNCTIONALITY: Definition of the BlobGroup class +AUTHOR: Inspecta S.L. +MODIFICATIONS (Modification, Author, Date): + +**************************************************************************/ + + +#if !defined(_CLASSE_BLOBRESULT_INCLUDED) +#define _CLASSE_BLOBRESULT_INCLUDED + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "BlobLibraryConfiguration.h" +#include "ComponentLabeling.h" +#include "defines.h" +#include +#include "opencv2/core/core_c.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "blob.h" +#include "BlobOperators.h" +#include "ComponentLabeling.h" + +class BlobGroup +{ +public: + //! Constructor, opencv 1.0 and 2.0 interfaces. + BlobGroup(); + + BlobGroup(const BlobGroup& source); + + //! Destructor + virtual ~BlobGroup(); + + //! Assigment operator + BlobGroup& operator=(const BlobGroup& source); + + //! Addition operator to concatenate two sets of blobs + BlobGroup operator+(const BlobGroup& source) const; + + //! Adds a blob to the set of blobs + void addBlob(Blob* blob); + +#ifdef MATRIXCV_ACTIU + //! Computes some property on all the blobs of the class + DoubleVector getResult(BlobOperator* evaluator) const; +#endif + //! Computes some property on all the blobs of the class + DoubleStlVector getStlResult(BlobOperator* evaluator) const; + + //! Computes some property on one blob of the class + double getNumber(int indexblob, BlobOperator* evaluator) const; + + //! Filters the blobs of the class using some property + void filter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit = 0); + + void filter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit = 0) const; + + void filter(BlobGroup& dst, FilterAction filterAction, BlobOperator* evaluator, FilterCondition condition, double lowLimit, double highLimit = 0); + + //! Sorts the blobs of the class acording to some criteria and returns the n-th blob + void getNthBlob(BlobOperator* criteria, int nBlob, Blob& dst) const; + + //! Gets the n-th blob of the class (without sorting) + Blob getBlob(int index) const; + + Blob* getBlob(int index); + + Blob getBlobByID(LabelID id) const; + + Blob* getBlobByID(LabelID id); + + //! Clears all the blobs of the class + void clearBlobs(); + + //! Prints some features of all the blobs in a file + void printBlobs(char* fileName) const; + + //! Returns blob with center nearest to point pt + Blob* getBlobNearestTo(const cv::Point& pt); + + //! Gets the total number of blobs + int getNumBlobs() const { + return (m_blobs.size()); + } + + const BlobVector& getBlobVector() { return m_blobs; } + +private: + //! Function to manage the errors + void raiseError(int errorCode) const; + + //! Does the filter method job + void doFilter(BlobGroup& dst, int filterAction, BlobOperator* evaluator, int condition, double lowLimit, double highLimit = 0) const; + +protected: + //! Vector with all the blobs + BlobVector m_blobs; + + friend class BlobDetector; +}; + +#endif // !defined(_CLASSE_BLOBRESULT_INCLUDED) diff --git a/examples/contour/blob/BlobLibraryConfiguration.h b/examples/contour/blob/BlobLibraryConfiguration.h new file mode 100644 index 00000000..0efad05a --- /dev/null +++ b/examples/contour/blob/BlobLibraryConfiguration.h @@ -0,0 +1,28 @@ +/************************************************************************ +BlobLibraryConfiguration.h + +FUNCTIONALITY: Global configuration of the library +AUTHOR: Inspecta S.L. +MODIFICATIONS (Modification, Author, Date): + +**************************************************************************/ + +//! Indicates whether the MatrixCV is to be used or not +//! Use/Not use the MatrixCV class +//#define MATRIXCV_ACTIU + +//! Uses/not use the blob object factory +//#define BLOB_OBJECT_FACTORY + +//! Show/not show blob access errors +//#define _SHOW_ERRORS + +#ifndef EXCEPTION_READ_FAULT +#define EXCEPTION_READ_FAULT 0 // Access violation was caused by a read +#endif +#ifndef EXCEPTION_WRITE_FAULT +#define EXCEPTION_WRITE_FAULT 1 // Access violation was caused by a read +#endif +#ifndef EXCEPTION_EXECUTE_FAULT +#define EXCEPTION_EXECUTE_FAULT 8 // Access violation was caused by a read +#endif diff --git a/examples/contour/blob/BlobOperators.cpp b/examples/contour/blob/BlobOperators.cpp new file mode 100644 index 00000000..bae1783d --- /dev/null +++ b/examples/contour/blob/BlobOperators.cpp @@ -0,0 +1,456 @@ +#include +#include "BlobOperators.h" + +/*************************************************************************** + Implementation of the helper classes to perform operations on blobs +***************************************************************************/ + +/** +- FUNCTION: moment +- FUNCTIONALITY: Calculates the pq moment of the blob +- PARAMETERS: +- RESULT: + - returns the pq moment or 0 if the moment it is not implemented +- RESTRICTIONS: + - Currently, implemented moments up to 3 +- AUTHOR: Ricard Borràs +- CREATION DATE: 20-07-2004. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetMoment::operator()(Blob& blob) +{ + return blob.moment(m_p, m_q); +} + +/** +- FUNCTION: BlobGetHullPerimeter +- FUNCTIONALITY: Calculates the convex hull perimeter of the blob +- PARAMETERS: +- RESULT: + - returns the convex hull perimeter of the blob or the perimeter if the + blob edges could not be retrieved +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetHullPerimeter::operator()(Blob& blob) +{ + Contours convexHull; + blob.getConvexHull(convexHull); + double perimeter; + + if (convexHull.size() != 0) { + perimeter = fabs(arcLength(convexHull[0], true)); + } + else { + return 0; + } + return perimeter; +} + +double BlobGetHullArea::operator()(Blob& blob) +{ + Contours convexHull; + blob.getConvexHull(convexHull); + double area; + + if (convexHull.size() == 0) { + area = fabs(contourArea(convexHull[0], true)); + } + else { + return 0; + } + return area; +} + +/** +- FUNCTION: BlobGetMinXatMinY +- FUNCTIONALITY: Calculates the minimum X on the minimum Y +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetMinXatMinY::operator()(Blob& blob) +{ + double result = static_cast(LONG_MAX); + + //CvSeqReader reader; + //CvPoint actualPoint; + PointList externalContour; + + externalContour = blob.getExternalContour()->getContourPoints(); + if (externalContour.size()==0) return result; + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + for (; it != en; ++it) { + cv::Point &actualPoint = *it; + + if ((actualPoint.y == blob.minY()) && (actualPoint.x < result)) { + result = actualPoint.x; + } + } + + return result; +} + +/** +- FUNCTION: BlobGetMinXatMinY +- FUNCTIONALITY: Calculates the minimum Y on the maximum X +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetMinYatMaxX::operator()(Blob& blob) +{ + double result = static_cast(LONG_MAX); + + //CvSeqReader reader; + //CvPoint actualPoint; + PointList externalContour; + + externalContour = blob.getExternalContour()->getContourPoints(); + if (externalContour.size() == 0) { return result; } + + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + for (; it != en; ++it) { + cv::Point actualPoint = *it; + + if ((actualPoint.x == blob.maxX()) && (actualPoint.y < result)) { + result = actualPoint.y; + } + } + + return result; +} + +/** +- FUNCTION: BlobGetMaxXatMaxY +- FUNCTIONALITY: Calculates the maximum X on the maximum Y +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetMaxXatMaxY::operator()(Blob& blob) +{ + double result = LONG_MIN; + + //CvSeqReader reader; + //CvPoint actualPoint; + PointList externalContour; + + externalContour = blob.getExternalContour()->getContourPoints(); + if (externalContour.size() == 0) { return result; } + + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + for (; it != en; ++it) { + cv::Point &actualPoint = *it; + + if ((actualPoint.y == blob.maxY()) && (actualPoint.x > result)) { + result = actualPoint.x; + } + } + return result; +} + +/** +- FUNCTION: BlobGetMaxYatMinX +- FUNCTIONALITY: Calculates the maximum Y on the minimum X +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetMaxYatMinX::operator()(Blob& blob) +{ + double result = LONG_MIN; + + //CvSeqReader reader; + //CvPoint actualPoint; + PointList externalContour; + + externalContour = blob.getExternalContour()->getContourPoints(); + if (externalContour.size() == 0) { return result; } + + + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + for (; it != en; ++it) { + cv::Point &actualPoint = *it; + + if ((actualPoint.x == blob.minX()) && (actualPoint.y > result)) { + result = actualPoint.y; + } + } + + return result; +} + +/** +- FUNCTION: BlobGetElongation +- FUNCTIONALITY: Calculates the elongation of the blob (length/breadth) +- PARAMETERS: +- RESULT: +- RESTRICTIONS: - See below to see how the lenght and the breadth are aproximated +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetElongation::operator()(Blob& blob) +{ + double widthC, lengthC, width, length; + + double tmp; + + tmp = blob.perimeter()*blob.perimeter() - 16 * blob.area(); + + if (tmp > 0.0) { + widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4; + // error intrínsec en els càlculs de l'àrea i el perímetre + } + else { + widthC = (double) (blob.perimeter()) / 4; + } + if (widthC <= 0.0) { return 0; } + lengthC = (double) blob.area() / widthC; + + length = MAX(lengthC, widthC); + width = MIN(lengthC, widthC); + + return (double) length/width; +} + +/** +- FUNCTION: BlobGetCompactness +- FUNCTIONALITY: Calculates the compactness of the blob + (maximum for circle shaped blobs, minimum for the rest) +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetCompactness::operator()(Blob& blob) +{ + if (blob.area() != 0.0) { + return (double) pow(blob.perimeter(), 2)/(4 * CV_PI * blob.area()); + } + else { + return 0.0; + } +} + +/** +- FUNCTION: BlobGetRoughness +- FUNCTIONALITY: Calculates the roughness of the blob + (ratio between perimeter and convex hull perimeter) +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetRoughness::operator()(Blob& blob) +{ + BlobGetHullPerimeter getHullPerimeter = BlobGetHullPerimeter(); + + double hullPerimeter = getHullPerimeter(blob); + + if (hullPerimeter != 0.0) { + return blob.perimeter() / hullPerimeter;//HullPerimeter(); + } + return 0.0; +} + +/** +- FUNCTION: BlobGetLength +- FUNCTIONALITY: Calculates the lenght of the blob (the biggest axis of the blob) +- PARAMETERS: +- RESULT: +- RESTRICTIONS: + - The lenght is an aproximation to the real lenght +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetLength::operator()(Blob& blob) +{ + double widthC, lengthC; + double tmp; + + tmp = blob.perimeter() * blob.perimeter() - 16 * blob.area(); + + if (tmp > 0.0) { + widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4; + // error intrínsec en els càlculs de l'àrea i el perímetre + } + else { + widthC = (double) (blob.perimeter()) / 4; + } + if (widthC <= 0.0) { return 0; } + lengthC = (double) blob.area() / widthC; + + return MAX(lengthC , widthC); +} + +/** +- FUNCTION: BlobGetBreadth +- FUNCTIONALITY: Calculates the breadth of the blob (the smallest axis of the blob) +- PARAMETERS: +- RESULT: +- RESTRICTIONS: + - The breadth is an aproximation to the real breadth +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetBreadth::operator()(Blob& blob) +{ + double widthC, lengthC; + double tmp; + + tmp = blob.perimeter() * blob.perimeter() - 16 * blob.area(); + + if (tmp > 0.0) { + widthC = (double) (blob.perimeter() + sqrt(tmp)) / 4; + // error intrínsec en els càlculs de l'àrea i el perímetre + } + else { + widthC = (double) (blob.perimeter()) / 4; + } + if (widthC <= 0.0) { return 0; } + lengthC = (double) blob.area() / widthC; + + return MIN(lengthC , widthC); +} + +/** +- FUNCTION: BlobGetDistanceFromPoint +- FUNCTIONALITY: Calculates the euclidean distance between the blob center and + the point specified in the constructor +- PARAMETERS: +- RESULT: +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetDistanceFromPoint::operator()(Blob& blob) +{ + BlobGetXCenter getXCenter; + BlobGetYCenter getYCenter; + + double deltaX = m_x - getXCenter(blob); + double deltaY = m_y - getYCenter(blob); + + return sqrt((deltaX * deltaX) + (deltaY * deltaY)); +} + +/** +- FUNCTION: BlobGetXYInside +- FUNCTIONALITY: Calculates whether a point is inside the + rectangular bounding box of a blob +- PARAMETERS: +- RESULT: + - returns 1 if it is inside; o if not +- RESTRICTIONS: +- AUTHOR: Francesc Pinyol Margalef +- CREATION DATE: 16-01-2006. +- MODIFICATION: Date. Author. Description. +*/ +double BlobGetXYInside::operator()(Blob& blob) +{ + const PointList &contourPoints = blob.getExternalContour()->getContourPoints(); + if (contourPoints.size() == 0) { + return pointPolygonTest(contourPoints, m_p, false) >= 0; + } + + return 0; +} +#ifdef BLOB_OBJECT_FACTORY + +/** +- FUNCTION: RegisterAll Operators +- FUNCTIONALITY: Register all operators defined in blob.h +- PARAMETERS: +- fabricaOperadorsBlob: factory where the operators will be registered +- RESULT: +- Modifies the manufacturesOperatorsBlob object +- RESTRICTIONS: +- Only blob.h operators will be registered. If you want to add them, you need to add them with +the Register method of the factory. +- AUTHOR: trees +- DATE OF CREATION: 2006/05/18 +- MODIFICATION: Date. Author. Description. +*/ +void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob) +{ + // blob shape + fabricaOperadorsBlob.Register(BlobGetArea().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetBreadth().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetCompactness().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetElongation().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetExterior().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetLength().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetPerimeter().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetRoughness().name(), Type2Type()); + + // blob color + fabricaOperadorsBlob.Register(BlobGetMean(NULL).name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetStdDev(NULL).name(), Type2Type()); + + // external pixels + fabricaOperadorsBlob.Register(BlobGetExternalPerimeterRatio().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetExternalHullPerimeterRatio().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetExternalPerimeter().name(), Type2Type()); + + + // hull + fabricaOperadorsBlob.Register(BlobGetHullPerimeter().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetHullArea().name(), Type2Type()); + + + // elipse info + fabricaOperadorsBlob.Register(BlobGetMajorAxisLength().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMinorAxisLength().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetAxisRatio().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetOrientation().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetOrientationCos().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetAreaElipseRatio().name(), Type2Type()); + + // min an max + fabricaOperadorsBlob.Register(BlobGetMaxX().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMaxY().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMinX().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMinY().name(), Type2Type()); + + fabricaOperadorsBlob.Register(BlobGetMaxXatMaxY().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMaxYatMinX().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMinXatMinY().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetMinYatMaxX().name(), Type2Type()); + + // coordinate info + fabricaOperadorsBlob.Register(BlobGetXYInside().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetDiffY().name(), Type2Type()); + fabricaOperadorsBlob.Register(CBlobGetDiffX().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetXCenter().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetYCenter().name(), Type2Type()); + fabricaOperadorsBlob.Register(BlobGetDistanceFromPoint().name(), Type2Type()); + + // moments + fabricaOperadorsBlob.Register(BlobGetMoment().name(), Type2Type()); + +} + +#endif //BLOB_OBJECT_FACTORY diff --git a/examples/contour/blob/BlobOperators.h b/examples/contour/blob/BlobOperators.h new file mode 100644 index 00000000..8fbd49bc --- /dev/null +++ b/examples/contour/blob/BlobOperators.h @@ -0,0 +1,731 @@ +/************************************************************************** + Definition of classes to perform operations on blobs + Helper classes to perform operations on blobs +**************************************************************************/ + + +#ifndef BLOB_OPERATORS_H_INCLUDED +#define BLOB_OPERATORS_H_INCLUDED + +#include "blob.h" + +//! Degree to radian conversion factor +#define DEGREE2RAD (CV_PI / 180.0) + + +//! Interface to derive all blob operations +class OperatorBlob +{ +public: + virtual ~OperatorBlob() {}; + + //! Apply operator to blob + virtual double operator()(Blob& blob) = 0; + //! Get operator name + virtual const char *name() = 0; + + operator OperatorBlob*() + { + return (OperatorBlob*)this; + } +}; + +typedef OperatorBlob BlobOperator; + +#ifdef BLOB_OBJECT_FACTORY +/** + Function to compare two identifiers within the COperadorBlobs factory + */ +struct functorComparacioIdOperador +{ + bool operator()(const char* s1, const char* s2) const + { + return strcmp(s1, s2) < 0; + } +}; + +//! Definition of Object factory type for OperatorBlob objects +typedef ObjectFactory t_OperadorBlobFactory; + +//! Global function to register all operators defined in blob.h +void RegistraTotsOperadors(t_OperadorBlobFactory &fabricaOperadorsBlob); + +#endif + + +//! Class to get ID of a blob +class BlobGetID : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.getID(); + } + const char *name() + { + return "BlobGetID"; + } +}; + +//! Class to get bool m_toBeDeleted +class BlobGetTBDeleted : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.m_toBeDeleted; + } + const char *name() + { + return "BlobGetTBDeleted"; + } +}; + + +//! Class to get the area of a blob +class BlobGetArea : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.area(); + } + const char *name() + { + return "BlobGetArea"; + } +}; + +//! Class to get the perimeter of a blob +class BlobGetPerimeter : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.perimeter(); + } + const char *name() + { + return "BlobGetPerimeter"; + } +}; + +//! Class to get the external flag of a blob +class BlobGetExterior : public OperatorBlob +{ +public: + BlobGetExterior() + { + m_mask = NULL; + m_xBorder = true; + m_yBorder = true; + } + BlobGetExterior(IplImage* mask, bool xBorder = true, bool yBorder = true) + { + m_mask = mask; + m_xBorder = xBorder; + m_yBorder = yBorder; + } + double operator()(Blob& blob) + { + return blob.exterior(m_mask, m_xBorder, m_yBorder); + } + const char *name() + { + return "BlobGetExterior"; + } +private: + IplImage* m_mask; + bool m_xBorder, m_yBorder; +}; + +//! Class to get the mean grey level of a blob +class BlobGetMean : public OperatorBlob +{ +public: + BlobGetMean() + { + m_image = NULL; + } + BlobGetMean(IplImage* image) + { + m_image = image; + }; + + double operator()(Blob& blob) + { + return blob.mean(m_image); + } + const char *name() + { + return "BlobGetMean"; + } +private: + + IplImage* m_image; +}; + + +//! Class to get the standard deviation of the grey level values of a blob +class BlobGetStdDev : public OperatorBlob +{ +public: + BlobGetStdDev() + { + m_image = NULL; + } + BlobGetStdDev(IplImage* image) + { + m_image = image; + }; + double operator()(Blob& blob) + { + return blob.stdDev(m_image); + } + const char *name() + { + return "BlobGetStdDev"; + } +private: + + IplImage* m_image; + +}; + +//! Class to calculate the compactness of a blob +class BlobGetCompactness : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetCompactness"; + } +}; + +//! Class to calculate the length of a blob +class BlobGetLength : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetLength"; + } +}; + +//! Class to calculate the breadth of a blob +class BlobGetBreadth : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetBreadth"; + } +}; + +//! Class to calculate the difference in X of the blob +class CBlobGetDiffX : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.getBoundingBox().width; + } + const char *name() + { + return "CBlobGetDiffX"; + } +}; + +//! Class to calculate the difference in X of the blob +class BlobGetDiffY : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.getBoundingBox().height; + } + const char *name() + { + return "BlobGetDiffY"; + } +}; + +//! Class to calculate the P, Q moment of a blob +class BlobGetMoment : public OperatorBlob +{ +public: + //! Standard constructor (gets the 00 moment) + BlobGetMoment() + { + m_p = m_q = 0; + } + + //! Constructor: gets the P, Q moment + BlobGetMoment(int p, int q) + { + m_p = p; + m_q = q; + }; + double operator()(Blob& blob); + const char *name() + { + return "BlobGetMoment"; + } + +private: + //! moment que volem calcular + int m_p, m_q; +}; + +//! Class to calculate the convex hull perimeter of a blob +class BlobGetHullPerimeter : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetHullPerimeter"; + } +}; + +//! Class to calculate the convex hull area of a blob +class BlobGetHullArea : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetHullArea"; + } +}; + +//! Class to calculate the minimum x on the minimum y +class BlobGetMinXatMinY : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetMinXatMinY"; + } +}; + +//! Class to calculate the minimum y on the maximum x +class BlobGetMinYatMaxX : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetMinYatMaxX"; + } +}; + +//! Class to calculate the maximum x on the maximum y +class BlobGetMaxXatMaxY : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetMaxXatMaxY"; + } +}; + +//! Class to calculate the maximum y on the minimum y +class BlobGetMaxYatMinX : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetMaxYatMinX"; + } +}; + +//! Class to get the minimum x +class BlobGetMinX : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.minX(); + } + const char *name() + { + return "BlobGetMinX"; + } +}; + +//! Class to get the maximum x +class BlobGetMaxX : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.maxX(); + } + const char *name() + { + return "BlobGetMaxX"; + } +}; + +//! Class to get the minimum y +class BlobGetMinY : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.minY(); + } + const char *name() + { + return "BlobGetMinY"; + } +}; + +//! Class to get the maximum y +class BlobGetMaxY : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.maxY(); + } + const char *name() + { + return "BlobGetMaxY"; + } +}; + + +//! Class to calculate the elongation of the blob +class BlobGetElongation : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetElongation"; + } +}; + +//! Class to calculate the roughness of the blob +class BlobGetRoughness : public OperatorBlob +{ +public: + double operator()(Blob& blob); + const char *name() + { + return "BlobGetRoughness"; + } +}; + +//! Class to calculate the euclidean distance between the center of a blob and a given point +class BlobGetDistanceFromPoint : public OperatorBlob +{ +public: + //! Standard constructor (distance to point 0,0) + BlobGetDistanceFromPoint() + { + m_x = m_y = 0.0; + } + //! Constructor (distance to point x,y) + BlobGetDistanceFromPoint(const double x, const double y) + { + m_x = x; + m_y = y; + } + + double operator()(Blob& blob); + const char *name() + { + return "BlobGetDistanceFromPoint"; + } + +private: + // coordinates of the point where we want to calculate the distance + double m_x, m_y; +}; + +//! Class to get the number of external pixels of a blob +class BlobGetExternalPerimeter : public OperatorBlob +{ +public: + BlobGetExternalPerimeter() + { + m_mask = NULL; + m_xBorder = true; + m_yBorder = true; + } + BlobGetExternalPerimeter(IplImage* mask, bool xBorder = true, bool yBorder = true) + { + m_mask = mask; + m_xBorder = xBorder; + m_yBorder = yBorder; + } + double operator()(Blob& blob) + { + return blob.externalPerimeter(m_mask, m_xBorder, m_yBorder); + } + const char *name() + { + return "BlobGetExternalPerimeter"; + } +private: + IplImage* m_mask; + bool m_xBorder, m_yBorder; +}; + +//! Class to calculate the ratio between the perimeter and number of external pixels +//! values close to 0 indicate that most of the blob is internal +//! values close to 1 indicate that most of the blob is external +//! Class to calculate the ratio between the perimeter and the number of external pixels +class BlobGetExternalPerimeterRatio : public OperatorBlob +{ +public: + BlobGetExternalPerimeterRatio() + { + m_mask = NULL; + m_xBorder = false; + m_yBorder = false; + } + BlobGetExternalPerimeterRatio(IplImage* mask, bool xBorder = true, bool yBorder = true) + { + m_mask = mask; + m_xBorder = xBorder; + m_yBorder = yBorder; + } + double operator()(Blob& blob) + { + if (blob.perimeter() != 0) { + return blob.externalPerimeter(m_mask, m_xBorder, m_yBorder) / blob.perimeter(); + } + else { + return blob.externalPerimeter(m_mask, m_xBorder, m_yBorder); + } + } + const char *name() + { + return "BlobGetExternalPerimeterRatio"; + } +private: + IplImage* m_mask; + bool m_xBorder, m_yBorder; +}; + +//! Class to calculate the ratio between the convex perimeter and the number of external pixels +//! values close to 0 indicate that most of the blob is internal blob +//! values close to 1 indicate that most of the blob is external +//! Class to calculate the ratio between the perimeter and the number of external pixels +class BlobGetExternalHullPerimeterRatio : public OperatorBlob +{ +public: + BlobGetExternalHullPerimeterRatio() + { + m_mask = NULL; + m_xBorder = false; + m_yBorder = false; + } + BlobGetExternalHullPerimeterRatio(IplImage* mask, bool xBorder = true, bool yBorder = true) + { + m_mask = mask; + m_xBorder = xBorder; + m_yBorder = yBorder; + } + double operator()(Blob& blob) + { + BlobGetHullPerimeter getHullPerimeter; + double hullPerimeter; + + if ((hullPerimeter = getHullPerimeter(blob)) != 0) { + return blob.externalPerimeter(m_mask, m_xBorder, m_yBorder) / hullPerimeter; + } + else { + return blob.externalPerimeter(m_mask, m_xBorder, m_yBorder); + } + } + const char *name() + { + return "BlobGetExternalHullPerimeterRatio"; + } +private: + IplImage* m_mask; + bool m_xBorder, m_yBorder; + +}; + +//! Class to calculate the center in the X direction +class BlobGetXCenter : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.minX() + ((blob.maxX() - blob.minX()) / 2.0); + } + const char *name() + { + return "BlobGetXCenter"; + } +}; + +//! Class to calculate the center in the Y direction +class BlobGetYCenter : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + return blob.minY() + ((blob.maxY() - blob.minY()) / 2.0); + } + const char *name() + { + return "BlobGetYCenter"; + } +}; + +//! Class to calculate the length of the major axis of the ellipse that fits the blob edges +class BlobGetMajorAxisLength : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + cv::RotatedRect elipse = blob.getEllipse(); + + return elipse.size.width; + } + const char *name() + { + return "BlobGetMajorAxisLength"; + } +}; + +//! Class to calculate the ratio between the area of the ellipse and that of the spot +class BlobGetAreaElipseRatio : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + if (blob.area() == 0.0) { return 0.0; } + + cv::RotatedRect elipse = blob.getEllipse(); + + double ratioAreaElipseAreaTaca = ((elipse.size.width/2.0) * (elipse.size.height/2.0) * CV_PI) / blob.area(); + + return ratioAreaElipseAreaTaca; + } + const char *name() + { + return "BlobGetAreaElipseRatio"; + } +}; + +//! Class to calculate the length of the minor axis of the ellipse that fits the blob edges +class BlobGetMinorAxisLength : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + cv::RotatedRect elipse = blob.getEllipse(); + + return elipse.size.height; + } + const char *name() + { + return "BlobGetMinorAxisLength"; + } +}; + +//! Class to calculate the orientation of the ellipse that fits the blob edges in radians +class BlobGetOrientation : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + cv::RotatedRect elipse = blob.getEllipse(); + /* + if (elipse.angle > 180.0) + return ((elipse.angle - 180.0)* DEGREE2RAD); + else + return (elipse.angle * DEGREE2RAD); +*/ + return elipse.angle; + } + const char *name() + { + return "BlobGetOrientation"; + } +}; + +//! Class to calculate the cosinus of the orientation of the ellipse that fits the blob edges +class BlobGetOrientationCos : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + BlobGetOrientation getOrientation; + return fabs(cos(getOrientation(blob)*DEGREE2RAD)); + } + const char *name() + { + return "BlobGetOrientationCos"; + } +}; + + +//! Class to calculate the ratio between both axes of the ellipse +class BlobGetAxisRatio : public OperatorBlob +{ +public: + double operator()(Blob& blob) + { + double major,minor; + BlobGetMajorAxisLength getMajor; + BlobGetMinorAxisLength getMinor; + + major = getMajor(blob); + minor = getMinor(blob); + + if (major != 0) { + return minor / major; + } + else { + return 0; + } + } + const char *name() + { + return "BlobGetAxisRatio"; + } +}; + +//! Class to calculate whether a point is inside a blob +class BlobGetXYInside : public OperatorBlob +{ +public: + //! Standard constructor + BlobGetXYInside() + { + m_p.x = 0; + m_p.y = 0; + } + //! Constructor: sets the point + BlobGetXYInside(cv::Point2f p) + { + m_p = p; + }; + double operator()(Blob& blob); + const char *name() + { + return "BlobGetXYInside"; + } + +private: + //! point to be considered + cv::Point2f m_p; +}; + +#endif //!BLOB_OPERATORS_H_INCLUDED diff --git a/examples/contour/blob/ComponentLabeling.cpp b/examples/contour/blob/ComponentLabeling.cpp new file mode 100644 index 00000000..21b34fe3 --- /dev/null +++ b/examples/contour/blob/ComponentLabeling.cpp @@ -0,0 +1,538 @@ +#include "ComponentLabeling.h" + +CompLabeler::CompLabeler(cv::Mat& binImage, BlobContour** lab, cv::Point start, cv::Point end) + : m_binaryImage(binImage) + , m_startPoint(start) + , m_endPoint(end) +{ + m_parent = NULL; + m_labels = lab; + m_r = 0; + m_c = 0; + m_dir = 0; +} + +CompLabeler::~CompLabeler() +{ + +} + +void CompLabeler::reset() +{ + m_blobs.clear(); +} + +void CompLabeler::label() +{ + m_ptrDataBinary = m_binaryImage.data; + m_ptrDataLabels = m_labels; + m_currentBlob = NULL; + + m_h = m_binaryImage.size().height; + m_w = m_binaryImage.size().width; + BlobContour* label = NULL; + for (m_r = m_startPoint.y; m_r < m_endPoint.y; ++m_r) { + //First col + m_pos = m_r * m_w; + m_c = m_startPoint.x; + if (m_ptrDataBinary[m_pos]) { + label = m_ptrDataLabels[m_pos]; + if (label) { + m_currentBlob = label->m_parent; + } + //Else if so to not check for label==NULL + else if (m_ptrDataBinary[m_pos] /*&& ptrDataLabels[pos]==NULL*/) { + m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); + m_blobs.push_back(m_currentBlob); + tracerExt(); + } + if (!m_ptrDataBinary[m_pos + 1] && !m_ptrDataLabels[m_pos + 1]) { + tracerInt(); + } + } + //Other cols + for (m_c = m_startPoint.x+1; m_c < m_endPoint.x-1; ++m_c) { + m_pos = m_r * m_w + m_c; + if (m_ptrDataBinary[m_pos]) { + label = m_ptrDataLabels[m_pos]; + if (label != 0) { + m_currentBlob = label->m_parent; + } + else if (!m_ptrDataBinary[m_pos - 1]) { + m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); + m_blobs.push_back(m_currentBlob); + tracerExt(); + } + if (!m_ptrDataBinary[m_pos+1] && m_ptrDataLabels[m_pos+1] == 0) { + tracerInt(); + } + } + } + m_pos = m_r * m_w + m_c; + //Last column + if (m_ptrDataBinary[m_pos]) { + label = m_ptrDataLabels[m_pos]; + if (label != 0) { + m_currentBlob = label->m_parent; + } + else if (!m_ptrDataBinary[m_pos - 1]) { + m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); + m_blobs.push_back(m_currentBlob); + tracerExt(); + } + } + } +} + +void* CompLabeler::threadLabeling(void* o) +{ + CompLabeler* obj = (CompLabeler*)o; + obj->label(); + return 0; +} + +void CompLabeler::tracerExt() +{ + //Dir: + //321 + //4-0 + //567 + //cout << "tracerExt\t" << c<<","<m_externalContour; +#ifdef DEBUG_COMPONENT_LABELLING + std::cout << "tracerExt\t" << m_c << ", "<< m_r << std::endl; + bool debugDraw = true; +#endif + int sR = m_r, sC = m_c; + int startPos = sR * m_w + sC; + m_dir = 6; +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 150; //Debug +#endif + getNextPointCCW(); + if (m_singlePixBlob) { + m_r = sR; + m_c = sC; + m_pos = m_r * m_w + m_c; + m_ptrDataLabels[m_pos] = m_currentContour; + return; + } + ChainCodeList* cont = &m_currentBlob->m_externalContour.m_contour[0]; + while (m_pos != startPos) { +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 150; +#endif + cont->push_back(m_dir); + m_ptrDataLabels[m_pos] = m_currentContour; + getNextPointCCW(); +#ifdef DEBUG_COMPONENT_LABELLING + if (debugDraw) { + cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); + cv::imshow("im", m_binaryImage); + std::stringstream ss; + ss << m_c << ", " << m_r; + cv::displayOverlay("im", ss.str()); + int k = cv::waitKey(); + if (k == ' ') { + debugDraw = false; + } + } +#endif + } + cont->push_back(m_dir); + m_ptrDataLabels[m_pos] = m_currentContour; + //For blobs in which the starting point must be crossed many times + for (int i = 0; i < 3; ++i) { + getNextPointCCW(); + if (m_ptrDataLabels[m_pos] == 0) { +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 150; +#endif + while (m_pos != startPos) { + //cout << r << "," << c << endl; + m_ptrDataLabels[m_pos] = m_currentContour; + cont->push_back(m_dir); + getNextPointCCW(); +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos]= 150; + if (debugDraw) { + cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); + cv::imshow("im", m_binaryImage); + int k = cv::waitKey(); + if (k == ' ') { + debugDraw = false; + } + } +#endif + } + cont->push_back(m_dir); + m_ptrDataLabels[m_pos] = m_currentContour; + } + else { + m_r = sR; + m_c = sC; + m_pos = m_r * m_w + m_c; + break; + } + } +} + +void CompLabeler::tracerInt(int startDir /*= 5*/) +{ + //Dir: + //321 + //4-0 + //567 + //cout << "tracerInt\t" << c<<","<m_parent = m_currentBlob; + ChainCodeList* cont = &m_currentContour->m_contour[0]; + m_dir = startDir; +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 50; +#endif + getNextPointCW(); + //uchar firstDir = m_dir; +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 100; +#endif + cont->push_back(m_dir); + m_ptrDataLabels[m_pos] = m_currentContour; + while (m_pos != startPos) { + // cout << r << "," << c << endl; + getNextPointCW(); + m_ptrDataLabels[m_pos] = m_currentContour; + cont->push_back(m_dir); +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 100; + if (debugDraw) { + cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); + cv::imshow("im", m_binaryImage); + std::stringstream ss; + ss << m_c << ", " << m_r; + cv::displayOverlay("im", ss.str()); + int k = cv::waitKey(); + if (k == ' ') { + debugDraw = false; + } + } +#endif + } + + //For internal contours in which the starting point must be crossed many times, like: + // ooooooooooooooo + // ooooos o + // ooooo o o + // ooooo o o + // ooooo o + // ooooooooooooooo + for (int i = 0; i < 3; ++i) { + getNextPointCW(); + //In tracerExt i check for label==0, beacuse there can not be situations in which a pixel belongs to 2 contours. + //This can happen with internal contorus, so I have to modify the condition. + if (m_ptrDataLabels[m_pos] != m_currentContour) { +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 100; +#endif + while (m_pos != startPos) { + m_ptrDataLabels[m_pos] = m_currentContour; + cont->push_back(m_dir); + getNextPointCW(); +#ifdef DEBUG_COMPONENT_LABELLING + m_ptrDataBinary[m_pos] = 100; + if (debugDraw) { + cv::namedWindow("im",CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); + cv::imshow("im", m_binaryImage); + int k = cv::waitKey(); + if (k == ' ') { + debugDraw = false; + } + } +#endif + } + cont->push_back(m_dir); + m_ptrDataLabels[m_pos] = m_currentContour; + } + else { + m_r = sR; + m_c = sC; + m_pos = m_r * m_w + m_c; + break; + } + } + + //If labeler has m_parent it means that I'm using more than 1 thread. + //Then I must check for collisions when adding internal contours to the same blob. + if (m_parent) { + std::lock_guard lock(m_parent->m_mutexBlob); + //m_parent->acquireMutex(); + m_currentBlob->m_internalContours.push_back(m_currentContour); + //m_parent->releaseMutex(); + } + else { + m_currentBlob->m_internalContours.push_back(m_currentContour); + } +} + +void CompLabeler::getNextPointCCW() +{ + //Dir: + //321 + //4-0 + //567 + + m_dir = (m_dir + 6) % 8; + int i = 0; + for (; i < 8; i++, m_dir = (m_dir + 1) % 8) { + m_tempR = m_r + m_freemanR[m_dir]; + m_tempC = m_c + m_freemanC[m_dir]; + if (!(m_tempR < 0 || m_tempR >= m_h || m_tempC < 0 || m_tempC >= m_w)) { + m_pos = m_tempR * m_w + m_tempC; + if (m_ptrDataBinary[m_pos]) { + m_r = m_tempR; + m_c = m_tempC; + break; + } + m_ptrDataLabels[m_pos] = m_currentContour; + } + } + m_singlePixBlob = i == 8; + m_pos = m_r * m_w + m_c; + + /* + // I tried to pre-create all the various direction vectors in order to avoid checking too many times for the boundary of the image. + // However, there was no gain in performance. + //It's useless (in terms of performance) to create the direction vectors according to all cases. + 8 directions + + cases: + 0-r=0 + 1-r=0,c=0 + 2-r=0,c=w + 3-r=h + 4-r=h,c=0 + 5-r=h,c=w + 6-c=0 + 7-c=w + 8-otherwise + total:9 + */ + + //static uchar directions[8][9][8] = {{{0,4,5,6,7,0,0,0},{0,6,7,0,0,0,0,0},{5,6,7,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{0,1,2,6,7,0,0,0},{2,3,4,5,6,0,0,0},{0,1,2,3,4,5,6,7}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{1,2,3,4,0,0,0,0},{1,2,0,0,0,0,0,0},{2,3,4,0,0,0,0,0},{1,2,6,7,0,0,0,0},{2,3,4,5,6,0,0,0},{1,2,3,4,5,6,7,0}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{2,3,4,0,1,0,0,0},{2,0,1,0,0,0,0,0},{2,3,4,0,0,0,0,0},{2,6,7,0,1,0,0,0},{2,3,4,5,6,0,0,0},{2,3,4,5,6,7,0,1}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{3,4,0,1,2,0,0,0},{0,1,2,0,0,0,0,0},{3,4,2,0,0,0,0,0},{6,7,0,1,2,0,0,0},{3,4,5,6,2,0,0,0},{3,4,5,6,7,0,1,2}},{{4,5,6,7,0,4,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{4,0,1,2,3,0,0,0},{0,1,2,0,0,0,0,0},{4,2,3,0,0,0,0,0},{6,7,0,1,2,0,0,0},{4,5,6,2,3,0,0,0},{4,5,6,7,0,1,2,3}},{{5,6,7,0,4,5,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{6,7,0,1,2,0,0,0},{5,6,2,3,4,0,0,0},{5,6,7,0,1,2,3,4}},{{6,7,0,4,5,6,0,0},{6,7,0,0,0,0,0,0},{6,7,5,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{6,7,0,1,2,0,0,0},{6,2,3,4,5,0,0,0},{6,7,0,1,2,3,4,5}},{{7,0,4,5,6,7,0,0},{7,0,6,0,0,0,0,0},{7,5,6,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{7,0,1,2,6,0,0,0},{2,3,4,5,6,0,0,0},{7,0,1,2,3,4,5,6}}}; + //static uchar nDirs[8][9] = {{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8}}; + //static uchar* dirVec; + //int p =-1; + //if (r== 0) { + // if (c= =0) + // p = 1; + // else if (c == (w-1)) + // p = 2; + // else + // p = 0; + //} + //else if (r== (h-1)) { + // if (c == 0) + // p = 4; + // else if (c == (w-1)) + // p = 5; + // else + // p = 3; + //} + //else if (c == 0) + // p = 6; + //else if (c == (w-1)) + // p = 7; + //else + // p = 8; + // + //dirVec = directions[dir][p]; + //int i = 0; + //int d = dir; + //for (i; i < nDirs[d][p]; ++i) { + // tempR = r + freemanR[dirVec[i]]; + // tempC = c + freemanC[dirVec[i]]; + // pos = tempR*w +tempC; + // if (ptrDataBinary[pos]) { + // r = tempR; + // c = tempC; + // dir = dirVec[i]; + // break; + // } + // ptrDataLabels[pos] = (int)currentBlob; + //} + //singlePixBlob = i == nDirs[d][p]; + //pos = r*w+c; + +} + +void CompLabeler::getNextPointCW() +{ + //Dir: + //321 + //4-0 + //567 + m_dir = (m_dir + 2) % 8; + for (int i = 0; i < 8; ++i) { + m_tempR = m_r + m_freemanR[m_dir]; + m_tempC = m_c + m_freemanC[m_dir]; + if (!(m_tempR < 0 || m_tempR >= m_h || m_tempC < 0 || m_tempC >= m_w)) { + m_pos = m_tempR * m_w + m_tempC; + if (m_ptrDataBinary[m_pos]) { + m_r = m_tempR; + m_c = m_tempC; + break; + } + m_ptrDataLabels[m_pos] = m_currentContour; + } + m_dir--; + m_dir = m_dir % 8; //To cycle through values 7->0 + } + m_pos = m_r * m_w + m_c; +} + +int CompLabeler::m_freemanC[8] = {1, 1, 0, -1, -1, -1, 0, 1}; + +int CompLabeler::m_freemanR[8] = {0, -1, -1, -1, 0, 1, 1, 1}; + +void CompLabelerGroup::doLabeling(BlobVector& blobs) +{ + LabelID label = 0; + if (m_numThreads > 1) { + //Preliminary step in order to pre-compute all the blobs crossing the border + for (int i = 1; i < m_numThreads; i++) { + cv::Point offset(m_img.size().width, 1); + CompLabeler lbl(m_img, m_labels, m_labelers[i]->m_startPoint, m_labelers[i]->m_startPoint + offset); + lbl.m_parent = this; + lbl.label(); + //cout << "Single pass\t" << lbl.blobs.size()<setID(label); + label++; + blobs.push_back(lbl.m_blobs[i]); + } + } + std::vector ths; + for (int i = 0; i < m_numThreads; i++) { + //pthread_create(&tIds[i], NULL, CompLabeler::thread_Labeling, labelers[i]); + ths.emplace_back(std::thread([label = m_labelers[i]]() { + CompLabeler::threadLabeling(label); + })); + } + for (int i = 0; i < m_numThreads; i++) { + //pthread_join(tIds[i], 0); + if (ths[i].joinable()) { + ths[i].join(); + } + } + } + else { + m_labelers[0]->label(); + } + + for (int i = 0; i < m_numThreads; ++i) { + //cout << "MT pass " <blobs.size()<m_blobs.size(); ++j) { + m_labelers[i]->m_blobs[j]->setID(label); + label++; + blobs.push_back(m_labelers[i]->m_blobs[j]); + } + } +} + +CompLabelerGroup::CompLabelerGroup() +{ + //m_mutexBlob = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + m_labels = NULL; + m_labelers = NULL; + //m_tIds = NULL; +} + +CompLabelerGroup::~CompLabelerGroup() +{ + if (m_labelers) { + for (int i = 0; i < m_numThreads; ++i) { + delete m_labelers[i]; + } + delete []m_labelers; + } + //if (m_tIds) { + // delete [] m_tIds; + //} + if (m_labels) { + delete [] m_labels; + } +} + +void CompLabelerGroup::set(int nThreads, const cv::Mat& binImg) +{ + this->m_numThreads = nThreads; + if (binImg.isContinuous()) { + this->m_img = binImg; + } + else { + this->m_img = binImg.clone(); + } + //this->labels = Mat_::zeros(img.size()); + if (m_labels) { + delete [] m_labels; + } + int nPts = binImg.size().width * binImg.size().height; + m_labels = new BlobContour*[nPts]; + memset(m_labels, 0, nPts*sizeof(Blob*)); + + //if (m_tIds) { + // delete [] m_tIds; + //} + if (m_labelers) { + for (int i = 0; i < nThreads; ++i) { + delete m_labelers[i]; + } + delete [] m_labelers; + } + //m_tIds = new pthread_t[nThreads]; + m_labelers = new CompLabeler*[nThreads]; + cv::Size sz = binImg.size(); + //int numPx = sz.width*sz.width; + int i = 0; + for (; i < nThreads-1; ++i) { + int yStart = (int)((float)i / nThreads * sz.height); + int yEnd = (int)((float)(i + 1) / nThreads * sz.height); + cv::Point st(0, yStart); + cv::Point en(sz.width, yEnd); + m_labelers[i] = new CompLabeler(m_img, m_labels, st, en); + m_labelers[i]->m_parent = this; + } + cv::Point st(0, (int)((float)i / nThreads * sz.height)); + cv::Point en(sz.width, sz.height); + //st = Point(0,187); + m_labelers[i] = new CompLabeler(m_img, m_labels, st, en); + m_labelers[i]->m_parent = this; +} + +void CompLabelerGroup::reset() +{ + if (m_labels) { + delete [] m_labels; + m_labels = NULL; + } + for (int i = 0; i < m_numThreads; ++i) { + m_labelers[i]->reset(); + } +} + +void CompLabelerGroup::acquireMutex() +{ + //pthread_mutex_lock(&m_mutexBlob); +} + +void CompLabelerGroup::releaseMutex() +{ + //pthread_mutex_unlock(&m_mutexBlob); +} + diff --git a/examples/contour/blob/ComponentLabeling.h b/examples/contour/blob/ComponentLabeling.h new file mode 100644 index 00000000..8987b834 --- /dev/null +++ b/examples/contour/blob/ComponentLabeling.h @@ -0,0 +1,120 @@ +#if !defined(_COMPONENT_LABELING_H_INCLUDED) +#define _COMPONENT_LABELING_H_INCLUDED + +//#define DEBUG_COMPONENT_LABELLING + +#include +#include +#include "BlobContour.h" +#include "defines.h" +#include "blob.h" +#include "opencv2/opencv.hpp" +//#include + +//! forward decl. for "parent" field of CompLabeler. +class CompLabelerGroup; + +//! implementation of F.Chang algorithm (Luca Nardelli) +class CompLabeler { +public: + //! Double pointer so to pass the array of blob pointers + CompLabeler(cv::Mat& binImage, BlobContour** lab, cv::Point start = cv::Point(-1, -1), cv::Point end = cv::Point(-1, -1)); + ~CompLabeler(); + + //! Do labeling in region defined by startpoint and endpoint, populating blobs + void label(); + + //! Resets internal buffers + void reset(); + + //! External contours tracer + void tracerExt(); + + //! Internal contours tracer + void tracerInt(int startDir = 5); + + //! Counter clockwise + void getNextPointCCW(); + + //! Clockwise + void getNextPointCW(); + + //! Thread function + static void* threadLabeling(void* o); + +public: + BlobVector m_blobs; + + cv::Mat m_binaryImage; + + cv::Point m_startPoint, m_endPoint; + +private: + friend class CompLabelerGroup; + + //! CompLabelerGroup parent, in order to get access to mutexes. + CompLabelerGroup* m_parent; + + BlobContour** m_labels; + + //! currentLabel + int m_currentLabel; + + int m_r, m_c, m_pos; + + //! Width& Height of image + int m_w, m_h; + + uchar m_dir; + + static int m_freemanR[8], m_freemanC[8]; + + bool m_singlePixBlob; + + uchar* m_ptrDataBinary; + + BlobContour** m_ptrDataLabels; + + int m_tempR, m_tempC; + + Blob* m_currentBlob; + + BlobContour* m_currentContour; +}; + +class CompLabelerGroup { +public: + CompLabelerGroup(); + + ~CompLabelerGroup(); + + void doLabeling(BlobVector& blobs); + + void set(int numThreads, const cv::Mat& img); + + void reset(); + +private: + void acquireMutex(); + + void releaseMutex(); + +public: + cv::Mat m_img; + +private: + friend class CompLabeler; + + CompLabeler** m_labelers; + + int m_numThreads; + + std::mutex m_mutexBlob; + + //pthread_t* m_tIds; + //pthread_mutex_t m_mutexBlob; + + BlobContour** m_labels; +}; + +#endif //!_COMPONENT_LABELING_H_INCLUDED diff --git a/examples/contour/blob/blob.cpp b/examples/contour/blob/blob.cpp new file mode 100644 index 00000000..3eb74a25 --- /dev/null +++ b/examples/contour/blob/blob.cpp @@ -0,0 +1,1044 @@ +/************************************************************************ + Blob.cpp + +FUNCTIONALITY: Implementation of the Blob class and some helper classes to perform + some calculations on it +AUTHOR: Inspecta S.L. +MODIFICATIONS (Modification, Author, Date): + +**************************************************************************/ + + +#include "blob.h" +#include +#include "opencv2/core/types_c.h" +#include "opencv2/imgproc/imgproc_c.h" + +Blob::Blob() +{ + m_area = m_perimeter = -1; + m_externalPerimeter = m_meanGray = m_stdDevGray = -1; + m_boundingBox.width = -1; + m_ellipse.size.width = -1; + m_id = -1; + m_toBeDeleted = 0; + m_deleteRequestOwnerBlob = NULL; + m_isJoined = false; + m_startPassed = false; +} + +Blob::Blob(LabelID id, const cv::Point& startPoint, const cv::Size& originalImageSize) : m_externalContour(startPoint, originalImageSize) +{ + m_externalContour.m_parent = this; + m_id = id; + m_area = m_perimeter = -1; + m_externalPerimeter = m_meanGray = m_stdDevGray = -1; + m_boundingBox.width = -1; + m_ellipse.size.width = -1; + m_originalImageSize = originalImageSize; + m_toBeDeleted = 0; + m_deleteRequestOwnerBlob = NULL; + m_isJoined = false; + m_startPassed = false; +} + +//! Copy constructor +Blob::Blob(const Blob& src) +{ + *this = src; +} + +Blob::Blob(const Blob *src) +{ + if (src != NULL) { + *this = *src; + } +} + +Blob& Blob::operator=(const Blob& src) +{ + if (this != &src) { + m_id = src.m_id; + m_area = src.m_area; + m_perimeter = src.m_perimeter; + m_externalPerimeter = src.m_externalPerimeter; + m_meanGray = src.m_meanGray; + m_stdDevGray = src.m_stdDevGray; + m_boundingBox = src.m_boundingBox; + m_ellipse = src.m_ellipse; + m_originalImageSize = src.m_originalImageSize; + m_toBeDeleted = src.m_toBeDeleted; + m_deleteRequestOwnerBlob = src.m_deleteRequestOwnerBlob; + m_startPassed = false; + //! clear all current blob contours + clearContours(); + m_externalContour = src.m_externalContour; + + //! copy all internal contours + if (src.m_internalContours.size() != 0) { + //m_internalContours = t_contourList(src.m_internalContours.size()); + BlobContourList::const_iterator itSrc,enSrc; + BlobContourList::iterator it; + + itSrc = src.m_internalContours.begin(); + enSrc = src.m_internalContours.end(); + + while (itSrc != enSrc) { + m_internalContours.push_back(new BlobContour(*itSrc)); + itSrc++; + } + } + } + + m_isJoined = src.m_isJoined; + m_joinedBlobs.clear(); + if (m_isJoined) { + std::list::const_iterator it,en = src.m_joinedBlobs.end(); + for (it = src.m_joinedBlobs.begin(); it != en; ++it) { + m_joinedBlobs.push_back(new Blob(*it)); + } + } + + return *this; +} + +Blob::~Blob() +{ + if (m_isJoined) { + std::list::iterator it,en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + delete (*it); + } + } + + clearContours(); +} + +void Blob::clearContours() +{ + BlobContourList::iterator it = m_internalContours.begin(), en = m_internalContours.end(); + for (; it != en; ++it) { + delete (*it); + } + m_internalContours.clear(); + m_externalContour.reset(); +} + +void Blob::addInternalContour(const BlobContour &newContour) +{ + m_internalContours.push_back(new BlobContour(newContour)); +} + +//! Shows if the blob has associated information +bool Blob::isEmpty() +{ + return getExternalContour()->m_contour.size() == 0; +} + +/** +- FUNCTIONS: Area +- FUNCTIONALITY: Get blob area, ie. external contour area minus internal contours area +- PARAMETERS: - +- RESULT: - +- RESTRICTIONS: - +- AUTHOR: trees +- DATE OF CREATION: 30/04/2008 +- MODIFICATION: Date. Author. Description. +*/ +double Blob::area(AreaMode areaCompMode) +{ + double area=0; + switch (areaCompMode) + { + case GREEN: + { + BlobContourList::iterator itContour; + if (m_isJoined) { + std::list::iterator it, en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + area += (*it)->area(); + } + } + else { + area = m_externalContour.getArea(); + itContour = m_internalContours.begin(); + while (itContour != m_internalContours.end()) { + if (*itContour) { + area -= (*itContour)->getArea(); + } + itContour++; + } + } + break; + } + case PIXELWISE: + { + cv::Rect bbox = getBoundingBox(); + cv::Mat image = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1); + fillBlob(image, cv::Scalar(255), -bbox.x, -bbox.y, true); + area = countNonZero(image); + break; + } + } + + return area; +} + +/** +- FUNCTIONS: Perimeter +- FUNCTIONALITY: Get perimeter blob, ie. sum of the length of all the contours +- PARAMETERS: - +- RESULT: - +- RESTRICTIONS: - +- AUTHOR: trees +- DATE OF CREATION: 30/04/2008 +- MODIFICATION: Date. Author. Description. +*/ +double Blob::perimeter() +{ + double perimeter = 0; + BlobContourList::iterator itContour; + + if (m_isJoined) { + std::list::iterator it, en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + perimeter += (*it)->perimeter(); + } + } + else { + perimeter = m_externalContour.getPerimeter(); + itContour = m_internalContours.begin(); + while (itContour != m_internalContours.end()) { + if (*itContour) { + perimeter += (*itContour)->getPerimeter(); + } + itContour++; + } + } + + return perimeter; +} + +/** +- FUNCTIONS: exterior +- FUNCIONALITAT: Return true for external blobs +- PARAMETERS: + - xBorder: true to consider blobs touching horizontal borders as external + - yBorder: true to consider blobs touching vertical borders as external +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATE OF CREATION: 2008/05/06 +- MODIFICATION: Data. Autor. Description. +*/ +int Blob::exterior(IplImage *mask, bool xBorder /* = true */, bool yBorder /* = true */) +{ + int result = 0; + if (externalPerimeter(mask, xBorder, yBorder) > 0) + { + result = 1; + } + + return result; +} + +int Blob::exterior(const cv::Mat& mask, bool xBorder /* = true */, bool yBorder /* = true */) +{ + IplImage temp = cvIplImage(mask); + return exterior(&temp, xBorder, yBorder); +} + +/** +- FUNCTIONS: externalPerimeter +- FUNCIONALITAT: Get external perimeter (perimeter touching image borders) +- PARAMETERS: + - maskImage: if != NULL, counts maskImage black pixels as external pixels and contour points touching them are counted as external contour points. + - xBorder: true to consider blobs touching horizontal borders as external + - yBorder: true to consider blobs touching vertical borders as external +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATE OF CREATION: 2008/05/05 +- MODIFICATION: Data. Autor. Description. +- NOTA: If BlobContour::getContourPoints aproximates contours with a method different that NONE, this function will not give correct results +*/ +double Blob::externalPerimeter(IplImage *maskImage, bool xBorder /* = true */, bool yBorder /* = true */) +{ + PointList externalContour, externalPoints; + cv::Point actualPoint, previousPoint; + bool find = false; + //int i,j; + int delta = 0; + + //! it is calculated? + /*if (m_externalPerimeter != -1) + { + return m_externalPerimeter; + }*/ + + + if (m_isJoined) { + //! it an en are always different at first assignment (if m_isJoined is true I have at least one joined blob). + std::list::iterator it = m_joinedBlobs.begin(),en = m_joinedBlobs.end(); + m_externalPerimeter = (*it)->externalPerimeter(maskImage, xBorder, yBorder); + it++; + for (; it != en; ++it) { + m_externalPerimeter += (*it)->externalPerimeter(maskImage, xBorder, yBorder); + } + return m_externalPerimeter; + } + + + //! get contour pixels + externalContour = m_externalContour.getContourPoints(); + m_externalPerimeter = 0; + + //! there are contour pixels? + if (externalContour.size() == 0) { + return m_externalPerimeter; + } + + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + + previousPoint.x = -1; + + //! which contour pixels touch border? + for (; it != en; ++it) { + actualPoint = *(it); + find = false; + + //! pixel is touching border? + if (xBorder & ((actualPoint.x == 0) || (actualPoint.x == m_originalImageSize.width - 1)) || + yBorder & ((actualPoint.y == 0) || (actualPoint.y == m_originalImageSize.height - 1))) { + find = true; + } + else { + if (maskImage != NULL) { + //! verify if some of 8-connected neighbours is black in mask + char *pMask; + + pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y - 1) * maskImage->widthStep); + + for (int i = 0; i < 3; i++, pMask++) { + if (*pMask == 0 && !find) { + find = true; + break; + } + } + + if (!find) { + pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y) * maskImage->widthStep); + + for (int i = 0; i < 3; i++, pMask++) { + if (*pMask == 0 && !find) { + find = true; + break; + } + } + } + + if (!find) { + pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y + 1) * maskImage->widthStep); + + for (int i = 0; i < 3; i++, pMask++) { + if (*pMask == 0 && !find) { + find = true; + break; + } + } + } + } + } + + if (find) { + if (previousPoint.x > 0) { + delta = abs(previousPoint.x - actualPoint.x) + abs(previousPoint.y - actualPoint.y); + } + + //! calculate separately each external contour segment + if (delta > 2) { + m_externalPerimeter += arcLength(externalPoints, false); + + externalPoints.clear(); + delta = 0; + previousPoint.x = -1; + } + externalPoints.push_back(actualPoint); + previousPoint = actualPoint; + } + + } + + if (externalPoints.size() != 0) { + m_externalPerimeter += arcLength(externalPoints, false); + } + //! divide by two because external points have one side inside the blob and the other outside + //! Perimeter of external points counts both sides, so it must be divided + m_externalPerimeter /= 2.0; + return m_externalPerimeter; +} + +double Blob::externalPerimeter(const cv::Mat& maskImage, bool xBorder /* = true */, bool yBorder /* = true */) { + if (!maskImage.data) { + return externalPerimeter(NULL, xBorder /* = true */, yBorder /* = true */); + } + else { + IplImage temp = cvIplImage(maskImage); + return externalPerimeter(&temp, xBorder /* = true */, yBorder /* = true */); + } +} + +//! Compute blob's moment (p,q up to MAX_CALCULATED_MOMENTS) +double Blob::moment(int p, int q, bool intContours /*= true*/) +{ + double moment=0; + BlobContourList::iterator itContour; + + if (m_isJoined) { + std::list::iterator it,en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + moment += (*it)->moment(p, q); + } + } + else { + moment = m_externalContour.getMoment(p, q); + if (intContours) { + itContour = m_internalContours.begin(); + while (itContour != m_internalContours.end()) { + moment -= (*itContour)->getMoment(p, q); + itContour++; + } + } + } + return moment; +} + +/** +- FUNCTIONS: mean +- FUNCIONALITAT: Get blob mean color in input image +- PARAMETERS: + - image: image from gray color are extracted +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATE OF CREATION: 2008/05/06 +- MODIFICATION: Data. Autor. Description. +*/ +double Blob::mean(IplImage *image) +{ + //! Create a mask with same size as blob bounding box + IplImage *mask; + CvScalar mean, std; + cv::Point offset; + + getBoundingBox(); + + if (m_boundingBox.height == 0 ||m_boundingBox.width == 0 || !CV_IS_IMAGE(image)) { + m_meanGray = 0; + return m_meanGray; + } + + //! apply ROI and mask to input image to compute mean gray and standard deviation + mask = cvCreateImage(cvSize(m_boundingBox.width, m_boundingBox.height), IPL_DEPTH_8U, 1); + cvSetZero(mask); + + offset.x = -m_boundingBox.x; + offset.y = -m_boundingBox.y; + + cv::Mat mask_mat = cv::cvarrToMat(mask); + + //! If joined + if (m_isJoined) { + std::list::iterator it,en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + //cvDrawContours(mask, (*it)->m_externalContour.getContourPoints(), CV_RGB(255, 255, 255), CV_RGB(255, 255, 255),0, CV_FILLED, 8, offset); + //vector conts; + //conts.push_back((*it)->m_externalContour.getContourPoints()); + drawContours(mask_mat,(*it)->m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + BlobContourList::iterator itint = (*it)->m_internalContours.begin(); + while(itint != (*it)->m_internalContours.end()) { + //cvDrawContours(mask, (*itint).getContourPoints(), CV_RGB(0, 0, 0), CV_RGB(0, 0, 0),0, CV_FILLED, 8, offset); + drawContours(mask_mat, (*itint)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + itint++; + } + } + } + else { + //! draw contours on mask + //cvDrawContours(mask, m_externalContour.getContourPoints(), CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 0, CV_FILLED, 8, offset); + //vector conts; + //conts.push_back(m_externalContour.getContourPoints()); + drawContours(mask_mat, m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + //! draw internal contours + BlobContourList::iterator it = m_internalContours.begin(); + while(it != m_internalContours.end()) + { + //cvDrawContours(mask, (*it).getContourPoints(), CV_RGB(0, 0, 0), CV_RGB(0, 0, 0),0, CV_FILLED, 8, offset); + drawContours(mask_mat, (*it)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + it++; + } + } + + cvSetImageROI(image, cvRect(m_boundingBox)); + cvAvgSdv(image, &mean, &std, mask); + + m_meanGray = mean.val[0]; + m_stdDevGray = std.val[0]; + + cvReleaseImage(&mask); + cvResetImageROI(image); + + return m_meanGray; +} + +double Blob::mean(const cv::Mat& image) { + IplImage temp = cvIplImage(image); + return mean(&temp); +} + +double Blob::stdDev(IplImage *image) { + //! call mean calculation (where also standard deviation is calculated) + mean(image); + + return m_stdDevGray; +} + +double Blob::stdDev(const cv::Mat& image) { + IplImage temp = cvIplImage(image); + return stdDev(&temp); +} + +//void Blob::meanStdDev(Mat image, double *mean, double *stddev) +//{ +// IplImage temp = cvIplImage(image); +// mean(&temp); +// *mean = m_meanGray; +// *stddev = m_stdDevGray; +// return; +//} + +void Blob::meanStdDev(const cv::Mat& image, cv::Scalar& mean, cv::Scalar& stddev) +{ + getBoundingBox(); + //! Create a mask with same size as blob bounding box and set it to 0 + cv::Mat mask_mat = cv::Mat(m_boundingBox.height, m_boundingBox.width, CV_8UC1, cv::Scalar(0)); + cv::Point offset(-m_boundingBox.x, -m_boundingBox.y); + + //! If joined + if (m_isJoined) { + std::list::iterator it,en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin();it!=en;it++) { + drawContours(mask_mat, (*it)->m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + BlobContourList::iterator itint = (*it)->m_internalContours.begin(); + while (itint != (*it)->m_internalContours.end()) { + drawContours(mask_mat, (*itint)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + itint++; + } + } + } + else { + //! draw contours on mask + drawContours(mask_mat, m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + //! draw internal contours + BlobContourList::iterator it = m_internalContours.begin(); + while (it != m_internalContours.end()) { + drawContours(mask_mat, (*it)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset); + it++; + } + } + + cv::meanStdDev(image(m_boundingBox), mean, stddev, mask_mat); +} + +/** +- FUNCTIONS: getBoundingBox +- FUNCIONALITAT: Get bounding box (without rotation) of a blob +- PARAMETERS: + - +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATE OF CREATION: 2008/05/06 +- MODIFICATION: Data. Autor. Description. +*/ +cv::Rect Blob::getBoundingBox() +{ + //! it is calculated? + if (m_boundingBox.width != -1) { + return m_boundingBox; + } + + if (m_isJoined) { + cv::Rect bigRect; + bigRect.x = 1000000; + bigRect.y = 1000000; + bigRect.height = 0; + bigRect.width = 0; + std::list::iterator it, en = m_joinedBlobs.end(); + int maxX = 0, maxY = 0; + for (it = m_joinedBlobs.begin(); it != en; ++it) { + cv::Rect temp = (*it)->getBoundingBox(); + if (bigRect.x > temp.x) { + bigRect.x = temp.x; + } + if (bigRect.y > temp.y) { + bigRect.y = temp.y; + } + //! -1 in order to obtain the correct measure (I'm looking for the points) + if (maxX < temp.x + temp.width) { + maxX = temp.x + temp.width - 1; + } + if (maxY < temp.y + temp.height) { + maxY = temp.y + temp.height - 1; + } + } + //! +1 in order to obtain the correct measure + bigRect.width = maxX - bigRect.x + 1; + bigRect.height = maxY - bigRect.y + 1; + m_boundingBox = bigRect; + return m_boundingBox; + } + + PointList externalContour; + + //! get contour pixels + externalContour = m_externalContour.getContourPoints(); + + //! it is an empty blob? + m_boundingBox.x = 1000000; + m_boundingBox.y = 1000000; + m_boundingBox.width = 0; + m_boundingBox.height = 0; + + + PointList::iterator it = externalContour.begin(), en = externalContour.end(); + for (; it != en; ++it) { + cv::Point& actualPoint = *it; + + m_boundingBox.x = MIN(actualPoint.x, m_boundingBox.x); + m_boundingBox.y = MIN(actualPoint.y, m_boundingBox.y); + + m_boundingBox.width = MAX(actualPoint.x, m_boundingBox.width); + m_boundingBox.height = MAX(actualPoint.y, m_boundingBox.height); + } + + //! +1 in order to take into account single pixel blobs. + //! In this case, a single pixel has a bounding box like Rect(x,y,1,1), which is ok with opencv functions + m_boundingBox.width = m_boundingBox.width - m_boundingBox.x + 1; + m_boundingBox.height = m_boundingBox.height - m_boundingBox.y + 1; + + return m_boundingBox; +} + +/** +- FUNCTIONS: getEllipse +- FUNCIONALITAT: Calculates bounding ellipse of external contour points +- PARAMETERS: + - +- RESULTAT: + - +- RESTRICCIONS: + - +- AUTOR: rborras +- DATE OF CREATION: 2008/05/06 +- MODIFICATION: Data. Autor. Description. +- NOTA: Calculation is made using second order moment aproximation +*/ +cv::RotatedRect Blob::getEllipse() +{ + //! it is calculated? + if (m_ellipse.size.width != -1) { + return m_ellipse; + } + + double u00, u11, u01, u10, u20, u02, delta, num, den, temp; + + //! central moments calculation + u00 = moment(0,0); + + //! empty blob? + if (u00 <= 0) { + m_ellipse.size.width = 0; + m_ellipse.size.height = 0; + m_ellipse.center.x = 0; + m_ellipse.center.y = 0; + m_ellipse.angle = 0; + return m_ellipse; + } + u10 = moment(1, 0) / u00; + u01 = moment(0, 1) / u00; + + u11 = -(moment(1, 1) - moment(1, 0) * moment(0, 1) / u00) / u00; + u20 = (moment(2, 0) - moment(1, 0) * moment(1, 0) / u00) / u00; + u02 = (moment(0, 2) - moment(0, 1) * moment(0, 1) / u00) / u00; + + + //! elipse calculation + delta = sqrt(4*u11*u11 + (u20-u02)*(u20-u02)); + m_ellipse.center.x = (float)u10; + m_ellipse.center.y = (float)u01; + + temp = u20 + u02 + delta; + if (temp > 0) { + m_ellipse.size.width = (float)sqrt(2 * (u20 + u02 + delta)); + } + else { + m_ellipse.size.width = 0; + return m_ellipse; + } + + temp = u20 + u02 - delta; + if (temp > 0) { + m_ellipse.size.height = (float)sqrt(2 * (u20 + u02 - delta)); + } + else { + m_ellipse.size.height = 0; + return m_ellipse; + } + + //! elipse orientation + if (u20 > u02) { + num = u02 - u20 + sqrt((u02 - u20) * (u02 - u20) + 4 * u11 * u11); + den = 2*u11; + } + else { + num = 2*u11; + den = u20 - u02 + sqrt((u20 - u02) * (u20 - u02) + 4 * u11 * u11); + } + + if (num != 0 && den != 00) { + m_ellipse.angle = (float)(180.0 + (180.0 / CV_PI) * atan(num / den)); + } + else { + m_ellipse.angle = 0; + } + + return m_ellipse; +} + +/** +- FUNCTION: fillBlob +- FUNCTIONALITY: + - Fills the blob with a specified colour +- PARAMETERS: + - image: where to paint + - color: colour to paint the blob + - offset: point offset for drawing + - intContours: do not paint the internal holes (leave them transm_parent) + - srcImage: image from where to copy the internal holes contents +- RESULT: - modified input image +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: - sep/2013. Luca Nardelli. Added functionality to consider internal contours when filling the blob. +*/ +void Blob::fillBlob(IplImage* image, cv::Scalar color, int offsetX , int offsetY, bool intContours, const IplImage* srcImage) +{ + if (srcImage == NULL) { + fillBlob(cv::cvarrToMat(image), color, offsetX, offsetY, intContours, cv::Mat()); + } + else { + fillBlob(cv::cvarrToMat(image), color, offsetX, offsetY, intContours, cv::cvarrToMat(srcImage)); + } +} + +void Blob::fillBlob(const cv::Mat& image, cv::Scalar color, int offsetX, int offsetY, bool intContours, const cv::Mat& srcImage) { + CV_FUNCNAME("Blob::fillBlob"); + __CV_BEGIN__; + if (srcImage.data && intContours) CV_ASSERT(image.size() == srcImage.size() && image.type() == srcImage.type()); + { + cv::Rect bbox = getBoundingBox(); + cv::Point drawOffset(offsetX, offsetY); + cv::Size imSz = image.size(); + if (bbox.x + offsetX + bbox.width >= imSz.width) { + bbox.width = imSz.width - bbox.x - offsetX; + } + else if (bbox.x + offsetX < 0) { + bbox.x = -offsetX; + bbox.width = bbox.width + offsetX; + } + if (bbox.y+offsetY+bbox.height >= imSz.height) { + bbox.height = imSz.height - bbox.y - offsetY; + } + else if (bbox.y + offsetY < 0) { + bbox.y = -offsetY; + bbox.height = bbox.height + offsetY; + } + if (bbox.width <0 || bbox.height < 0) { + return; + } + if (bbox.width == 0) { + bbox.width++; + } + if (bbox.height == 0) { + bbox.height++; + } + if (m_isJoined) { + std::list::iterator itBlob = m_joinedBlobs.begin(), enBlob = m_joinedBlobs.end(); + for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) { + (*itBlob)->fillBlob(image, color, offsetX, offsetY, intContours, srcImage); + } + //if (intContours) { + // Point offset(-bbox.x, -bbox.y); + // Size sz(bbox.width, bbox.height); + // Mat temp(sz,image.type()); + // Mat mask(sz, CV_8UC1); + // mask.setTo(0); + // for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) { + // Blob *curBlob = *itBlob; + // BlobContourList::iterator it = curBlob->m_internalContours.begin(),en = curBlob->m_internalContours.end(); + // for (it; it != en; ++it) { + // drawContours(mask,(*it)->getContours(),-1,255,CV_FILLED,8,noArray(),2147483647,offset); + // } + // } + // srcImage(bbox).copyTo(temp,mask); + // for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) { + // drawContours(image,(*itBlob)->m_externalContour.getContours(),-1,color,CV_FILLED,8,noArray(),2147483647,drawOffset); + // } + // temp.copyTo(image(bbox+drawOffset),mask); + // for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) { + // Blob *curBlob = *itBlob; + // BlobContourList::const_iterator it = curBlob->m_internalContours.begin(),en = curBlob->m_internalContours.end(); + // for (it;it!=en;it++) { + // drawContours(image,(*it)->getContours(),-1,color,1,8,noArray(),2147483647,drawOffset); + // } + // } + //} + //else { + // for (itBlob = m_joinedBlobs.begin();itBlob!=enBlob;itBlob++) { + // drawContours(image,(*itBlob)->m_externalContour.getContours(),-1,color,CV_FILLED,8,noArray(),2147483647,drawOffset); + // } + //} + } + else { + if (intContours) { + cv::Point offset(-bbox.x, -bbox.y); + BlobContourList::iterator it = m_internalContours.begin(), en = m_internalContours.end(); + cv::Mat temp(bbox.height, bbox.width, image.type()); + drawContours(image, m_externalContour.getContours(), -1, color, CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset); + if (srcImage.data) { + cv::Mat mask(bbox.height, bbox.width, CV_8UC1); + mask.setTo(0); + for (; it != en; ++it) { + drawContours(mask, (*it)->getContours(), -1, 255, CV_FILLED, 8, cv::noArray(), 2147483647, offset); + } + srcImage(bbox).copyTo(temp,mask); + cv::Mat image_roi = image(bbox + drawOffset); + temp.copyTo(image_roi, mask); + } + else { + for (; it != en; ++it) { + drawContours(image, (*it)->getContours(), -1,CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset); + } + } + + for (it = m_internalContours.begin(); it != en; ++it) { + drawContours(image, (*it)->getContours(), -1, color, 1, 8, cv::noArray(), 2147483647, drawOffset); + } + } + else { + drawContours(image,m_externalContour.getContours(), -1, color, CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset); + } + } + } + __CV_END__; +} + +/** +- FUNCTION: getConvexHull +- FUNCTIONALITY: Calculates the convex hull polygon of the blob +- PARAMETERS: + - dst: where to store the result +- RESULT: + - true if no error ocurred +- RESTRICTIONS: +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: Date. Author. Description. +*/ +void Blob::getConvexHull(Contours& hull) +{ + hull.clear(); + PointList extCont; + hull.push_back(PointList()); + if (m_isJoined) { + int numPts = 0; + BlobList::iterator it, en = m_joinedBlobs.end(); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + numPts += (*it)->getExternalContour()->getContourPoints().size(); + } + hull[0].reserve(numPts); + extCont.reserve(numPts); + for (it = m_joinedBlobs.begin(); it != en; ++it) { + const PointList& pts = (*it)->getExternalContour()->getContourPoints(); + extCont.insert(extCont.end(), pts.begin(), pts.end()); + } + } + else { + extCont = m_externalContour.getContourPoints(); + } + cv::convexHull(extCont, hull[0], true, true); +} + +/** +- FUNCTION: joinBlob +- FUNCTIONALITY: Joins the 2 blobs, creating another blob which contains the 2 joined ones +- PARAMETERS: + - blob: blob to join with the calling one +- RESULT: + - Joined blob +- RESTRICTIONS: Only external contours are added +- AUTHOR: Ricard Borràs +- CREATION DATE: 25-05-2005. +- MODIFICATION: 08-2013, Luca Nardelli & Saverio Murgia, Created a working version of the join blob function +*/ +void Blob::joinBlob(Blob* blob) +{ + //! Check on m_storage in order to not add empty blobs. + if (!m_isJoined && !isEmpty()) { + this->m_joinedBlobs.push_back(new Blob(this)); + } + if (blob->m_isJoined) { + std::list::iterator it, en = blob->m_joinedBlobs.end(); + for (it = blob->m_joinedBlobs.begin(); it != en; ++it) { + this->m_joinedBlobs.push_back(new Blob(*it)); + } + } + else { + this->m_joinedBlobs.push_back(new Blob(blob)); + } + + this->m_isJoined = true; + this->m_boundingBox.width = -1; + this->m_externalPerimeter = -1; + this->m_meanGray = -1; +} + +void Blob::requestDeletion(Blob* blob) +{ + //! If the blob has already been reported for deletion, then I also report the blobs that have requested it to be deleted + while (blob->m_deleteRequestOwnerBlob != NULL) { + Blob *temp = blob; + blob->m_deleteRequestOwnerBlob->m_toBeDeleted = 1; + blob = blob->m_deleteRequestOwnerBlob; + temp->m_deleteRequestOwnerBlob = this; + } + blob->m_toBeDeleted = 1; + blob->m_deleteRequestOwnerBlob = this; +} + +int Blob::getNumJoinedBlobs() +{ + return m_joinedBlobs.size(); +} + +void Blob::shiftBlob(int x, int y) +{ + m_externalContour.shiftBlobContour(x,y); + BlobContourList::iterator it, en = m_internalContours.end(); + for (it = m_internalContours.begin(); it != en; ++it) { + (*it)->shiftBlobContour(x,y); + } + m_boundingBox.x += x; + m_boundingBox.y += y; +} + +cv::Point Blob::getCenter() +{ + return cv::Point((int)(getBoundingBox().x + getBoundingBox().width * 0.5), (int)(getBoundingBox().y + getBoundingBox().height * 0.5)); +} + +int Blob::overlappingPixels(Blob* blob) +{ + cv::Rect r1 = getBoundingBox(),r2 = blob->getBoundingBox(); + cv::Rect interRect = r1 & r2; + if (interRect.width == 0 || interRect.height == 0) { + return 0; + } + //! Minimum containing rectangle + cv::Rect minContainingRect = r1 | r2; + cv::Mat m1 = cv::Mat::zeros(minContainingRect.height, minContainingRect.width, CV_8UC1); + cv::Mat m2 = cv::Mat::zeros(minContainingRect.height, minContainingRect.width, CV_8UC1); + fillBlob(m1, cv::Scalar(255), -minContainingRect.x, -minContainingRect.y, true); + blob->fillBlob(m2, cv::Scalar(255), -minContainingRect.x, -minContainingRect.y, true); + return countNonZero(m1 & m2); +} + +double Blob::density(AreaMode areaCalculationMode) +{ + double density = 0; + BlobContourList::iterator itContour; + switch (areaCalculationMode) { + case GREEN: + { + double blobArea = area(); + Contours cHull, cHullPts; + getConvexHull(cHull); + //approxPolyDP(cHull, cHullPts, 0.001, true); + double cHullArea = fabs(contourArea(cHull[0], false)); + density = blobArea/cHullArea; + break; + } + case PIXELWISE: + { + Contours cHull; + getConvexHull(cHull); + cv::Rect bbox = getBoundingBox(); + cv::Mat blMat = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1); + cv::Mat cHullMat = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1); + fillBlob(blMat, cv::Scalar(255), -bbox.x, -bbox.y, true); + drawContours(cHullMat, cHull, -1, cv::Scalar(255), -1, 8, cv::noArray(), 2147483647, cv::Point(-bbox.x, -bbox.y)); + int totArea = countNonZero(cHullMat); + int actArea = countNonZero(blMat); + density = (double)actArea/totArea; + break; + } + } + + return density; +} + +void Blob::getExtremes(cv::Point& xmax,cv::Point& xmin, cv::Point& ymax, cv::Point& ymin) +{ + const PointList& pts = m_externalContour.getContourPoints(); + /*Contours hull; + getConvexHull(hull); + const PointList &pts = hull[0];*/ + int nPts = pts.size(); + xmax = pts[0]; + xmin = pts[0]; + ymax = pts[0]; + ymin = pts[0]; + for (int i = 1; i < nPts; ++i) { + if (xmax.x < pts[i].x) { + xmax = pts[i]; + } + else if (xmin.x > pts[i].x) { + xmin = pts[i]; + } + if (ymax.y < pts[i].y) { + ymax = pts[i]; + } + else if (ymin.y > pts[i].y) { + ymin = pts[i]; + } + } +} + +cv::Point2f Blob::getCentroid(bool useInternalContours) +{ + float m00 = moment(0, 0, useInternalContours), m10 = moment(1, 0, useInternalContours); + float m01 = moment(0, 1, useInternalContours); + float x = m10 / m00, y = m01 / m00; + return cv::Point2f(x, y); +} + + diff --git a/examples/contour/blob/blob.h b/examples/contour/blob/blob.h new file mode 100644 index 00000000..7be5f503 --- /dev/null +++ b/examples/contour/blob/blob.h @@ -0,0 +1,268 @@ +/************************************************************************ +Blob.h + +FUNCTIONALITY: Definition of the Blob class and some helper classes to perform + some calculations on it +AUTHOR: Inspecta S.L. +MODIFICATIONS (Modification, Author, Date): + +**************************************************************************/ + +//! Disable warnings referred to 255 character truncation for the std:map +//#pragma warning(disable : 4786) + +#ifndef CBLOB_INSPECTA_INCLUDED +#define CBLOB_INSPECTA_INCLUDED + +class Blob; +#include "opencv2/core/core_c.h" +#include "opencv2/opencv.hpp" +#include "BlobLibraryConfiguration.h" +#include "BlobContour.h" +#include +#include + + +#ifdef BLOB_OBJECT_FACTORY +//! Object factory pattern implementation +#include "..\inspecta\DesignPatterns\ObjectFactory.h" +#endif + +//! Type of labelled images +typedef unsigned int LabelID; +typedef std::list BlobList; +typedef std::list BlobContourList; + +enum AreaMode {GREEN, PIXELWISE}; + +//! Blob class +class Blob +{ + friend class CompLabeler; +public: + Blob(); + Blob(LabelID id, const cv::Point& startPoint, const cv::Size& originalImageSize); + ~Blob(); + + //! Copy constructor + Blob(const Blob& src); + Blob(const Blob* src); + + //! Assigment operator + Blob& operator=(const Blob& src); + + //! Adds a new internal contour to the blob + void addInternalContour(const BlobContour& newContour); + + //! Retrieves contour in Freeman's chain code + BlobContour* getExternalContour() { + return &m_externalContour; + } + + BlobContourList& getInternalContours() { + return m_internalContours; + } + + //! Bool to permit deletion with filter function + double m_toBeDeleted; + + //! Get label ID + LabelID getID() { + return m_id; + } + + void setID(LabelID newID) { + m_id = newID; + } + + //! > 0 for external blobs, 0 if not + int exterior(IplImage* mask, bool xBorder = true, bool yBorder = true); + + //! opencv2 Interface + int exterior(const cv::Mat& mask, bool xBorder = true, bool yBorder = true); + + //! Computes the area of the blob. + //! areaCompMode defines which way to compute the areas: + //! - Using green's formula (not exact result, probably faster) + //! - Counting the pixels (probably slower) + double area(AreaMode areaCompMode = GREEN); + + //! Compute blob's perimeter + double perimeter(); + + //! Compute blob's moment (p,q up to MAX_CALCULATED_MOMENTS) + //! if intContours = false, internal contours do not count for moment computation (i.e. the blob is considered without holes). + double moment(int p, int q, bool intContours = true); + + //! Compute external perimeter + double externalPerimeter(IplImage* mask, bool xBorder = true, bool yBorder = true); + + //! opencv2 interface + double externalPerimeter(const cv::Mat& mask, bool xBorder = true, bool yBorder = true); + + //! Get mean grey color + //(Warning: use meanStdDev for simultaneous computation of mean and std. dev, and for RGB images). + double mean(IplImage *image); + + //! opencv2 interface + //(Warning: use meanStdDev for simultaneous computation of mean and std. dev, and for RGB images). + double mean(const cv::Mat& image); + + //! Get standard deviation grey color + //(Warning: use meanStdDev for simultaneous computation of mean and std. dev, and for RGB images). + double stdDev(IplImage *image); + + //! opencv2 interface + //(Warning: use meanStdDev for simultaneous computation of mean and std. dev, and for RGB images). + double stdDev(const cv::Mat& image); + + //! Computes mean and standard deviation of image, which can be in any opencv format + //! Since mean and standard deviation are computed with the same function call, this results quicker than + //! calling separately mean and standard deviation. + void meanStdDev(const cv::Mat& image, cv::Scalar& mean, cv::Scalar& stddev); + + //void meanStdDev(Mat image, double *mean, double *stddev); + + //! Shows if the blob has associated information + bool isEmpty(); + + //! Calculates the convex hull of the blob + void getConvexHull(Contours& hull); + + //! Paints the blob in an image + //! intContours - determines wheter to draw the holes of the blob (true) or not (false) + //! srcImage - image from where to copy the holes contents. If unassigned and intContours is true, the internal pixels will be set to black. + void fillBlob(IplImage *image, cv::Scalar color, int offsetX = 0, int offsetY = 0, bool intContours = false, const IplImage* srcImage = NULL); + + void fillBlob(const cv::Mat& image, cv::Scalar color, int offsetX = 0, int offsetY = 0, bool intContours = false, const cv::Mat& srcImage = cv::Mat()); + + //! Joins a blob to current one + //! NOTE: All the data is copied, a new blob is created and joined to the caller one. + void joinBlob(Blob* blob); + + //! Get bounding box + cv::Rect getBoundingBox(); + + //! Get bounding ellipse + cv::RotatedRect getEllipse(); + + //! Minimun X + double minX() { + return getBoundingBox().x; + } + + //! Minimun Y + double minY() { + return getBoundingBox().y; + } + + //! Maximun X + double maxX() { + return getBoundingBox().x + getBoundingBox().width; + } + + //! Maximun Y + double maxY() { + return getBoundingBox().y + getBoundingBox().height; + } + + /** + Computes extremes for the contour (i.e. 4 points, respectively with max X, max Y, min X, minY) + In case of perfect rectangular shapes, the code will return the vertexes in counterclockwise order. + TODO: Extend function to joined blobs + */ + void getExtremes(cv::Point& xmax, cv::Point& xmin, cv::Point& ymax, cv::Point& ymin); + + + //! Shifts the blob by (x,y) + void shiftBlob(int x, int y); + + //! Returns the number of overlapping pixels between the caller blob and blob. + //! A preliminary check is performed with respect to the bounding boxes in order to avoid unnecessary computations + int overlappingPixels(Blob* blob); + + //! Computes the density of the blob, i.e. the ratio (blob Area) / (ConvexHullArea) + //! areaCalculationMode defines which way to compute the areas: + //! - Using green's formula (not exact result, probably faster) + //! - Counting the pixels + double density(AreaMode areaCalculationMode); + + //! Returns blob center in pixels (integers). + cv::Point getCenter(); + + //! Return blob centroid + cv::Point2f getCentroid(bool useInternalContours); + + /* + * Border: 0 = top, 1 = right, 2 = bottom, 3 = left + */ + /*Contours getPointsTouchingBorder(int border);*/ + + //! For joined blobs, return the number of sub-blobs. + int getNumJoinedBlobs(); + + cv::Size originalImageSize() const { return m_originalImageSize; } + + void originalImageSize(int width, int height) { m_originalImageSize.width = width; m_originalImageSize.height = height; } + +private: + void requestDeletion(Blob* blob); + + //! Deallocates all contours + void clearContours(); + +private: + //! Just for multithread joining routine; + bool m_startPassed; + + bool m_isJoined; + + BlobList m_joinedBlobs; + + Blob* m_deleteRequestOwnerBlob; + + ////////////////////////////////////////////////////////////////////////// + // Blob contours + ////////////////////////////////////////////////////////////////////////// + + //! External contour of the blob (crack codes) + BlobContour m_externalContour; + + //! Internal contours (crack codes) + BlobContourList m_internalContours; + + ////////////////////////////////////////////////////////////////////////// + // Blob features + ////////////////////////////////////////////////////////////////////////// + + //! Label number + LabelID m_id; + + //! Area + double m_area; + + //! Perimeter + double m_perimeter; + + //! external perimeter from blob + double m_externalPerimeter; + + //! mean gray color + double m_meanGray; + + //! Standard deviation from gray color blob distribution + double m_stdDevGray; + + //! Bounding box + cv::Rect m_boundingBox; + + //! Bounding ellipse + cv::RotatedRect m_ellipse; + + //! Sizes from image where blob is extracted + cv::Size m_originalImageSize; + + friend class BlobGroup; +}; + +#endif //CBLOB_INSPECTA_INCLUDED diff --git a/examples/contour/blob/defines.h b/examples/contour/blob/defines.h new file mode 100644 index 00000000..34d3200b --- /dev/null +++ b/examples/contour/blob/defines.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#ifdef MATRIXCV_ACTIU +#include "matrixCV.h" +#else +#include "vector" +//! Vector de doubles +typedef std::vector DoubleStlVector; +#endif + +/************************************************************************** + Filtres / Filters +**************************************************************************/ + +//! Actions performed by a filter (include or exclude blobs) +#define B_INCLUDE 1L +#define B_EXCLUDE 2L + +enum FilterAction {FLT_INCLUDE = 1, FLT_EXCLUDE}; + +//! Conditions to apply the filters +#define B_EQUAL 3L +#define B_NOT_EQUAL 4L +#define B_GREATER 5L +#define B_LESS 6L +#define B_GREATER_OR_EQUAL 7L +#define B_LESS_OR_EQUAL 8L +#define B_INSIDE 9L +#define B_OUTSIDE 10L + +enum FilterCondition {FLT_EQUAL = 3, FLT_NOTEQUAL, FLT_GREATER, FLT_LESS, FLT_GREATEROREQUAL, FLT_LESSOREQUAL, FLT_INSIDE, FLT_OUTSIDE}; + +/************************************************************************** + Excepcions / Exceptions +**************************************************************************/ + +//! Exceptions thrown by functions: +#define EXCEPTION_BLOB_OUT_OF_BOUNDS 1000 +#define EXCEPCIO_CALCUL_BLOBS 1001 + +/** + * Class that contains a set of blobs and allows properties to be extracted or filter them according to certain criteria. + * Class to calculate the blobs of an image and calculate some properties on them. Also, the class provides functions to filter the blobs using some criteria. +*/ + +class Blob; + +//! vector of blob pointers typedef +typedef std::vector BlobVector; diff --git a/examples/contour/bspline.cpp b/examples/contour/bspline.cpp new file mode 100644 index 00000000..6e0a8431 --- /dev/null +++ b/examples/contour/bspline.cpp @@ -0,0 +1,144 @@ +#include "bspline.hpp" + +namespace cvpr +{ + BSpline::BSpline() : m_isValid(false) + { + + } + + BSpline::BSpline(const std::vector>& points, int degree) + { + createImpl(points, degree); + } + + void BSpline::create(const std::vector>& points, int degree) + { + createImpl(points, degree); + } + + void BSpline::createImpl(const std::vector>& points, int degree) + { + m_isValid = true; + + m_knots.clear(); + + m_weights.clear(); + + m_v.clear(); + + m_degree = degree; + + if (points.empty()) { + m_isValid = false; + return; + } + + int n = points.size(); + + if (m_degree < 1) { + m_isValid = false; + return; + } + + if (m_degree > (n - 1)) { + m_isValid = false; + return; + } + + int step = m_degree + 1; + + int count = (m_degree + 1 + points.size()); + + for (int i = 0; i < count; ++i) { + if (i < step) { + m_knots.emplace_back(0); + } + else if (i >= count - step) { + m_knots.emplace_back(count - 2 * step + 1); + } + else { + m_knots.emplace_back(i - step + 1); + } + } + + m_dim = points[0].size(); + + if (m_weights.empty()) { + for (int i = 0; i < n; i++) { + m_weights.emplace_back(1); + } + } + + if (m_knots.empty()) { + for (int i = 0; i < n + m_degree + 1; i++) { + m_knots.emplace_back(i); + } + } + else if (m_knots.size() != (n + m_degree + 1)) { + m_isValid = false; + return; + } + + m_domain = { m_degree, m_knots.size() - 1 - m_degree }; + + for (int i = 0; i < n; i++) { + m_v.emplace_back(std::vector()); + for (int j = 0; j < m_dim; j++) { + m_v[i].emplace_back(points[i][j] * m_weights[i]); + } + m_v[i].emplace_back(m_weights[i]); + } + } + + bool BSpline::isValid() + { + return m_isValid; + } + + std::vector BSpline::eval(double t) + { + if (!isValid()) { + return {}; + } + + std::vector result; + + double low = m_knots[m_domain.first]; + + double high = m_knots[m_domain.second]; + + t = t * (high - low) + low; + + if (t < low || t > high) { + return result; + } + + int s = 0; + + for (s = m_domain.first; s < m_domain.second; s++) { + if (t >= m_knots[s] && t <= m_knots[s + 1]) { + break; + } + } + + std::vector> v = m_v; + + double alpha = 0.0; + + for (int l = 1; l <= m_degree + 1; l++) { + for (int i = s; i > s - m_degree - 1 + l; i--) { + alpha = (t - m_knots[i]) / (m_knots[i + m_degree + 1 - l] - m_knots[i]); + for (int j = 0; j < m_dim + 1; j++) { + v[i][j] = (1 - alpha) * v[i - 1][j] + alpha * v[i][j]; + } + } + } + + for (int i = 0; i < m_dim; i++) { + result.emplace_back(v[s][i] / v[s][m_dim]); + } + + return result; + } +} \ No newline at end of file diff --git a/examples/contour/bspline.hpp b/examples/contour/bspline.hpp new file mode 100644 index 00000000..2e5522a9 --- /dev/null +++ b/examples/contour/bspline.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "i_bspline.hpp" + +namespace cvpr +{ + class BSpline : public IBSpline + { + public: + BSpline(); + + BSpline(const std::vector>& points, int degree); + + void create(const std::vector>& points, int degree) override; + + bool isValid() override; + + std::vector eval(double t) override; + + private: + void createImpl(const std::vector>& points, int degree); + + private: + bool m_isValid; + + std::pair m_domain; + + std::vector m_knots; + + std::vector m_weights; + + std::vector> m_v; + + int m_dim; + + int m_degree; + }; +} diff --git a/examples/contour/contour.cpp b/examples/contour/contour.cpp new file mode 100644 index 00000000..684aec82 --- /dev/null +++ b/examples/contour/contour.cpp @@ -0,0 +1,75 @@ +// basic.cpp : 定义应用程序的入口点。 +// + +#include "main.h" +#include "basic_form.h" +#include + +enum ThreadId +{ + kThreadUI +}; + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + AllocConsole(); + freopen("CONOUT$", "w", stdout); + + + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) + { + // TODO: change error code to suit your needs + _tprintf(_T("Fatal Error: MFC initialization failed\n")); + return -1; + } + // 创建主线程 + MainThread thread; + + // 执行主线程循环 + thread.RunOnCurrentThreadWithLoop(nbase::MessageLoop::kUIMessageLoop); + + return 0; +} + +void MainThread::Init() +{ + nbase::ThreadManager::RegisterThread(kThreadUI); + + // 获取资源路径,初始化全局参数 + std::wstring theme_dir = nbase::win32::GetCurrentModuleDirectory(); +#ifdef _DEBUG + // Debug 模式下使用本地文件夹作为资源 + // 默认皮肤使用 resources\\themes\\default + // 默认语言使用 resources\\lang\\zh_CN + // 如需修改请指定 Startup 最后两个参数 + ui::GlobalManager::Startup(theme_dir + L"resources\\", ui::CreateControlCallback(), true); + std::wcout<< theme_dir + L"resources\\"; +#else + + // Release 模式下使用资源中的压缩包作为资源 + // 资源被导入到资源列表分类为 THEME,资源名称为 IDR_THEME + // 如果资源使用的是本地的 zip 文件而非资源中的 zip 压缩包 + // 可以使用 OpenResZip 另一个重载函数打开本地的资源压缩包 + //ui::GlobalManager::OpenResZip(MAKEINTRESOURCE(IDR_THEME), L"THEME", ""); + ui::GlobalManager::OpenResZip(L"resources.zip", ""); + ui::GlobalManager::Startup(L"resources\\", ui::CreateControlCallback(), false); +#endif + + // 创建一个默认带有阴影的居中窗口 + BasicForm* window = new BasicForm(); + window->Create(NULL, BasicForm::kClassName.c_str(), WS_OVERLAPPEDWINDOW, 0); + window->CenterWindow(); + window->ShowWindow(); +} + +void MainThread::Cleanup() +{ + ui::GlobalManager::Shutdown(); + SetThreadWasQuitProperly(true); + nbase::ThreadManager::UnregisterThread(); +} \ No newline at end of file diff --git a/examples/contour/contour.h b/examples/contour/contour.h new file mode 100644 index 00000000..d00d47e7 --- /dev/null +++ b/examples/contour/contour.h @@ -0,0 +1,3 @@ +#pragma once + +#include "resource.h" diff --git a/examples/contour/contour.ico b/examples/contour/contour.ico new file mode 100644 index 00000000..b3ec03bd Binary files /dev/null and b/examples/contour/contour.ico differ diff --git a/examples/contour/contour.rc b/examples/contour/contour.rc new file mode 100644 index 00000000..8b3f0a36 Binary files /dev/null and b/examples/contour/contour.rc differ diff --git a/examples/contour/contour.vcxproj b/examples/contour/contour.vcxproj new file mode 100644 index 00000000..7797a30e --- /dev/null +++ b/examples/contour/contour.vcxproj @@ -0,0 +1,179 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {a01dd5d1-1d47-4956-a472-fd84b1af7036} + contour + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + G:\project\c++\nim_duilib\examples\contour;G:\project\c++\nim_duilib;C:\Users\caiyu\anaconda3\pkgs\opencv-4.6.0-py311h5d08a89_5\Library\include;G:\project\c++\nim_duilib\examples\contour\blob;$(IncludePath) + G:\project\c++\nim_duilib\examples\contour;D:\project\cpp\opencv-4.10.0\build\3rdparty\lib\Debug;$(ReferencePath) + G:\project\c++\nim_duilib\examples\contour;D:\project\cpp\opencv-4.10.0\build\3rdparty\lib\Debug;$(LibraryPath) + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + NotUsing + stdcpp20 + + + Windows + true + opencv_world4100d.lib;libjpeg-turbod.lib;zlibd.lib;libpngd.lib;libtiffd.lib;libwebpd.lib;ippiwd.lib;IlmImfd.lib;ittnotifyd.lib;libopenjp2d.lib;libprotobufd.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {8d9a6595-717a-41c8-b468-0011a72be3d1} + + + {e106acd7-4e53-4aee-942b-d0dd426db34e} + + + {0149ba6e-3c0a-426d-aa0a-0b9ec7742f19} + + + + + + \ No newline at end of file diff --git a/examples/contour/contour.vcxproj.filters b/examples/contour/contour.vcxproj.filters new file mode 100644 index 00000000..82303c7f --- /dev/null +++ b/examples/contour/contour.vcxproj.filters @@ -0,0 +1,82 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + + + 资源文件 + + + + + 资源文件 + + + 资源文件 + + + \ No newline at end of file diff --git a/examples/contour/contour.xml b/examples/contour/contour.xml new file mode 100644 index 00000000..08408c1f --- /dev/null +++ b/examples/contour/contour.xml @@ -0,0 +1,48 @@ + + + + + +