#include "ComponentLabeling.h" CompLabeler::CompLabeler(cv::Mat& binImage, BlobContour** lab, cv::Point start, cv::Point end) : m_binaryImage(binImage) , m_startPoint(start) , m_endPoint(end) { m_parent = NULL; m_labels = lab; m_r = 0; m_c = 0; m_dir = 0; } CompLabeler::~CompLabeler() { } void CompLabeler::reset() { m_blobs.clear(); } void CompLabeler::label() { m_ptrDataBinary = m_binaryImage.data; m_ptrDataLabels = m_labels; m_currentBlob = NULL; m_h = m_binaryImage.size().height; m_w = m_binaryImage.size().width; BlobContour* label = NULL; for (m_r = m_startPoint.y; m_r < m_endPoint.y; ++m_r) { //First col m_pos = m_r * m_w; m_c = m_startPoint.x; if (m_ptrDataBinary[m_pos]) { label = m_ptrDataLabels[m_pos]; if (label) { m_currentBlob = label->m_parent; } //Else if so to not check for label==NULL else if (m_ptrDataBinary[m_pos] /*&& ptrDataLabels[pos]==NULL*/) { m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); m_blobs.push_back(m_currentBlob); tracerExt(); } if (!m_ptrDataBinary[m_pos + 1] && !m_ptrDataLabels[m_pos + 1]) { tracerInt(); } } //Other cols for (m_c = m_startPoint.x+1; m_c < m_endPoint.x-1; ++m_c) { m_pos = m_r * m_w + m_c; if (m_ptrDataBinary[m_pos]) { label = m_ptrDataLabels[m_pos]; if (label != 0) { m_currentBlob = label->m_parent; } else if (!m_ptrDataBinary[m_pos - 1]) { m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); m_blobs.push_back(m_currentBlob); tracerExt(); } if (!m_ptrDataBinary[m_pos+1] && m_ptrDataLabels[m_pos+1] == 0) { tracerInt(); } } } m_pos = m_r * m_w + m_c; //Last column if (m_ptrDataBinary[m_pos]) { label = m_ptrDataLabels[m_pos]; if (label != 0) { m_currentBlob = label->m_parent; } else if (!m_ptrDataBinary[m_pos - 1]) { m_currentBlob = new Blob(m_currentLabel, cv::Point(m_c, m_r), cv::Size(m_w, m_h)); m_blobs.push_back(m_currentBlob); tracerExt(); } } } } void* CompLabeler::threadLabeling(void* o) { CompLabeler* obj = (CompLabeler*)o; obj->label(); return 0; } void CompLabeler::tracerExt() { //Dir: //321 //4-0 //567 //cout << "tracerExt\t" << c<<","<m_externalContour; #ifdef DEBUG_COMPONENT_LABELLING std::cout << "tracerExt\t" << m_c << ", "<< m_r << std::endl; bool debugDraw = true; #endif int sR = m_r, sC = m_c; int startPos = sR * m_w + sC; m_dir = 6; #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 150; //Debug #endif getNextPointCCW(); if (m_singlePixBlob) { m_r = sR; m_c = sC; m_pos = m_r * m_w + m_c; m_ptrDataLabels[m_pos] = m_currentContour; return; } ChainCodeList* cont = &m_currentBlob->m_externalContour.m_contour[0]; while (m_pos != startPos) { #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 150; #endif cont->push_back(m_dir); m_ptrDataLabels[m_pos] = m_currentContour; getNextPointCCW(); #ifdef DEBUG_COMPONENT_LABELLING if (debugDraw) { cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); cv::imshow("im", m_binaryImage); std::stringstream ss; ss << m_c << ", " << m_r; cv::displayOverlay("im", ss.str()); int k = cv::waitKey(); if (k == ' ') { debugDraw = false; } } #endif } cont->push_back(m_dir); m_ptrDataLabels[m_pos] = m_currentContour; //For blobs in which the starting point must be crossed many times for (int i = 0; i < 3; ++i) { getNextPointCCW(); if (m_ptrDataLabels[m_pos] == 0) { #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 150; #endif while (m_pos != startPos) { //cout << r << "," << c << endl; m_ptrDataLabels[m_pos] = m_currentContour; cont->push_back(m_dir); getNextPointCCW(); #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos]= 150; if (debugDraw) { cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); cv::imshow("im", m_binaryImage); int k = cv::waitKey(); if (k == ' ') { debugDraw = false; } } #endif } cont->push_back(m_dir); m_ptrDataLabels[m_pos] = m_currentContour; } else { m_r = sR; m_c = sC; m_pos = m_r * m_w + m_c; break; } } } void CompLabeler::tracerInt(int startDir /*= 5*/) { //Dir: //321 //4-0 //567 //cout << "tracerInt\t" << c<<","<m_parent = m_currentBlob; ChainCodeList* cont = &m_currentContour->m_contour[0]; m_dir = startDir; #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 50; #endif getNextPointCW(); //uchar firstDir = m_dir; #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 100; #endif cont->push_back(m_dir); m_ptrDataLabels[m_pos] = m_currentContour; while (m_pos != startPos) { // cout << r << "," << c << endl; getNextPointCW(); m_ptrDataLabels[m_pos] = m_currentContour; cont->push_back(m_dir); #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 100; if (debugDraw) { cv::namedWindow("im", CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); cv::imshow("im", m_binaryImage); std::stringstream ss; ss << m_c << ", " << m_r; cv::displayOverlay("im", ss.str()); int k = cv::waitKey(); if (k == ' ') { debugDraw = false; } } #endif } //For internal contours in which the starting point must be crossed many times, like: // ooooooooooooooo // ooooos o // ooooo o o // ooooo o o // ooooo o // ooooooooooooooo for (int i = 0; i < 3; ++i) { getNextPointCW(); //In tracerExt i check for label==0, beacuse there can not be situations in which a pixel belongs to 2 contours. //This can happen with internal contorus, so I have to modify the condition. if (m_ptrDataLabels[m_pos] != m_currentContour) { #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 100; #endif while (m_pos != startPos) { m_ptrDataLabels[m_pos] = m_currentContour; cont->push_back(m_dir); getNextPointCW(); #ifdef DEBUG_COMPONENT_LABELLING m_ptrDataBinary[m_pos] = 100; if (debugDraw) { cv::namedWindow("im",CV_WINDOW_NORMAL + CV_GUI_EXPANDED + CV_WINDOW_KEEPRATIO); cv::imshow("im", m_binaryImage); int k = cv::waitKey(); if (k == ' ') { debugDraw = false; } } #endif } cont->push_back(m_dir); m_ptrDataLabels[m_pos] = m_currentContour; } else { m_r = sR; m_c = sC; m_pos = m_r * m_w + m_c; break; } } //If labeler has m_parent it means that I'm using more than 1 thread. //Then I must check for collisions when adding internal contours to the same blob. if (m_parent) { std::lock_guard lock(m_parent->m_mutexBlob); //m_parent->acquireMutex(); m_currentBlob->m_internalContours.push_back(m_currentContour); //m_parent->releaseMutex(); } else { m_currentBlob->m_internalContours.push_back(m_currentContour); } } void CompLabeler::getNextPointCCW() { //Dir: //321 //4-0 //567 m_dir = (m_dir + 6) % 8; int i = 0; for (; i < 8; i++, m_dir = (m_dir + 1) % 8) { m_tempR = m_r + m_freemanR[m_dir]; m_tempC = m_c + m_freemanC[m_dir]; if (!(m_tempR < 0 || m_tempR >= m_h || m_tempC < 0 || m_tempC >= m_w)) { m_pos = m_tempR * m_w + m_tempC; if (m_ptrDataBinary[m_pos]) { m_r = m_tempR; m_c = m_tempC; break; } m_ptrDataLabels[m_pos] = m_currentContour; } } m_singlePixBlob = i == 8; m_pos = m_r * m_w + m_c; /* // I tried to pre-create all the various direction vectors in order to avoid checking too many times for the boundary of the image. // However, there was no gain in performance. //It's useless (in terms of performance) to create the direction vectors according to all cases. 8 directions cases: 0-r=0 1-r=0,c=0 2-r=0,c=w 3-r=h 4-r=h,c=0 5-r=h,c=w 6-c=0 7-c=w 8-otherwise total:9 */ //static uchar directions[8][9][8] = {{{0,4,5,6,7,0,0,0},{0,6,7,0,0,0,0,0},{5,6,7,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{0,1,2,6,7,0,0,0},{2,3,4,5,6,0,0,0},{0,1,2,3,4,5,6,7}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{1,2,3,4,0,0,0,0},{1,2,0,0,0,0,0,0},{2,3,4,0,0,0,0,0},{1,2,6,7,0,0,0,0},{2,3,4,5,6,0,0,0},{1,2,3,4,5,6,7,0}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{2,3,4,0,1,0,0,0},{2,0,1,0,0,0,0,0},{2,3,4,0,0,0,0,0},{2,6,7,0,1,0,0,0},{2,3,4,5,6,0,0,0},{2,3,4,5,6,7,0,1}},{{4,5,6,7,0,0,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{3,4,0,1,2,0,0,0},{0,1,2,0,0,0,0,0},{3,4,2,0,0,0,0,0},{6,7,0,1,2,0,0,0},{3,4,5,6,2,0,0,0},{3,4,5,6,7,0,1,2}},{{4,5,6,7,0,4,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{4,0,1,2,3,0,0,0},{0,1,2,0,0,0,0,0},{4,2,3,0,0,0,0,0},{6,7,0,1,2,0,0,0},{4,5,6,2,3,0,0,0},{4,5,6,7,0,1,2,3}},{{5,6,7,0,4,5,0,0},{6,7,0,0,0,0,0,0},{5,6,7,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{6,7,0,1,2,0,0,0},{5,6,2,3,4,0,0,0},{5,6,7,0,1,2,3,4}},{{6,7,0,4,5,6,0,0},{6,7,0,0,0,0,0,0},{6,7,5,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{6,7,0,1,2,0,0,0},{6,2,3,4,5,0,0,0},{6,7,0,1,2,3,4,5}},{{7,0,4,5,6,7,0,0},{7,0,6,0,0,0,0,0},{7,5,6,0,0,0,0,0},{0,1,2,3,4,0,0,0},{0,1,2,0,0,0,0,0},{2,3,4,0,0,0,0,0},{7,0,1,2,6,0,0,0},{2,3,4,5,6,0,0,0},{7,0,1,2,3,4,5,6}}}; //static uchar nDirs[8][9] = {{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8},{5,3,3,5,3,3,5,5,8}}; //static uchar* dirVec; //int p =-1; //if (r== 0) { // if (c= =0) // p = 1; // else if (c == (w-1)) // p = 2; // else // p = 0; //} //else if (r== (h-1)) { // if (c == 0) // p = 4; // else if (c == (w-1)) // p = 5; // else // p = 3; //} //else if (c == 0) // p = 6; //else if (c == (w-1)) // p = 7; //else // p = 8; // //dirVec = directions[dir][p]; //int i = 0; //int d = dir; //for (i; i < nDirs[d][p]; ++i) { // tempR = r + freemanR[dirVec[i]]; // tempC = c + freemanC[dirVec[i]]; // pos = tempR*w +tempC; // if (ptrDataBinary[pos]) { // r = tempR; // c = tempC; // dir = dirVec[i]; // break; // } // ptrDataLabels[pos] = (int)currentBlob; //} //singlePixBlob = i == nDirs[d][p]; //pos = r*w+c; } void CompLabeler::getNextPointCW() { //Dir: //321 //4-0 //567 m_dir = (m_dir + 2) % 8; for (int i = 0; i < 8; ++i) { m_tempR = m_r + m_freemanR[m_dir]; m_tempC = m_c + m_freemanC[m_dir]; if (!(m_tempR < 0 || m_tempR >= m_h || m_tempC < 0 || m_tempC >= m_w)) { m_pos = m_tempR * m_w + m_tempC; if (m_ptrDataBinary[m_pos]) { m_r = m_tempR; m_c = m_tempC; break; } m_ptrDataLabels[m_pos] = m_currentContour; } m_dir--; m_dir = m_dir % 8; //To cycle through values 7->0 } m_pos = m_r * m_w + m_c; } int CompLabeler::m_freemanC[8] = {1, 1, 0, -1, -1, -1, 0, 1}; int CompLabeler::m_freemanR[8] = {0, -1, -1, -1, 0, 1, 1, 1}; void CompLabelerGroup::doLabeling(BlobVector& blobs) { LabelID label = 0; if (m_numThreads > 1) { //Preliminary step in order to pre-compute all the blobs crossing the border for (int i = 1; i < m_numThreads; i++) { cv::Point offset(m_img.size().width, 1); CompLabeler lbl(m_img, m_labels, m_labelers[i]->m_startPoint, m_labelers[i]->m_startPoint + offset); lbl.m_parent = this; lbl.label(); //cout << "Single pass\t" << lbl.blobs.size()<setID(label); label++; blobs.push_back(lbl.m_blobs[i]); } } std::vector ths; for (int i = 0; i < m_numThreads; i++) { //pthread_create(&tIds[i], NULL, CompLabeler::thread_Labeling, labelers[i]); ths.emplace_back(std::thread([label = m_labelers[i]]() { CompLabeler::threadLabeling(label); })); } for (int i = 0; i < m_numThreads; i++) { //pthread_join(tIds[i], 0); if (ths[i].joinable()) { ths[i].join(); } } } else { m_labelers[0]->label(); } for (int i = 0; i < m_numThreads; ++i) { //cout << "MT pass " <blobs.size()<m_blobs.size(); ++j) { m_labelers[i]->m_blobs[j]->setID(label); label++; blobs.push_back(m_labelers[i]->m_blobs[j]); } } } CompLabelerGroup::CompLabelerGroup() { //m_mutexBlob = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; m_labels = NULL; m_labelers = NULL; //m_tIds = NULL; } CompLabelerGroup::~CompLabelerGroup() { if (m_labelers) { for (int i = 0; i < m_numThreads; ++i) { delete m_labelers[i]; } delete []m_labelers; } //if (m_tIds) { // delete [] m_tIds; //} if (m_labels) { delete [] m_labels; } } void CompLabelerGroup::set(int nThreads, const cv::Mat& binImg) { this->m_numThreads = nThreads; if (binImg.isContinuous()) { this->m_img = binImg; } else { this->m_img = binImg.clone(); } //this->labels = Mat_::zeros(img.size()); if (m_labels) { delete [] m_labels; } int nPts = binImg.size().width * binImg.size().height; m_labels = new BlobContour*[nPts]; memset(m_labels, 0, nPts*sizeof(Blob*)); //if (m_tIds) { // delete [] m_tIds; //} if (m_labelers) { for (int i = 0; i < nThreads; ++i) { delete m_labelers[i]; } delete [] m_labelers; } //m_tIds = new pthread_t[nThreads]; m_labelers = new CompLabeler*[nThreads]; cv::Size sz = binImg.size(); //int numPx = sz.width*sz.width; int i = 0; for (; i < nThreads-1; ++i) { int yStart = (int)((float)i / nThreads * sz.height); int yEnd = (int)((float)(i + 1) / nThreads * sz.height); cv::Point st(0, yStart); cv::Point en(sz.width, yEnd); m_labelers[i] = new CompLabeler(m_img, m_labels, st, en); m_labelers[i]->m_parent = this; } cv::Point st(0, (int)((float)i / nThreads * sz.height)); cv::Point en(sz.width, sz.height); //st = Point(0,187); m_labelers[i] = new CompLabeler(m_img, m_labels, st, en); m_labelers[i]->m_parent = this; } void CompLabelerGroup::reset() { if (m_labels) { delete [] m_labels; m_labels = NULL; } for (int i = 0; i < m_numThreads; ++i) { m_labelers[i]->reset(); } } void CompLabelerGroup::acquireMutex() { //pthread_mutex_lock(&m_mutexBlob); } void CompLabelerGroup::releaseMutex() { //pthread_mutex_unlock(&m_mutexBlob); }