Intel® Integrated Performance Primitives
Deliberate problems developing high-performance vision, signal, security, and storage applications.

Morphological filters problem

krzysztofpiotrowski
741 Views

Hello,

I am using IPP 2018 update 1 as single-threaded static library on Visual Studio 2015. My runtime environments is Intel® Core™ i7-3770 CPU @ 3.40GHz on Windows 7.

I found a problem with the following morphological filters:

  • ippiMorphOpenBorder_32f_C1R,
  • ippiMorphTophatBorder_32f_C1R.

Could you please confirm the problem and tell when you could fix it?

For your information - I have to use top-hat operation on large images with large filters, therefore I have to perform the analysis using tiling strategy. The sizes of the data in the sample source codes were reduced to show the essence of the problem.

I tested above functions using 3 scenarios:

  1. processing the whole image by using ippBorderRepl option,
  2. processing the whole image by using ippBorderInMem (border is replicated manually),
  3. processing the image in two separate halves by using ippBorderInMem (border is replicated manually).

It seems that for both functions open and top-hat the results in the first and the second scenario are proper but in the case of the last scenario results are invalid.

Below thare is a screen shot with results obtained from the test application (invalid data are marked by the red color):

results.png

I also attached the calculations performed in the MS Excel just for the reference.609260

The source code which I used is listed below (please note that I removed checking of ippStatus to increase the readability of the codes):

#include <ipp.h>
#include <vector>
#include <iostream>

const int borderSize = 1;
const int width = 3;
const int height = 4;
const int halfHeight = height / 2;
const int srcWithBorderWidth = width + 2 * borderSize;
const int srcWithBorderHeight = height + 2 * borderSize;

const int srcStep = width * sizeof(Ipp32f);
const int srcWithBorderStep = srcWithBorderWidth * sizeof(Ipp32f);
const int dstStep = width * sizeof(Ipp32f);

const IppiSize maskSize{ 3, 3 };
const IppiSize fullRoiSize{ width, height };
const IppiSize halfRoiSize{ width, halfHeight };

Ipp8u mask[]
{
	1, 1, 1,
	1, 1, 1,
	1, 1, 1
};

const auto s00 = 0.1653f;
const auto s10 = 0.1671f;
const auto s20 = 0.1729f;
const auto s01 = 0.1213f;
const auto s11 = 0.1319f;
const auto s21 = 0.1604f;
const auto s02 = 0.1114f;
const auto s12 = 0.1332f;
const auto s22 = 0.1898f;
const auto s03 = 0.1029f;
const auto s13 = 0.1226f;
const auto s23 = 0.1737f;

const Ipp32f src[]
{
	s00, s10, s20,
	s01, s11, s21,
	s02, s12, s22,
	s03, s13, s23
};

const Ipp32f srcWithBorder[]
{
	s00, s00, s10, s20, s20,
	s00, s00, s10, s20, s20,
	s01, s01, s11, s21, s21,
	s02, s02, s12, s22, s22,
	s03, s03, s13, s23, s23,
	s03, s03, s13, s23, s23
};

Ipp32f dst[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

auto srcWithBorderBegin = srcWithBorder + borderSize * srcWithBorderWidth + borderSize;
auto srcWithBorderHalf = srcWithBorderBegin + halfHeight * srcWithBorderWidth;
auto dstHalf = dst + halfHeight * width;

std::vector<Ipp8u> spec;
std::vector<Ipp8u> buffer;

void printResult(std::string title)
{
	std::cout << title.c_str() << std::endl;
	auto data = dst;
	for (auto y = 0; y < height; ++y)
	{
		for (auto x = 0; x < width; ++x)
			std::cout << *data++ << "\t";
		std::cout << std::endl;
	}
	std::cout << std::endl;
}

void init(const IppiSize roiSize)
{
	int specSize = 0;
	int bufferSize = 0;
	ippiMorphAdvGetSize_32f_C1R(roiSize, maskSize, &specSize, &bufferSize);
	spec.resize(specSize);
	buffer.resize(bufferSize);
	ippiMorphAdvInit_32f_C1R(roiSize, mask, maskSize, (IppiMorphAdvState*)spec.data(), buffer.data());
}

void open(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSize roiSize, const IppiBorderType border)
{
	ippiMorphOpenBorder_32f_C1R(src, srcStep, dst, dstStep, roiSize, border, 0, (IppiMorphAdvState*)spec.data(), buffer.data());
}

void topHat(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSize roiSize, const IppiBorderType border)
{
	ippiMorphTophatBorder_32f_C1R(src, srcStep, dst, dstStep, roiSize, border, 0, (IppiMorphAdvState*)spec.data(), buffer.data());
}

void testOpenWholeReplBorder()
{
	init(fullRoiSize);
	open(src, srcStep, dst, dstStep, fullRoiSize, ippBorderRepl);
	printResult("testOpenWholeReplBorder");
}

void testOpenWholeInMemBorder()
{
	init(fullRoiSize);
	open(srcWithBorderBegin, srcWithBorderStep, dst, dstStep, fullRoiSize, ippBorderInMem);
	printResult("testOpenWholeInMemBorder");
}

void testOpenTilesInMemBorder()
{
	init(halfRoiSize);
	open(srcWithBorderBegin, srcWithBorderStep, dst, dstStep, halfRoiSize, ippBorderInMem);
	open(srcWithBorderHalf, srcWithBorderStep, dstHalf, dstStep, halfRoiSize, ippBorderInMem);
	printResult("testOpenTilesInMemBorder");
}

void testTopHatWholeReplBorder()
{
	init(fullRoiSize);
	topHat(src, srcStep, dst, dstStep, fullRoiSize, ippBorderRepl);
	printResult("testTopHatWholeReplBorder");
}

void testTopHatWholeInMemBorder()
{
	init(fullRoiSize);
	topHat(srcWithBorderBegin, srcWithBorderStep, dst, dstStep, fullRoiSize, ippBorderInMem);
	printResult("testTopHatWholeInMemBorder");
}

void testTopHatTilesInMemBorder()
{
	init(halfRoiSize);
	topHat(srcWithBorderBegin, srcWithBorderStep, dst, dstStep, halfRoiSize, ippBorderInMem);
	topHat(srcWithBorderHalf, srcWithBorderStep, dstHalf, dstStep, halfRoiSize, ippBorderInMem);
	printResult("testTopHatTilesInMemBorder");
}

int main(int, char*)
{
	ippInit();

	const IppLibraryVersion* lib = ippiGetLibVersion();
	std::cout << lib->Name << "\t" << lib->Version << "\t" << lib->BuildDate << std::endl << std::endl;

	testOpenWholeReplBorder();
	testOpenWholeInMemBorder();
	testOpenTilesInMemBorder();

	testTopHatWholeReplBorder();
	testTopHatWholeInMemBorder();
	testTopHatTilesInMemBorder();

	return 0;
}

 

0 Kudos
7 Replies
krzysztofpiotrowski
741 Views

Hello,

I've performed another test with transposed data and vertical tiles (in the previous post horizontal ones were used).

The results which I got are as follows (invalid data are marked by the red color):

results2.png

If you whish to reproduce the results you can use source codes from the previous post with changed some lines on the beginning as follows:

#include <ipp.h>
#include <vector>
#include <iostream>

const int borderSize = 1;
const int width = 4;
const int height = 3;
const int halfWidth = width / 2;
const int srcWithBorderWidth = width + 2 * borderSize;
const int srcWithBorderHeight = height + 2 * borderSize;

const int srcStep = width * sizeof(Ipp32f);
const int srcWithBorderStep = srcWithBorderWidth * sizeof(Ipp32f);
const int dstStep = width * sizeof(Ipp32f);

const IppiSize maskSize{ 3, 3 };
const IppiSize fullRoiSize{ width, height };
const IppiSize halfRoiSize{ halfWidth, height };

Ipp8u mask[]
{
	1, 1, 1,
	1, 1, 1,
	1, 1, 1
};

const auto s00 = 0.1653f;
const auto s10 = 0.1671f;
const auto s20 = 0.1729f;
const auto s01 = 0.1213f;
const auto s11 = 0.1319f;
const auto s21 = 0.1604f;
const auto s02 = 0.1114f;
const auto s12 = 0.1332f;
const auto s22 = 0.1898f;
const auto s03 = 0.1029f;
const auto s13 = 0.1226f;
const auto s23 = 0.1737f;

const Ipp32f src[]
{
	s00, s01, s02, s03,
	s11, s11, s12, s13,
	s21, s21, s21, s23
};

const Ipp32f srcWithBorder[]
{
	s00, s00, s01, s02, s03, s03,
	s00, s00, s01, s02, s03, s03,
	s11, s11, s11, s12, s13, s13,
	s21, s21, s21, s21, s23, s23,
	s21, s21, s21, s21, s23, s23,
};

Ipp32f dst[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

auto srcWithBorderBegin = srcWithBorder + borderSize * srcWithBorderWidth + borderSize;
auto srcWithBorderHalf = srcWithBorderBegin + halfWidth;
auto dstHalf = dst + halfWidth;

...

I suspect that ippiMorphOpenBorder_32f_C1R and ippiMorphTophatBorder_32f_C1R always replicates the upper and the left border even if the parameters is set to ippBorderInMem. The lower and the right borders are probably taken in respect of the passed parameter. Could you please confirm the above hypothesis? Please note that the problem is very important and time critical for us.

Thank you in advance for your support.

0 Kudos
krzysztofpiotrowski
741 Views

Hello again,

It seems that the problem is wider. I confirm invalid calculation also for functions:

  • ippiMorphOpenBorder_16u_C1R,
  • ippiMorphOpenBorder_16s_C1R,
  • ippiMorphOpenBorder_8u_C1R,
  • ippiMorphTophatBorder_16u_C1R,
  • ippiMorphTophatBorder_16s_C1R,
  • ippiMorphTophatBorder_8u_C1R.

I suspect that the problem exists for multichannel types also but I didn't check it.

For Ipp16u/Ipp16s I used the following source data:

const auto s00 = 1653;
const auto s10 = 1671;
const auto s20 = 1729;
const auto s01 = 1213;
const auto s11 = 1319;
const auto s21 = 1604;
const auto s02 = 1114;
const auto s12 = 1332;
const auto s22 = 1898;
const auto s03 = 1029;
const auto s13 = 1226;
const auto s23 = 1737;

and for Ipp8u data were as follows:

const auto s00 = 165;
const auto s10 = 167;
const auto s20 = 172;
const auto s01 = 121;
const auto s11 = 131;
const auto s21 = 160;
const auto s02 = 111;
const auto s12 = 133;
const auto s22 = 189;
const auto s03 = 102;
const auto s13 = 122;
const auto s23 = 173;

For above data I receive the following results for horizontal tiles:

results4.png

and for vertical tiles:

results3.png

Thank you in advance for your response.

0 Kudos
Andrey_B_Intel
Employee
741 Views

Hello Krzysztof.

Thanks for use of IPP library.

In your sample ippiMorphOpenBorder function is used. It is 2 staged function. First stage is Erode, second is Dilate. Every stage requires half-mask size of pixels in memory when borderType is "BorderInMem". Therefore ippiMorphOpenBorder requires 2x times more pixels in memory.

For your example it is necessary to use next offset srcWithBorderBegin2 = srcWithBorder + 2*borderSize * srcWithBorderWidth + 2*borderSize.  

 In latest IPP releases we developed so-named "_L" API for Morphology operations. This API supports new concept of one and two cascade borders. The keyword is ippBorderFirstStageInMem.

https://software.intel.com/en-us/ipp-dev-reference-user-defined-border-types

https://software.intel.com/en-us/ipp-dev-reference-morphopen

And I recommend to use this "_L" API. 

Thanks for your question. 

0 Kudos
krzysztofpiotrowski
741 Views

Hello Andrey,

Thank you very much for quick answer. I checked the "L" API and used additional border offset as you suggested. Unfortunatelly I still obtain invalid results but different than before.

For border ippBorderFirstStageInMem | ippBorderInMem i have the following results (invalid are marked by red color):

results5.png

Below you can see the modified source code which I used to generete the above results. Could you please tell me what I am doing wrong? Or maybe it is not possible to apply the tiling strategy to two stages function like Open or TopHat? Thank you in advance for your help.

#include <ipp.h>
#include <vector>
#include <iostream>

const int borderSize = 1;
const int width = 3;
const int height = 4;
const int halfHeight = height / 2;
const int srcWithBorderWidth = width + 2 * borderSize;
const int srcWithBorderHeight = height + 2 * borderSize;
const int srcWithBorder2Width = width + 4 * borderSize;
const int srcWithBorder2Height = height + 4 * borderSize;

const int srcStep = width * sizeof(Ipp32f);
const int srcWithBorderStep = srcWithBorderWidth * sizeof(Ipp32f);
const int srcWithBorder2Step = srcWithBorder2Width * sizeof(Ipp32f);
const int dstStep = width * sizeof(Ipp32f);

const IppiSize maskSize{ 3, 3 };
const IppiSize fullRoiSize{ width, height };
const IppiSizeL maskSizeL{ maskSize.width, maskSize.height };
const IppiSizeL halfRoiSizeL{ width, halfHeight };

Ipp8u mask[]
{
	1, 1, 1,
	1, 1, 1,
	1, 1, 1
};

const auto s00 = 0.1653f;
const auto s10 = 0.1671f;
const auto s20 = 0.1729f;
const auto s01 = 0.1213f;
const auto s11 = 0.1319f;
const auto s21 = 0.1604f;
const auto s02 = 0.1114f;
const auto s12 = 0.1332f;
const auto s22 = 0.1898f;
const auto s03 = 0.1029f;
const auto s13 = 0.1226f;
const auto s23 = 0.1737f;

const Ipp32f src[]
{
	s00, s10, s20,
	s01, s11, s21,
	s02, s12, s22,
	s03, s13, s23
};

const Ipp32f srcWithBorder2[]
{
	s00, s00, s00, s10, s20, s20, s20,
	s00, s00, s00, s10, s20, s20, s20,
	s00, s00, s00, s10, s20, s20, s20,
	s01, s01, s01, s11, s21, s21, s21,
	s02, s02, s02, s12, s22, s22, s22,
	s03, s03, s03, s13, s23, s23, s23,
	s03, s03, s03, s13, s23, s23, s23,
	s03, s03, s03, s13, s23, s23, s23,
};

Ipp32f dst[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

auto srcWithBorder2Begin = srcWithBorder2 + 2 * borderSize * srcWithBorder2Width + 2 * borderSize;
auto srcWithBorder2Half = srcWithBorder2Begin + halfHeight * srcWithBorder2Width;
auto dstHalf = dst + halfHeight * width;

std::vector<Ipp8u> spec;
std::vector<Ipp8u> buffer;

std::vector<Ipp8u> specL;
std::vector<Ipp8u> bufferL;

constexpr auto mixedBorder = IppiBorderType(ippBorderFirstStageInMem | ippBorderInMem);

void printResult(std::string title)
{
	std::cout << title.c_str() << std::endl;
	auto data = dst;
	for (auto y = 0; y < height; ++y)
	{
		for (auto x = 0; x < width; ++x)
			std::cout << *data++ << "\t";
		std::cout << std::endl;
	}
	std::cout << std::endl;
}

void init(const IppiSize roiSize)
{
	int specSize = 0;
	int bufferSize = 0;
	ippiMorphAdvGetSize_32f_C1R(roiSize, maskSize, &specSize, &bufferSize);
	spec.resize(specSize);
	buffer.resize(bufferSize);
	ippiMorphAdvInit_32f_C1R(roiSize, mask, maskSize, (IppiMorphAdvState*)spec.data(), buffer.data());
	std::fill(std::begin(dst), std::end(dst), -1.f);
}

void initL(const IppiSizeL roiSize)
{
	IppSizeL specSize = 0;
	IppSizeL bufferSize = 0;
	ippiMorphGetBufferSize_L(roiSize, maskSizeL, ipp32f, 1, &bufferSize);
	ippiMorphGetSpecSize_L(roiSize, maskSizeL, ipp32f, 1, &specSize);
	specL.resize(specSize);
	bufferL.resize(bufferSize);
	ippiMorphInit_L(roiSize, mask, maskSizeL, ipp32f, 1, (IppiMorphAdvStateL*)specL.data());
	std::fill(std::begin(dst), std::end(dst), -1.f);
}

void open(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSize roiSize, const IppiBorderType border)
{
	const auto res = ippiMorphOpenBorder_32f_C1R(src, srcStep, dst, dstStep, roiSize, border, 0, (IppiMorphAdvState*)spec.data(), buffer.data());
	if (res != ippStsOk) std::cout << "open res=" << res << std::endl;
}

void openL(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSizeL roiSize, const IppiBorderType border)
{
	const auto res = ippiMorphOpen_32f_C1R_L(src, srcStep, dst, dstStep, roiSize, border, nullptr, (IppiMorphAdvStateL*)specL.data(), bufferL.data());
	if (res != ippStsOk) std::cout << "openL res=" << res << std::endl;
}

void topHat(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSize roiSize, const IppiBorderType border)
{
	const auto res = ippiMorphTophatBorder_32f_C1R(src, srcStep, dst, dstStep, roiSize, border, 0, (IppiMorphAdvState*)spec.data(), buffer.data());
	if (res != ippStsOk) std::cout << "topHat res=" << res << std::endl;
}

void topHatL(const Ipp32f* src, const int srcStep, Ipp32f* dst, const int dstStep, const IppiSizeL roiSize, const IppiBorderType border)
{
	const auto res = ippiMorphTophat_32f_C1R_L(src, srcStep, dst, dstStep, roiSize, border, nullptr, (IppiMorphAdvStateL*)specL.data(), bufferL.data());
	if (res != ippStsOk) std::cout << "topHatL res=" << res << std::endl;
}

void testOpenWholeReplBorder()
{
	init(fullRoiSize);
	open(src, srcStep, dst, dstStep, fullRoiSize, ippBorderRepl);
	printResult("testOpenWholeReplBorder");
}

void testOpenWholeInMemBorder()
{
	init(fullRoiSize);
	open(srcWithBorder2Begin, srcWithBorder2Step, dst, dstStep, fullRoiSize, ippBorderInMem);
	printResult("testOpenWholeInMemBorder");
}

void testOpenTilesInMemBorder()
{
	initL(halfRoiSizeL);
	openL(srcWithBorder2Begin, srcWithBorder2Step, dst, dstStep, halfRoiSizeL, mixedBorder);
	openL(srcWithBorder2Half, srcWithBorder2Step, dstHalf, dstStep, halfRoiSizeL, mixedBorder);
	printResult("testOpenTilesInMemBorder");
}

void testTopHatWholeReplBorder()
{
	init(fullRoiSize);
	topHat(src, srcStep, dst, dstStep, fullRoiSize, ippBorderRepl);
	printResult("testTopHatWholeReplBorder");
}

void testTopHatWholeInMemBorder()
{
	init(fullRoiSize);
	topHat(srcWithBorder2Begin, srcWithBorder2Step, dst, dstStep, fullRoiSize, ippBorderInMem);
	printResult("testTopHatWholeInMemBorder");
}

void testTopHatTilesInMemBorder()
{
	initL(halfRoiSizeL);
	topHatL(srcWithBorder2Begin, srcWithBorder2Step, dst, dstStep, halfRoiSizeL, mixedBorder);
	topHatL(srcWithBorder2Half, srcWithBorder2Step, dstHalf, dstStep, halfRoiSizeL, mixedBorder);
	printResult("testTopHatTilesInMemBorder");
}

int main(int, char*)
{
	ippInit();

	const IppLibraryVersion* lib = ippiGetLibVersion();
	std::cout << lib->Name << "\t" << lib->Version << "\t" << lib->BuildDate << std::endl << std::endl;

	testOpenWholeReplBorder();
	testOpenWholeInMemBorder();
	testOpenTilesInMemBorder();

	testTopHatWholeReplBorder();
	testTopHatWholeInMemBorder();
	testTopHatTilesInMemBorder();

	return 0;
}

 

0 Kudos
Andrey_B_Intel
Employee
741 Views

Hello Krzysztof.

Thanks for your patience and sorry for delay.  

I've changed border type as need

void testOpenTilesInMemBorder()
{
 initL(halfRoiSizeL);
    openL(srcWithBorder2Begin, srcWithBorder2Step, dst, dstStep, halfRoiSizeL, (IppiBorderType)((int)ippBorderRepl|(int)ippBorderInMemBottom)/* mixedBorder */);
    openL(srcWithBorder2Half, srcWithBorder2Step, dstHalf, dstStep, halfRoiSizeL, (IppiBorderType)((int)ippBorderRepl|(int)ippBorderInMemTop)/* mixedBorder */);
 printResult("testOpenTilesInMemBorder");
}

It works now correctly.

Also there is next definition in ipptypes.h

ippBorderInMem        =  ippBorderInMemLeft|ippBorderInMemTop|ippBorderInMemRight|ippBorderInMemBottom

The conception of borders in 2 staged morph functions(open,close,..) is not easy and it seems that we should to add necessary chapter to ipp manual.

Thanks.

 

0 Kudos
krzysztofpiotrowski
741 Views

Hello Andrey,

Thank you very much for explanation. Now it works as I expected. The modification of IPP manual is very good idea.

By the way, I found the problem on the following page:

https://software.intel.com/en-us/ipp-dev-reference-morphological-operations

There is written:

Opening operation of A by B is AºB = (AB) ΘB.

Closing operation of A by B is AB = (AΘB) B.

In fact the definitions of opening and closing are swapped.

Thanks.

0 Kudos
Chao_Y_Intel
Moderator
741 Views

thanks, krzysztofpiotrowski,. We tracked this error for fix in the future document.

Regards,
Chao

0 Kudos
Reply