添加opencv验证工具

This commit is contained in:
zcy 2025-03-16 16:42:44 +08:00
parent a326008bf5
commit 93627c8c23
44 changed files with 6437 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,301 @@
#include "basic_form.h"
#include <atlstr.h>
#include <afxdlgs.h>
#include <afxwin.h>
#include <io.h>
#include <iosfwd>
#include <iostream>
#include <ios>
#include <fstream>
#include <vector>
#include <codecvt>
#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<std::codecvt_utf8<wchar_t>> converter;
return converter.to_bytes(input);
}
static int bz2_read( struct bspatch_stream* stream, void* buffer, int length)
{
return 0;
}
std::vector<char>* ReadFile(std::string path) {
std::vector<char>* ret = nullptr;
std::ifstream file(path.c_str(), std::ios::in | std::ios::binary);
if (file)
{
// <20><><EFBFBD>ն<EFBFBD><D5B6><EFBFBD><EFBFBD>Ƹ<EFBFBD>ʽ<EFBFBD><CABD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>
file.seekg(0, std::ios::end);
long long fileSize = file.tellg();
printf("size of firm: %lld\n", fileSize);
// <20><><EFBFBD><EFBFBD>дλ<D0B4><CEBB><EFBFBD>ƶ<EFBFBD><C6B6><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ棬<DAB4><E6A3AC><EFBFBD>̼<EFBFBD><CCBC><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD>buffer
file.seekg(0, std::ios::beg);
char* buffer = new char[fileSize];
file.read(buffer, sizeof(char) * fileSize);
ret = new std::vector<char>(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<ui::Button*>(FindControl(L"btn_do_open1"));
ui::Button* btn_set_config = dynamic_cast<ui::Button*>(FindControl(L"btn_set_config"));
ui::RichEdit* min_contour = dynamic_cast<ui::RichEdit*>(FindControl(L"min_contour"));
ui::RichEdit* offset_controu = dynamic_cast<ui::RichEdit*>(FindControl(L"offset_controu"));
ui::RichEdit* epsilon = dynamic_cast<ui::RichEdit*>(FindControl(L"epsilon"));
ui::RichEdit* angel_threshold = dynamic_cast<ui::RichEdit*>(FindControl(L"angel_threshold"));
ui::RichEdit* smooth_step = dynamic_cast<ui::RichEdit*>(FindControl(L"smooth_step"));
ui::RichEdit* file1_dir = dynamic_cast<ui::RichEdit*>(FindControl(L"file1_dir"));
ui::Control* img1 = dynamic_cast<ui::Control*>(FindControl(L"img1"));
ui::Control* img2 = dynamic_cast<ui::Control*>(FindControl(L"img2"));
ui::Label* cnt = dynamic_cast<ui::Label*>(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<<smin_contour<<std::endl;
std::cout<<soffset_controu<<std::endl;
std::cout<<sepsilon<<std::endl;
std::cout<<ssmooth_step<<std::endl;
std::cout<<sangel_threshold<<std::endl;
img1->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<cv::Mat> 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<std::vector<cv::Point>> 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<cv::Mat> 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<std::vector<cv::Point>> 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"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);
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <afxwin.h>
#include "targetver.h"
// C runtime header
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <string>
// base header
#include "base/base.h"
// duilib
#include "duilib/UIlib.h"
class BasicForm : public ui::WindowImplBase
{
public:
BasicForm();
~BasicForm();
/**
*
* 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
*/
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;
};

View File

@ -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;
}

View File

@ -0,0 +1,122 @@
#ifndef BLOBCONTOUR_H_INCLUDED
#define BLOBCONTOUR_H_INCLUDED
#include <list>
#include "opencv2/imgproc/imgproc_c.h"
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
//! 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<ChainCode> ChainCodeList;
//! In order to emulate CvSeq objects and to comply with opencv 2.0 interface
typedef std::vector<ChainCodeList> ChainCodeContours;
//! Type of list of points
typedef std::vector<cv::Point> PointList;
typedef std::vector<PointList> 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

View File

@ -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;
}

View File

@ -0,0 +1,45 @@
#ifndef BLOBDETECTOR_H
#define BLOBDETECTOR_H
#include <vector>
#include <functional>
#include "opencv2/core/core_c.h"
#include <opencv2/opencv.hpp>
#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

View File

@ -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 <afx.h> //suport per a CStrings
#include <afxwin.h> //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<double>());
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;
}
}

View File

@ -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 <math.h>
#include "opencv2/core/core_c.h"
#include <opencv2/opencv.hpp>
#include <deque>
#include <limits.h>
#include <stdio.h>
#include <functional>
#include <algorithm>
#include <cfloat>
#include <vector>
#include <functional>
#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)

View File

@ -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

View File

@ -0,0 +1,456 @@
#include <limits.h>
#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<double>(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<double>(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<BlobGetArea>());
fabricaOperadorsBlob.Register(BlobGetBreadth().name(), Type2Type<BlobGetBreadth>());
fabricaOperadorsBlob.Register(BlobGetCompactness().name(), Type2Type<BlobGetCompactness>());
fabricaOperadorsBlob.Register(BlobGetElongation().name(), Type2Type<BlobGetElongation>());
fabricaOperadorsBlob.Register(BlobGetExterior().name(), Type2Type<BlobGetExterior>());
fabricaOperadorsBlob.Register(BlobGetLength().name(), Type2Type<BlobGetLength>());
fabricaOperadorsBlob.Register(BlobGetPerimeter().name(), Type2Type<BlobGetPerimeter>());
fabricaOperadorsBlob.Register(BlobGetRoughness().name(), Type2Type<BlobGetRoughness>());
// blob color
fabricaOperadorsBlob.Register(BlobGetMean(NULL).name(), Type2Type<BlobGetMean>());
fabricaOperadorsBlob.Register(BlobGetStdDev(NULL).name(), Type2Type<BlobGetStdDev>());
// external pixels
fabricaOperadorsBlob.Register(BlobGetExternalPerimeterRatio().name(), Type2Type<BlobGetExternalPerimeterRatio>());
fabricaOperadorsBlob.Register(BlobGetExternalHullPerimeterRatio().name(), Type2Type<BlobGetExternalHullPerimeterRatio>());
fabricaOperadorsBlob.Register(BlobGetExternalPerimeter().name(), Type2Type<BlobGetExternalPerimeter>());
// hull
fabricaOperadorsBlob.Register(BlobGetHullPerimeter().name(), Type2Type<BlobGetHullPerimeter>());
fabricaOperadorsBlob.Register(BlobGetHullArea().name(), Type2Type<BlobGetHullArea>());
// elipse info
fabricaOperadorsBlob.Register(BlobGetMajorAxisLength().name(), Type2Type<BlobGetMajorAxisLength>());
fabricaOperadorsBlob.Register(BlobGetMinorAxisLength().name(), Type2Type<BlobGetMinorAxisLength>());
fabricaOperadorsBlob.Register(BlobGetAxisRatio().name(), Type2Type<BlobGetAxisRatio>());
fabricaOperadorsBlob.Register(BlobGetOrientation().name(), Type2Type<BlobGetOrientation>());
fabricaOperadorsBlob.Register(BlobGetOrientationCos().name(), Type2Type<BlobGetOrientationCos>());
fabricaOperadorsBlob.Register(BlobGetAreaElipseRatio().name(), Type2Type<BlobGetAreaElipseRatio>());
// min an max
fabricaOperadorsBlob.Register(BlobGetMaxX().name(), Type2Type<BlobGetMaxX>());
fabricaOperadorsBlob.Register(BlobGetMaxY().name(), Type2Type<BlobGetMaxY>());
fabricaOperadorsBlob.Register(BlobGetMinX().name(), Type2Type<BlobGetMinX>());
fabricaOperadorsBlob.Register(BlobGetMinY().name(), Type2Type<BlobGetMinY>());
fabricaOperadorsBlob.Register(BlobGetMaxXatMaxY().name(), Type2Type<BlobGetMaxXatMaxY>());
fabricaOperadorsBlob.Register(BlobGetMaxYatMinX().name(), Type2Type<BlobGetMaxYatMinX>());
fabricaOperadorsBlob.Register(BlobGetMinXatMinY().name(), Type2Type<BlobGetMinXatMinY>());
fabricaOperadorsBlob.Register(BlobGetMinYatMaxX().name(), Type2Type<BlobGetMinYatMaxX>());
// coordinate info
fabricaOperadorsBlob.Register(BlobGetXYInside().name(), Type2Type<BlobGetXYInside>());
fabricaOperadorsBlob.Register(BlobGetDiffY().name(), Type2Type<BlobGetDiffY>());
fabricaOperadorsBlob.Register(CBlobGetDiffX().name(), Type2Type<CBlobGetDiffX>());
fabricaOperadorsBlob.Register(BlobGetXCenter().name(), Type2Type<BlobGetXCenter>());
fabricaOperadorsBlob.Register(BlobGetYCenter().name(), Type2Type<BlobGetYCenter>());
fabricaOperadorsBlob.Register(BlobGetDistanceFromPoint().name(), Type2Type<BlobGetDistanceFromPoint>());
// moments
fabricaOperadorsBlob.Register(BlobGetMoment().name(), Type2Type<BlobGetMoment>());
}
#endif //BLOB_OBJECT_FACTORY

View File

@ -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<OperatorBlob, const char *, functorComparacioIdOperador > 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

View File

@ -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<<","<<r<<endl;
m_currentContour = &m_currentBlob->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<<","<<r<<endl;
#ifdef DEBUG_COMPONENT_LABELLING
std::cout << "tracerInt\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_currentContour = new BlobContour(cv::Point(m_c, m_r), cv::Size(m_w, m_h));
m_currentContour->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<std::mutex> 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()<<endl;
for (unsigned int i = 0; i < lbl.m_blobs.size(); i++) {
lbl.m_blobs[i]->setID(label);
label++;
blobs.push_back(lbl.m_blobs[i]);
}
}
std::vector<std::thread> 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 " <<i<<"\t" << labelers[i]->blobs.size()<<endl;
for (unsigned int j = 0; j < m_labelers[i]->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_<int>::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);
}

View File

@ -0,0 +1,120 @@
#if !defined(_COMPONENT_LABELING_H_INCLUDED)
#define _COMPONENT_LABELING_H_INCLUDED
//#define DEBUG_COMPONENT_LABELLING
#include <thread>
#include <vector>
#include "BlobContour.h"
#include "defines.h"
#include "blob.h"
#include "opencv2/opencv.hpp"
//#include <pthread.h>
//! 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

File diff suppressed because it is too large Load Diff

View File

@ -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 <deque>
#include <list>
#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<Blob*> BlobList;
typedef std::list<BlobContour*> 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

View File

@ -0,0 +1,52 @@
#pragma once
#include <memory>
#include <vector>
#ifdef MATRIXCV_ACTIU
#include "matrixCV.h"
#else
#include "vector"
//! Vector de doubles
typedef std::vector<double> 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<Blob*> BlobVector;

View File

@ -0,0 +1,144 @@
#include "bspline.hpp"
namespace cvpr
{
BSpline::BSpline() : m_isValid(false)
{
}
BSpline::BSpline(const std::vector<std::vector<double>>& points, int degree)
{
createImpl(points, degree);
}
void BSpline::create(const std::vector<std::vector<double>>& points, int degree)
{
createImpl(points, degree);
}
void BSpline::createImpl(const std::vector<std::vector<double>>& 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<double>());
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<double> BSpline::eval(double t)
{
if (!isValid()) {
return {};
}
std::vector<double> 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<std::vector<double>> 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;
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#include <vector>
#include "i_bspline.hpp"
namespace cvpr
{
class BSpline : public IBSpline
{
public:
BSpline();
BSpline(const std::vector<std::vector<double>>& points, int degree);
void create(const std::vector<std::vector<double>>& points, int degree) override;
bool isValid() override;
std::vector<double> eval(double t) override;
private:
void createImpl(const std::vector<std::vector<double>>& points, int degree);
private:
bool m_isValid;
std::pair<int, int> m_domain;
std::vector<double> m_knots;
std::vector<double> m_weights;
std::vector<std::vector<double>> m_v;
int m_dim;
int m_degree;
};
}

View File

@ -0,0 +1,75 @@
// basic.cpp : 定义应用程序的入口点。
//
#include "main.h"
#include "basic_form.h"
#include <iostream>
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();
}

View File

@ -0,0 +1,3 @@
#pragma once
#include "resource.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
examples/contour/contour.rc Normal file

Binary file not shown.

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{a01dd5d1-1d47-4956-a472-fd84b1af7036}</ProjectGuid>
<RootNamespace>contour</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>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)</IncludePath>
<ReferencePath>G:\project\c++\nim_duilib\examples\contour;D:\project\cpp\opencv-4.10.0\build\3rdparty\lib\Debug;$(ReferencePath)</ReferencePath>
<LibraryPath>G:\project\c++\nim_duilib\examples\contour;D:\project\cpp\opencv-4.10.0\build\3rdparty\lib\Debug;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>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)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="contour.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="Resource.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="basic_form.cpp" />
<ClCompile Include="blob\blob.cpp" />
<ClCompile Include="blob\BlobContour.cpp" />
<ClCompile Include="blob\BlobDetector.cpp" />
<ClCompile Include="blob\BlobGroup.cpp" />
<ClCompile Include="blob\BlobOperators.cpp" />
<ClCompile Include="blob\ComponentLabeling.cpp" />
<ClCompile Include="bspline.cpp" />
<ClCompile Include="contour.cpp" />
<ClCompile Include="contours_extractor.cpp" />
<ClCompile Include="contours_extractor_factory.cpp" />
<ClCompile Include="contours_extractor_impl.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="contour.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="contour.ico" />
<Image Include="small.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\base\base.vcxproj">
<Project>{8d9a6595-717a-41c8-b468-0011a72be3d1}</Project>
</ProjectReference>
<ProjectReference Include="..\..\duilib\duilib.vcxproj">
<Project>{e106acd7-4e53-4aee-942b-d0dd426db34e}</Project>
</ProjectReference>
<ProjectReference Include="..\..\ui_components\ui_components.vcxproj">
<Project>{0149ba6e-3c0a-426d-aa0a-0b9ec7742f19}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="Resource.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="contour.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="contour.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="basic_form.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="bspline.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="contours_extractor.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="contours_extractor_factory.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="contours_extractor_impl.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\blob.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\BlobContour.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\BlobDetector.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\BlobGroup.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\BlobOperators.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="blob\ComponentLabeling.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="contour.rc">
<Filter>资源文件</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="small.ico">
<Filter>资源文件</Filter>
</Image>
<Image Include="contour.ico">
<Filter>资源文件</Filter>
</Image>
</ItemGroup>
</Project>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<Window size="1280,900" caption="0,0,0,35">
<VBox bkcolor="bk_wnd_darkcolor">
<HBox width="stretch" height="35" bkcolor="bk_wnd_lightcolor">
<Control />
<Label name="tooltip" text="轮廓线获取" margin="4,6,0,0"/>
<Control />
<Button class="btn_wnd_min" name="minbtn" margin="4,6,0,0"/>
<Box width="21" margin="4,6,0,0">
<Button class="btn_wnd_max" name="maxbtn"/>
<Button class="btn_wnd_restore" name="restorebtn" visible="false"/>
</Box>
<Button class="btn_wnd_close" name="closebtn" margin="4,6,8,0"/>
</HBox>
<VBox width="stretch" height="35" margin="10,10,0,0">
<HBox>
<Label text="文件1" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="600" name="file1_dir" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Button class="btn_global_blue_80x30" name="btn_do_open1" width="80" text="打开" bkcolor="lightcolor" margin="0,3,5,3" font="arial_18" />
</HBox>
</VBox>
<HBox margin="10,10,10,10" width="stretch" height="35">
<Label text="滑块示例" margin="0,0,10,0" height="100" width="80" font="arial_18"/>
<Slider class="slider_green" value="70" margin="10"/>
</HBox>
<VBox width="stretch" height="35" margin="10,10,0,0">
<HBox>
<Label text="最小轮廓阈值" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="50" name="min_contour" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Label text="轮廓的偏置距离" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="50" name="offset_controu" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Label text="多边形逼近精度" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="50" name="epsilon" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Label text="缺陷处的开口阈值" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="50" name="angel_threshold" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Label text="平滑步长" margin="0,10,0,0" height="40" width="80" font="arial_18"/>
<RichEdit class="simple input" width="50" name="smooth_step" height="30" margin="5,5,5,5" padding="6,6,6" font="arial_18"/>
<Button class="btn_global_blue_80x30" name="btn_set_config" width="80" text="打开" bkcolor="lightcolor" margin="0,3,5,3" font="arial_18" />
</HBox>
</VBox>
</VBox>
</Window>

View File

@ -0,0 +1,96 @@
#include "contours_extractor.hpp"
#include "contours_extractor_impl.hpp"
namespace cvpr
{
ContoursExtractor::ContoursExtractor()
: m_impl(std::make_shared<ContoursExtractorImpl>())
{
}
void ContoursExtractor::init()
{
m_impl->init();
}
void ContoursExtractor::destroy()
{
m_impl->destroy();
}
void ContoursExtractor::setAreaThreshold(double threshold)
{
m_impl->setAngelThreshold(threshold);
}
double ContoursExtractor::areaThreshold()
{
return m_impl->angelThreshold();
}
void ContoursExtractor::setContourOffset(int32_t offset)
{
m_impl->setContourOffset(offset);
}
int32_t ContoursExtractor::contourOffset()
{
return m_impl->contourOffset();
}
void ContoursExtractor::setApproxPolyEpsilon(double epsilon)
{
m_impl->setApproxPolyEpsilon(epsilon);
}
double ContoursExtractor::approxPolyEpsilon()
{
return m_impl->approxPolyEpsilon();
}
void ContoursExtractor::setSmoothMethod(SMOOTH_METHOD method)
{
m_impl->setSmoothMethod(method);
}
SMOOTH_METHOD ContoursExtractor::smoothMethod()
{
return m_impl->smoothMethod();
}
void ContoursExtractor::setAngelThreshold(double threshold)
{
m_impl->setAngelThreshold(threshold);
}
double ContoursExtractor::angelThreshold()
{
return m_impl->angelThreshold();
}
void ContoursExtractor::setDefectThreshold(double threshold)
{
m_impl->setDefectThreshold(threshold);
}
double ContoursExtractor::defectThreshold()
{
return m_impl->defectThreshold();
}
void ContoursExtractor::setSmoothStep(double step)
{
m_impl->setSmoothStep(step);
}
double ContoursExtractor::smoothStep()
{
return m_impl->smoothStep();
}
void ContoursExtractor::extract(const cv::Mat& alpha, std::vector<std::vector<cv::Point>>& outputContours)
{
m_impl->extract(alpha, outputContours);
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "i_contours_extractor.hpp"
namespace cvpr
{
class ContoursExtractor : public IContoursExtractor {
public:
ContoursExtractor();
~ContoursExtractor() = default;
void init() override;
void destroy() override;
void setAreaThreshold(double threshold) override;
double areaThreshold() override;
void setContourOffset(int32_t offset) override;
int32_t contourOffset() override;
void setApproxPolyEpsilon(double epsilon) override;
double approxPolyEpsilon() override;
void setSmoothMethod(SMOOTH_METHOD method) override;
SMOOTH_METHOD smoothMethod() override;
void setAngelThreshold(double threshold) override;
double angelThreshold() override;
void setDefectThreshold(double threshold) override;
double defectThreshold() override;
void setSmoothStep(double step) override;
double smoothStep() override;
void extract(const cv::Mat& alpha, std::vector<std::vector<cv::Point>>& outputContours) override;
private:
std::shared_ptr<IContoursExtractor> m_impl;
};
}

View File

@ -0,0 +1,10 @@
#include "contours_extractor_factory.hpp"
#include "contours_extractor.hpp"
namespace cvpr
{
std::shared_ptr<IContoursExtractor> ContoursExtractorFactory::create()
{
return std::make_shared<ContoursExtractor>();
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <memory>
#include "i_contours_extractor.hpp"
namespace cvpr
{
class __declspec(dllexport) ContoursExtractorFactory {
public:
static std::shared_ptr<IContoursExtractor> create();
};
}

View File

@ -0,0 +1,434 @@
#include "contours_extractor_impl.hpp"
#include <algorithm>
#include <cstdlib>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include "blob/blob.h"
#include "blob/BlobDetector.h"
#include "blob/BlobGroup.h"
#include "blob/BlobContour.h"
#include "opencv2/imgproc.hpp"
#include "bspline.hpp"
#define MIN_VALUE 1e-8
#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE)
namespace cvpr
{
ContoursExtractorImpl::ContoursExtractorImpl()
{
}
void ContoursExtractorImpl::init()
{
}
void ContoursExtractorImpl::destroy()
{
}
void ContoursExtractorImpl::setAreaThreshold(double threshold)
{
m_areaThreshold = threshold;
}
double ContoursExtractorImpl::areaThreshold()
{
return m_areaThreshold;
}
void ContoursExtractorImpl::setContourOffset(int32_t offset)
{
m_contourOffset = offset;
}
int32_t ContoursExtractorImpl::contourOffset()
{
return m_contourOffset;
}
void ContoursExtractorImpl::setApproxPolyEpsilon(double epsilon)
{
m_approxPolyEpsilon = epsilon;
}
double ContoursExtractorImpl::approxPolyEpsilon()
{
return m_approxPolyEpsilon;
}
void ContoursExtractorImpl::setSmoothMethod(SMOOTH_METHOD method)
{
m_smoothMethod = method;
}
SMOOTH_METHOD ContoursExtractorImpl::smoothMethod()
{
return m_smoothMethod;
}
void ContoursExtractorImpl::setAngelThreshold(double threshold)
{
m_angelThreshold = threshold;
}
double ContoursExtractorImpl::angelThreshold()
{
return m_angelThreshold;
}
void ContoursExtractorImpl::setDefectThreshold(double threshold)
{
m_defectThreshold = threshold;
}
double ContoursExtractorImpl::defectThreshold()
{
return m_defectThreshold;
}
void ContoursExtractorImpl::setSmoothStep(double step)
{
m_smoothStep = step;
}
double ContoursExtractorImpl::smoothStep()
{
return m_smoothStep;
}
void ContoursExtractorImpl::extract(const cv::Mat& alpha, std::vector<std::vector<cv::Point>>& outputContours)
{
assert(!alpha.empty());
if (alpha.empty()) {
return;
}
cv::Mat edges;
cv::threshold(alpha, edges, 20, 255, cv::THRESH_BINARY);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
cv::erode(edges, edges, kernel);
if (m_contourOffset == 0) {
cv::findContours(edges, outputContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
return;
}
#if DEBUG_CONTOURS_EXTRACTOR
cv::imshow("edges", edges);
cv::waitKey(0);
#endif
BlobDetector detector(edges);
BlobGroup group = detector.getBlobGroup();
group.filter(group, FilterAction::FLT_EXCLUDE, BlobGetArea(), FilterCondition::FLT_LESS, m_areaThreshold);
cv::Point center(edges.cols / 2, edges.rows / 2);
Blob* centerBlob = group.getBlobNearestTo(center);
cv::Point pt = centerBlob->getCentroid(true);
for (int i = 0; i < group.getNumBlobs(); i++) {
auto* blob = group.getBlob(i);
if (blob != centerBlob) {
auto center = blob->getCenter();
cv::line(edges, center, pt, cv::Scalar(255, 255, 255), m_contourOffset, 1);
}
}
std::vector<std::vector<cv::Point>> contours;
cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
cv::Mat tmp = cv::Mat::zeros(edges.rows, edges.cols, CV_8UC3);
#if DEBUG_CONTOURS_EXTRACTOR
cv::drawContours(tmp, contours, 0, cv::Scalar(0, 0, 255), 1);
#endif
for (int i = 0; i < contours.size(); ++i) {
for (int j = 0; j < contours[i].size(); ++j) {
cv::circle(tmp, contours[i][j], m_contourOffset, cv::Scalar(255, 255, 255), -1);
}
}
#if DEBUG_CONTOURS_EXTRACTOR
cv::imshow("tmp", tmp);
cv::waitKey(0);
#endif
cv::Mat bin;
cv::cvtColor(tmp, bin, CV_BGR2GRAY);
std::vector<std::vector<cv::Point>> output;
cv::findContours(bin, output, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
#if DEBUG_CONTOURS_EXTRACTOR
cv::drawContours(tmp, output, 0, cv::Scalar(0, 0, 255), 1);
cv::imshow("tmp", tmp);
cv::waitKey(0);
#endif
if (m_smoothMethod == SMOOTH_METHOD::BSPLINE) {
std::vector<cv::Point> approxCurve;
cv::approxPolyDP(output[0], approxCurve, m_approxPolyEpsilon, false);
//output[0].emplace_back(output[0][0]);
#if DEBUG_CONTOURS_EXTRACTOR
cv::Mat color = cv::imread("C:/Users/ouxia/source/repos/Contour/data/origin-03.png", cv::ImreadModes::IMREAD_UNCHANGED);
std::vector<std::vector<cv::Point >> cs;
cs.emplace_back(approxCurve);
cv::drawContours(color, cs, 0, cv::Scalar(255, 255, 255), 1);
std::vector<cv::Point> hullPoints;
cv::convexHull(approxCurve, hullPoints, true, true);
std::vector<std::vector<cv::Point >> hullContours;
hullContours.emplace_back(hullPoints);
cv::drawContours(color, hullContours, 0, cv::Scalar(0, 255, 0), 1);
#endif
//std::vector<std::vector<int>> indices;
//getSubCurves(approxCurve, 150.0, indices);
//for (const auto& indice : indices) {
// if (indice.size() < 3) {
// continue;
// }
// std::vector<std::vector<double>> psArr;
// for (int32_t idx = 0; idx < indice.size(); ++idx) {
// std::vector<double> pt;
// pt.emplace_back(approxCurve[indice[idx]].x);
// pt.emplace_back(approxCurve[indice[idx]].y);
// psArr.emplace_back(pt);
// }
// BSpline spline(psArr, 2);
// for (double t = 0.0; t < 1.0; t += 0.05) {
// std::vector<double> point = spline.eval(t);
// if (point.size() > 1) {
// cv::Point pt;
// pt.x = point[0];
// pt.y = point[1];
// cv::circle(color, pt, 1, cv::Scalar(0, 0, 255), -1);
// }
// }
//}
auto curve = approxCurve;
if (!IS_DOUBLE_ZERO(m_defectThreshold)) {
std::vector<int32_t> hull;
cv::convexHull(approxCurve, hull, true, false);
std::vector<cv::Vec4i> defects;
cv::convexityDefects(approxCurve, hull, defects);
for (const auto& defect : defects) {
cv::Point startPoint = approxCurve[defect[0]];
cv::Point endPoint = approxCurve[defect[1]];
#if DEBUG_CONTOURS_EXTRACTOR
cv::Point farthestPoint = cs[0][defect[2]];
float depth = defect[3] / 256.0;
cv::circle(color, startPoint, 5, cv::Scalar(255, 0, 0), -1);
cv::circle(color, endPoint, 5, cv::Scalar(255, 127, 0), -1);
cv::circle(color, farthestPoint, 5, cv::Scalar(0, 255, 255), -1);
std::string depthText = "Depth: " + std::to_string(depth);
cv::putText(color, depthText, farthestPoint + cv::Point(0, -10), cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(255, 255, 255));
cv::imshow("Convexity Defects", color);
#endif
int start = -1;
int end = -1;
for (int i = 0; i < curve.size(); ++i) {
if (startPoint == curve[i]) {
start = i;
}
if (endPoint == curve[i]) {
end = i;
}
}
if (start != -1 && end != -1 && start != end) {
if (euclideanDistance(startPoint, endPoint) < m_defectThreshold) {
cv::Point connected = (startPoint + endPoint) / 2;
std::vector<cv::Point> sub{ connected };
if (start > end) {
std::swap(start, end);
}
replaceSubsequence(curve, start, end, sub);
}
}
}
}
curve.emplace_back(curve[0]);
std::vector<std::vector<double>> psArr;
for (int i = 0; i < curve.size(); ++i) {
std::vector<double> pt;
pt.emplace_back(curve[i].x);
pt.emplace_back(curve[i].y);
psArr.emplace_back(pt);
}
BSpline spline(psArr, 2);
if (spline.isValid()) {
std::vector<double> weights;
std::vector<cv::Point> points;
for (double t = 0.0; t < 1.0; t += m_smoothStep) {
std::vector<double> point = spline.eval(t);
if (point.size() > 1) {
cv::Point pt;
pt.x = point[0];
pt.y = point[1];
points.emplace_back(pt);
}
}
outputContours.emplace_back(points);
}
else {
outputContours = output;
}
}
else {
outputContours = output;
}
#if DEBUG_CONTOURS_EXTRACTOR
cv::drawContours(alpha, outputContours, 0, cv::Scalar(255, 255, 255), 1);
cv::imshow("alpha", alpha);
cv::waitKey(0);
#endif
}
double ContoursExtractorImpl::calculateAngle(const cv::Point& pt1, const cv::Point& pt2, const cv::Point& pt3)
{
cv::Point vec1 = pt1 - pt2;
cv::Point vec2 = pt3 - pt2;
double dotProduct = vec1.x * vec2.x + vec1.y * vec2.y;
double magnitude1 = std::sqrt(vec1.x * vec1.x + vec1.y * vec1.y);
double magnitude2 = std::sqrt(vec2.x * vec2.x + vec2.y * vec2.y);
double cosTheta = dotProduct / (magnitude1 * magnitude2);
cosTheta = std::clamp<double>(cosTheta, -1.0, 1.0);
return std::acos(cosTheta) * 180.0 / CV_PI;
}
double ContoursExtractorImpl::euclideanDistance(const cv::Point& p1, const cv::Point& p2)
{
return cv::norm(p1 - p2);
}
double ContoursExtractorImpl::curveDistance(const std::vector<cv::Point>& contour, int startIdx, int endIdx)
{
double curveDistance = 0.0;
int n = contour.size();
for (int i = startIdx; i != endIdx; i = (i + 1) % n) {
curveDistance += euclideanDistance(contour[i], contour[(i + 1) % n]);
}
return curveDistance;
}
void ContoursExtractorImpl::getSubCurves(const std::vector<cv::Point>& contour, double thresholdAngle, std::vector<std::vector<int>>& indices)
{
std::set<int> currentGroup;
for (size_t i = 0; i < contour.size(); ++i) {
const cv::Point& pt1 = contour[(i + contour.size() - 1) % contour.size()];
const cv::Point& pt2 = contour[i];
const cv::Point& pt3 = contour[(i + 1) % contour.size()];
double angle = calculateAngle(pt1, pt2, pt3);
if (angle < thresholdAngle) {
currentGroup.insert((i + contour.size() - 1) % contour.size());
currentGroup.insert(i);
currentGroup.insert((i + 1) % contour.size());
}
else if (!currentGroup.empty()) {
std::vector<int> vec;
for (auto it = currentGroup.begin(); it != currentGroup.end(); ++it) {
vec.emplace_back(*it);
}
indices.push_back(vec);
currentGroup.clear();
}
}
if (!currentGroup.empty()) {
std::vector<int> vec;
for (auto it = currentGroup.begin(); it != currentGroup.end(); ++it) {
vec.emplace_back(*it);
}
indices.push_back(vec);
}
}
void ContoursExtractorImpl::getOptimalPoints(const std::vector<cv::Point>& contour, int& bestIdx1, int& bestIdx2, double& bestCurveDist, double& bestLineDist)
{
bestCurveDist = 0.0;
bestLineDist = std::numeric_limits<double>::max();
int n = contour.size();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
double curveDist = curveDistance(contour, i, j);
double lineDist = euclideanDistance(contour[i], contour[j]);
if (curveDist > bestCurveDist || (curveDist == bestCurveDist && lineDist < bestLineDist)) {
bestCurveDist = curveDist;
bestLineDist = lineDist;
bestIdx1 = i;
bestIdx2 = j;
}
}
}
}
void ContoursExtractorImpl::replaceSubsequence(std::vector<cv::Point>& contour, int startIdx, int endIdx, const std::vector<cv::Point>& newSubsequence)
{
int n = contour.size();
if (startIdx < 0 || endIdx < 0 || startIdx >= n || endIdx >= n) {
return;
}
if (startIdx > endIdx) {
return;
}
contour.erase(contour.begin() + startIdx, contour.begin() + endIdx + 1);
contour.insert(contour.begin() + startIdx, newSubsequence.begin(), newSubsequence.end());
}
cv::Point ContoursExtractorImpl::getPointOnLine(const cv::Point& pt1, const cv::Point& pt2, int32_t x)
{
double dx = pt2.x - pt1.x;
double dy = pt2.y - pt1.y;
cv::Point pt;
pt.x = pt1.x + x * dx;
pt.y = pt1.y + x * dy;
return pt;
}
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "i_contours_extractor.hpp"
namespace cvpr
{
class ContoursExtractorImpl : public IContoursExtractor {
public:
ContoursExtractorImpl();
~ContoursExtractorImpl() = default;
void init() override;
void destroy() override;
void setAreaThreshold(double threshold) override;
double areaThreshold() override;
void setContourOffset(int32_t offset) override;
int32_t contourOffset() override;
void setApproxPolyEpsilon(double epsilon) override;
double approxPolyEpsilon() override;
void setSmoothMethod(SMOOTH_METHOD method) override;
SMOOTH_METHOD smoothMethod() override;
void setAngelThreshold(double threshold) override;
double angelThreshold() override;
void setDefectThreshold(double threshold) override;
double defectThreshold() override;
void setSmoothStep(double step) override;
double smoothStep() override;
void extract(const cv::Mat& alpha, std::vector<std::vector<cv::Point>>& outputContours) override;
private:
double calculateAngle(const cv::Point& pt1, const cv::Point& pt2, const cv::Point& pt3);
double euclideanDistance(const cv::Point& p1, const cv::Point& p2);
double curveDistance(const std::vector<cv::Point>& contour, int startIdx, int endIdx);
void getSubCurves(const std::vector<cv::Point>& contour, double thresholdAngle, std::vector<std::vector<int>>& indices);
void getOptimalPoints(const std::vector<cv::Point>& contour, int& bestIdx1, int& bestIdx2, double& bestCurveDist, double& bestLineDist);
void replaceSubsequence(std::vector<cv::Point>& contour, int startIdx, int endIdx, const std::vector<cv::Point>& newSubsequence);
cv::Point getPointOnLine(const cv::Point& pt1, const cv::Point& pt2, int32_t x);
private:
double m_areaThreshold = 500.0;
int32_t m_contourOffset = 12;
double m_approxPolyEpsilon = 5;
SMOOTH_METHOD m_smoothMethod = SMOOTH_METHOD::BSPLINE;
double m_angelThreshold = 20.0;
double m_defectThreshold = 0.0;
double m_smoothStep = 0.001;
};
}

View File

@ -0,0 +1,15 @@
// header.h: 标准系统包含文件的包含文件,
// 或特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
// C 运行时头文件
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>

View File

@ -0,0 +1,17 @@
#pragma once
#include <vector>
namespace cvpr
{
class IBSpline {
public:
virtual ~IBSpline() = default;
virtual void create(const std::vector<std::vector<double>>& points, int degree) = 0;
virtual bool isValid() = 0;
virtual std::vector<double> eval(double t) = 0;
};
}

View File

@ -0,0 +1,79 @@
#pragma once
#include <vector>
#include "opencv2/opencv.hpp"
namespace cvpr
{
enum class SMOOTH_METHOD {
NONE = 0,
BSPLINE
//DEANGEL
};
class __declspec(dllexport) IContoursExtractor {
public:
virtual ~IContoursExtractor() = default;
virtual void init() = 0;
virtual void destroy() = 0;
/*
* threshold,
*/
virtual void setAreaThreshold(double threshold = 500.0) = 0;
virtual double areaThreshold() = 0;
/*
* contourOffset, contourOffset取值为0时setSmoothMethod()method参数
*/
virtual void setContourOffset(int32_t offset = 12) = 0;
virtual int32_t contourOffset() = 0;
/*
* epsilon, 线
*/
virtual void setApproxPolyEpsilon(double epsilon = 5) = 0;
virtual double approxPolyEpsilon() = 0;
/*
* method, 线b样条曲线
*/
virtual void setSmoothMethod(SMOOTH_METHOD method = SMOOTH_METHOD::BSPLINE) = 0;
virtual SMOOTH_METHOD smoothMethod() = 0;
/*
* threshold, 线b样条曲线进行平滑SMOOTH_METHOD::DEANGEL模式下有效
*/
virtual void setAngelThreshold(double threshold = 20.0) = 0;
virtual double angelThreshold() = 0;
/*
* threshold,
*/
virtual void setDefectThreshold(double threshold = 0.0) = 0;
virtual double defectThreshold() = 0;
/*
* smoothStep, 线0.01.0
*/
virtual void setSmoothStep(double step = 0.001) = 0;
virtual double smoothStep() = 0;
/* 描述: 提取轮廓或轮廓偏置线
*
* alpha, alpha通道
* outputContours,
*/
virtual void extract(const cv::Mat& alpha, std::vector<std::vector<cv::Point>>& outputContours) = 0;
};
}

48
examples/contour/main.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <afxwin.h>
#include "targetver.h"
// C runtime header
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <string>
// base header
#include "base/base.h"
// duilib
#include "duilib/UIlib.h"
#include "resource.h"
/** @class MainThread
* @brief 线UI线程 nbase::FrameworkThread
* @copyright (c) 2015, NetEase Inc. All rights reserved
* @author towik
* @date 2015/1/1
*/
class MainThread : public nbase::FrameworkThread
{
public:
MainThread() : nbase::FrameworkThread("MainThread") {}
virtual ~MainThread() {}
private:
/**
* 线
* @return void
*/
virtual void Init() override;
/**
* 线退
* @return void
*/
virtual void Cleanup() override;
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
examples/contour/res.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
examples/contour/small.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,6 @@
#pragma once
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
#include <SDKDDKVer.h>

View File

@ -35,6 +35,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmodbus", "libmodbus\libm
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bsdiff", "bsdiff\bsdiff.vcxproj", "{F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bsdiff", "bsdiff\bsdiff.vcxproj", "{F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "contour", "contour\contour.vcxproj", "{A01DD5D1-1D47-4956-A472-FD84B1AF7036}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32 Debug|Win32 = Debug|Win32
@ -153,6 +155,14 @@ Global
{F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|Win32.Build.0 = Release|Win32 {F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|Win32.Build.0 = Release|Win32
{F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|x64.ActiveCfg = Release|x64 {F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|x64.ActiveCfg = Release|x64
{F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|x64.Build.0 = Release|x64 {F9A0DD24-FD5A-4CCF-8945-3688EF808BFE}.Release|x64.Build.0 = Release|x64
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Debug|Win32.ActiveCfg = Debug|Win32
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Debug|Win32.Build.0 = Debug|Win32
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Debug|x64.ActiveCfg = Debug|x64
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Debug|x64.Build.0 = Debug|x64
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Release|Win32.ActiveCfg = Release|Win32
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Release|Win32.Build.0 = Release|Win32
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Release|x64.ActiveCfg = Release|x64
{A01DD5D1-1D47-4956-A472-FD84B1AF7036}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE