- PVSM.RU - https://www.pvsm.ru -

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

Привет, 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;
		}
	}	
	
}


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/pesochnitsa/88934