Estou adicionando filtragem bilinear ao meu software de renderização em C++. Para validar se funciona, estou tentando reduzir a escala de uma imagem de teste.
Aqui está a imagem de teste 1024x1024:
Quando realizo o redimensionamento da amostragem de pontos, obtenho isto:
Quando realizo o redimensionamento bilinear, infelizmente obtenho quase o mesmo resultado com alias. Por quê??? :
Veja como o redimensionamento simples é realizado:
const Graphics::Texture::SharedRGBAImagePtr input_image = Texture::Helper::createRGBAImage("D:\\resizing_test\\input_image.png");
const uint32_t destWidth = input_image->getWidth() / 16;
const uint32_t destHeight = input_image->getHeight() / 16;
Texture::RGB32FImage* resizedImage = new Texture::RGB32FImage(destWidth, destHeight);
for (uint32_t j = 0u; j < destHeight; ++j)
{
for (uint32_t i = 0u; i < destWidth; ++i)
{
const float xnorm = (float)i / (float)destWidth;
const float ynorm = (float)j / (float)destHeight;
#if 1
// Point sampling.
const float xpointSrcFloat = xnorm * ((nbFloat32)input_image->getWidth() - 1.0f);
const float ypointSrcFloat = ynorm * ((nbFloat32)input_image->getHeight() - 1.0f);
const Graphics::RGBAFColor color = input_image->getNormalizedPixelFromPosition(Math::Uvec2((uint32_t)xpointSrcFloat, (uint32_t)ypointSrcFloat));
resizedImage->setPixelFromPosition(Graphics::RGBFColor(color.x, color.y, color.z), Math::Uvec2(i, j));
#else
// Bilinear sampling.
const Graphics::RGBAFColor color = input_image->getNormalizedPixelFromRatio(Math::Vec2(xnorm, ynorm));
resizedImage->setPixelFromPosition(Graphics::RGBFColor(color.x, color.y, color.z), Math::Uvec2(i, j));
#endif
}
}
resizedImage->save("D:\\resizing_test\\ouput_image.png");
A definição de getNormalizedPixelFromPosition .
template <typename T>
inline T TImage<T>::getNormalizedPixelFromPosition(const Math::Uvec2& pos) const
{
const float div = isFloatingPointImage() ? 1.0f : 255.0f;
return getPixelFromPosition(pos) / div;
}
A definição de getNormalizedPixelFromRatio . Isso realiza filtragem bilinear :
template <typename T>
inline T TImage<T>::getNormalizedPixelFromRatio(const Math::Vec2& ratio) const
{
const uint32_t widthMinusOne = getWidth() - 1u;
const uint32_t heightMinusOne = getHeight() - 1u;
// The sampling weights.
const Math::Vec2 C00Float = Math::Vec2(ratio.x * (float)getWidth(), ratio.y * (float)getHeight());
const Math::Vec2 weights = glm::fract(C00Float);
// The sampling coordinates.
const Math::Uvec2 C00 = Math::Uvec2(
Math::clamp((uint32_t)C00Float.x, 0u, widthMinusOne),
Math::clamp((uint32_t)C00Float.y, 0u, heightMinusOne));
const Math::Uvec2 C10 = Math::Uvec2(
Math::clamp(C00.x + 1u, 0u, widthMinusOne),
Math::clamp(C00.y, 0u, heightMinusOne));
const Math::Uvec2 C01 = Math::Uvec2(
Math::clamp(C00.x, 0u, widthMinusOne),
Math::clamp(C00.y + 1u, 0u, heightMinusOne));
const Math::Uvec2 C11 = Math::Uvec2(
Math::clamp(C00.x + 1u, 0u, widthMinusOne),
Math::clamp(C00.y + 1u, 0u, heightMinusOne));
// The sampling values.
const T V00 = getNormalizedPixelFromPosition(C00);
const T V10 = getNormalizedPixelFromPosition(C10);
const T V01 = getNormalizedPixelFromPosition(C01);
const T V11 = getNormalizedPixelFromPosition(C11);
// Perform the interpolation.
const auto lerp = [](T t1, T t2, float t3) { return t1 + (t2 - t1) * t3; };
const T p0 = lerp(V00, V01, weights.y);
const T p1 = lerp(V10, V11, weights.y);
return lerp(p0, p1, weights.x);
}
O que estou fazendo errado?
Não acho que tenha relação alguma, mas nunca se sabe. Estou usando o OpenCV para armazenar e gerenciar minhas imagens. Quando uso o redimensionamento nativo, ele também dá os mesmos resultados para amostragem pontual :O
inline void BilinearScaling(const RGB32FImage& src, RGB32FImage& dest)
{
cv::resize(src.m_image,
dest.m_image,
cv::Size(dest.getWidth(), dest.getHeight()),
0.0,
0.0,
cv::InterpolationFlags::INTER_LINEAR);
}
De qualquer forma, isso não ajudará se funcionar, porque preciso interpolar manualmente . Meu objetivo final é interpolar um conjunto de 8 coeficientes harmônicos esféricos, não uma cor RGB ou RGBA.
Sua
getNormalizedPixelFromRatio
função implementa corretamente a interpolação bilinear, mas o problema é usá-la para amostrar cada pixel de destino durante a redução de escala.Você precisa de um filtro de redução de escala que considere a área na imagem de origem correspondente a cada pixel de destino, em vez de apenas amostrar ou interpolar em um único ponto. Implementar um filtro de caixa (calculando a média do bloco de origem) ou um filtro mais avançado proporcionará a redução de escala antisserrilhamento esperada.
O mesmo princípio se aplica à redução de escala dos seus dados de harmônicos esféricos.