OpenCV. Морфологическая реконструкция. Удаление объектов на границе изображения

в 21:39, , рубрики: Песочница, метки:

Привет, Habr!

Недавно начал изучать либу OpenCV, не обнаружил там некоторых нужных (имхо) функции на уровне предварительной обработки.
Решил исправить пробел, представлю их здесь. На полноту не претендую, функции рассчитаны на работу с изображением в градациях серого.
Пока две функции — морфологическая реконструкция и удаление объектов на границе изображения.
Если кому понравится положу еще утолщение без замыкания и утончение до остова.

Функция восстановления изображения (морфологическая реконструкция).

// восстановление изобр 
	// src - вх массив изобр
	// dst - вых массив изобр
	// mask - исход массив изобр
	// minBright - порог по яркости
void ImgProcAuxFunc::Reconstruct(cv::Mat& src, cv::Mat& dst, cv::Mat& mask, int minBright)
{	
	// обходим вх изобр
	for (int j = 0; j < src.rows; j++)
	{				
		const uchar* curr =	src.ptr<const uchar>(j); 
		const uchar* res =	dst.ptr<const uchar>(j); 
			
		for (int i = 0; i < src.cols; i++) {

			if ((curr[i] >= minBright) && (curr[i] != res[i]))
				RoundLinkObj(mask, dst, cv::Point2i(i, j), minBright); 
			
		}
	}
}

Функция удаления объектов находящихся на границе изображения.

// удаление объектов наход на границе
	// src - вх массив изобр
	// dst - вых массив изобр
	// border - граница изобр
void ImgProcAuxFunc::ClearObjOnBorder(cv::Mat& src, cv::Mat& dst, BorderImg border)
{	

	auto rndObj = [this](cv::Mat& src, cv::Mat& auxRes, int stRow, int endRow, int stCol, int endCol){
					
		for (int j = stRow; j < endRow; j++) {

			const uchar* curr =	src.ptr<const uchar>(j); 
			const uchar* res =	auxRes.ptr<const uchar>(j); 

			for (int i = stCol; i < endCol; i++) {

				if (curr[i] != res[i])
					RoundLinkObj(src, auxRes, cv::Point2i(i, j)); 
			}
		}
		
	};
	
	cv::Mat auxRes(src.size(), src.type(), cv::Scalar(0));

	switch (border)
	{
			case down:
				rndObj(src, auxRes, src.rows - 1, src.rows, 0, src.cols);
				dst = src ^ auxRes;
			break;

			case up:
				rndObj(src, auxRes, 0, 1, 0, src.cols);
				dst = src ^ auxRes;
				break;

			case left:
				rndObj(src, auxRes, 0, src.rows, 0, 1);
				dst = src ^ auxRes;
				break;

			case right:
				rndObj(src, auxRes, 0, src.rows, src.cols - 1, src.cols);
				dst = src ^ auxRes;
				break;

			case all:
				rndObj(src, auxRes, src.rows - 1, src.rows, 0, src.cols);
				rndObj(src, auxRes, 0, 1, 0, src.cols);
				rndObj(src, auxRes, 0, src.rows, 0, 1);
				rndObj(src, auxRes, 0, src.rows, src.cols - 1, src.cols);
				dst = src ^ auxRes;
				break;
	}
			
}

Вспом функция — выделение связанных областей. Ничего нового, аналог ф-ии floodFill.

// обход связанного объекта изобр
	// src - вх массив изобр
	// startPnt - старт точка обхода
	// minBright - порог яркости для включения 
	// isFourConn - связность пкс (true - 4 связн)
void ImgProcAuxFunc::RoundLinkObj(cv::Mat& src, cv::Mat& dst, cv::Point2i& startPnt,
	int minBright, bool isFourConn)
{
	std::vector<cv::Point2i> pntList = std::vector<cv::Point2i>();
		
	// первый проход по линии начал точки //
	RoundLine(src, dst, startPnt, pntList, minBright, isFourConn);
				
	// проходим по получен списку //
    while(!pntList.empty())
	{
		cv::Point2i nextPnt(pntList.back());
				
		pntList.pop_back();
		
		RoundLine(src, dst, nextPnt, pntList, minBright, isFourConn);
	}
	
}     

Вспом функция — обход гориз линии.

// обход связанной гориз линии на изобр
	// src - вх массив изобр
	// dst - вых массив изобр
	// startPnt - старт точка обхода
	// slist - список пикс, по кот идем 
	// minBright - порог яркости
    // isFourConn - связность пкс (true - 4 связн)
void ImgProcAuxFunc::RoundLine(cv::Mat& src, cv::Mat& dst, cv::Point2i& startPnt, std::vector<cv::Point2i>& slist,
								int minBright, bool isFourConn)
{
	bool isUpEna = ((startPnt.y + 1) < src.rows) ? true : false; // низ ?
	bool isDownEna = ((startPnt.y - 1) >= 0) ? true : false; // верх ?

	const uchar* currSrc = src.ptr<const uchar>(startPnt.y); 
	const uchar* upSrc = isUpEna ? src.ptr<const uchar>(startPnt.y + 1) : src.ptr<const uchar>(startPnt.y); 
	const uchar* downSrc = isDownEna ? src.ptr<const uchar>(startPnt.y - 1) : src.ptr<const uchar>(startPnt.y); 

	uchar* currDst = dst.ptr<uchar>(startPnt.y); 
	uchar* upDst = isUpEna ? dst.ptr<uchar>(startPnt.y + 1) : dst.ptr<uchar>(startPnt.y); 
	uchar* downDst = isDownEna ? dst.ptr<uchar>(startPnt.y - 1) : dst.ptr<uchar>(startPnt.y); 
	
	bool isPxlUp = false;  // один пкс на строку сверху уже добавлен
	bool isPxlDown = false; // один пкс на строку снизу уже добавлен

	// смотрим старт точку
	if (currSrc[startPnt.x] < minBright) return; 

	// идем влево, смотрим вниз и вверх, попавшихся соседей добавляем в промеж список
	for (int i = startPnt.x; i >= 0; i--) {

		// отмечаем тек точку
		currDst[i] = currSrc[i];
		
		// смотрим вниз
		if (isDownEna)
		if (downSrc[i] < minBright) isPxlDown = false;
		else if (!isPxlDown && (downDst[i] != downSrc[i])){
			downDst[i] = downSrc[i];
			slist.push_back(cv::Point2i(i, startPnt.y - 1));
			isPxlDown = true;
		}
		
		
		// смотрим вверх
		if (isUpEna)
		if (upSrc[i] < minBright) isPxlUp = false;
		else if (!isPxlUp && (upDst[i] != upSrc[i])){
			upDst[i] = upSrc[i];
			slist.push_back(cv::Point2i(i, startPnt.y + 1));
			isPxlUp = true;
		}
		

		// выходим, если на границе
		if (i == 0) break;

		// смотрим влево
		if (currSrc[i - 1] < minBright)
		{
			if (!isFourConn)
			{
					// смотрим влево-вниз
					if (isDownEna)
						if (!isPxlDown && (downSrc[i - 1] >= minBright) && (downDst[i - 1] != downSrc[i - 1])) 
						{
							downDst[i - 1] = downSrc[i - 1];
							slist.push_back(cv::Point2i(i - 1, startPnt.y - 1));
						}
					// смотрим влево-вверх
					if (isUpEna)
						if (!isPxlUp && (upSrc[i - 1] >= minBright) && (upDst[i - 1] != upSrc[i - 1]))
						{
							upDst[i - 1] = upSrc[i - 1];
							slist.push_back(cv::Point2i(i - 1, startPnt.y + 1));
						}
			}
			// выходим 
			break;
		}
	}

	if (downSrc[startPnt.x] >= minBright) isPxlDown = true;  // один пкс на строку сверху уже добавлен
	else isPxlDown = false;

	if (upSrc[startPnt.x] >= minBright) isPxlUp = true; // один пкс на строку снизу уже добавлен
	else isPxlUp = false;

	// идем вправо, смотрим вниз и вверх, попавшихся соседей добавляем в промеж список
	for (int i = startPnt.x; i < src.cols; i++) {

		// отмечаем тек точку
		currDst[i] = currSrc[i];
		
		// смотрим вниз
		if (isDownEna)
		if (downSrc[i] < minBright) isPxlDown = false;
		else if (!isPxlDown && (downDst[i] != downSrc[i])){
			downDst[i] = downSrc[i];
			slist.push_back(cv::Point2i(i, startPnt.y - 1));
			isPxlDown = true;
		}
		
		
		// смотрим вверх
		if (isUpEna)
		if (upSrc[i] < minBright) isPxlUp = false;
		else if (!isPxlUp && (upDst[i] != upSrc[i])){
			upDst[i] = upSrc[i];
			slist.push_back(cv::Point2i(i, startPnt.y + 1));
			isPxlUp = true;
		}
		
		// выходим, если на границе
		if (i == src.cols - 1) break;

		// смотрим вправо
		if (currSrc[i + 1] < minBright)
		{
			if (!isFourConn)
			{
					// смотрим вправо-вниз
					if (isDownEna)
						if (!isPxlDown && (downSrc[i + 1] >= minBright) && (downDst[i + 1] != downSrc[i + 1]))
						{
							downDst[i + 1] = downSrc[i + 1];
							slist.push_back(cv::Point2i(i + 1, startPnt.y - 1));
						}

					// смотрим вправо-вверх
					if (isUpEna)
						if (!isPxlUp && (upSrc[i + 1] >= minBright) && (upDst[i + 1] != upSrc[i + 1]))
						{
							upDst[i + 1] = upSrc[i + 1];
							slist.push_back(cv::Point2i(i + 1, startPnt.y + 1));
						}
			}
			// выходим 
			break;
		}
	}	
	
}


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js