Issue #107: Conversion algorithm

This commit is contained in:
Jakub Melka
2023-11-17 13:45:49 +01:00
parent cf7d65dc82
commit c5ddb521ed
6 changed files with 434 additions and 96 deletions

View File

@ -0,0 +1,188 @@
#include "pdfimageconversion.h"
namespace pdf
{
PDFImageConversion::PDFImageConversion()
{
}
void PDFImageConversion::setImage(QImage image)
{
m_image = std::move(image);
m_convertedImage = QImage();
m_automaticThreshold = DEFAULT_THRESHOLD;
}
void PDFImageConversion::setConversionMethod(ConversionMethod method)
{
m_conversionMethod = method;
}
void PDFImageConversion::setThreshold(int threshold)
{
m_manualThreshold = threshold;
}
bool PDFImageConversion::convert()
{
if (m_image.isNull())
{
return false;
}
QImage bitonal(m_image.width(), m_image.height(), QImage::Format_Mono);
bitonal.fill(0);
// Thresholding
int threshold = DEFAULT_THRESHOLD;
switch (m_conversionMethod)
{
case pdf::PDFImageConversion::ConversionMethod::Automatic:
m_automaticThreshold = calculateOtsu1DThreshold();
threshold = m_automaticThreshold;
break;
case pdf::PDFImageConversion::ConversionMethod::Manual:
threshold = m_manualThreshold;
break;
default:
Q_ASSERT(false);
break;
}
for (int y = 0; y < m_image.height(); ++y)
{
for (int x = 0; x < m_image.width(); ++x)
{
QColor pixelColor = m_image.pixelColor(x, y);
int pixelValue = pixelColor.lightness();
bool bit = (pixelValue >= threshold);
bitonal.setPixel(x, y, bit);
}
}
m_convertedImage = std::move(bitonal);
return true;
}
int PDFImageConversion::getThreshold() const
{
switch (m_conversionMethod)
{
case pdf::PDFImageConversion::ConversionMethod::Automatic:
return m_automaticThreshold;
case pdf::PDFImageConversion::ConversionMethod::Manual:
return m_manualThreshold;
default:
Q_ASSERT(false);
break;
}
return DEFAULT_THRESHOLD;
}
QImage PDFImageConversion::getConvertedImage() const
{
return m_convertedImage;
}
int PDFImageConversion::calculateOtsu1DThreshold() const
{
if (m_image.isNull())
{
return 128;
}
// Histogram of lightness occurences
std::array<int, 256> histogram = { };
for (int x = 0; x < m_image.width(); ++x)
{
for (int y = 0; y < m_image.height(); ++y)
{
int lightness = m_image.pixelColor(x, y).lightness();
Q_ASSERT(lightness >= 0 && lightness <= 255);
int clampedLightness = qBound(0, lightness, 255);
histogram[clampedLightness] += 1;
}
}
float factor = 1.0f / float(m_image.width() * m_image.height());
std::array<float, 256> normalizedHistogram = { };
std::array<float, 256> cumulativeProbabilities = { };
std::array<float, 256> interClassVariance = { };
// Compute probabilities
for (size_t i = 0; i < histogram.size(); ++i)
{
normalizedHistogram[i] = histogram[i] * factor;
cumulativeProbabilities[i] = normalizedHistogram[i];
if (i > 0)
{
cumulativeProbabilities[i] += cumulativeProbabilities[i - 1];
}
}
// Calculate the inter-class variance for each threshold. Variables
// with the subscript 0 denote the background, while those with
// subscript 1 denote the foreground.
for (size_t i = 0; i < histogram.size(); ++i)
{
const float w0 = cumulativeProbabilities[i] - normalizedHistogram[i];
const float w1 = 1.0f - w0;
float u0 = 0.0f;
float u1 = 0.0f;
// Calculate mean intensity value of the background.
if (!qFuzzyIsNull(w0))
{
for (size_t j = 0; j < i; ++j)
{
u0 += j * normalizedHistogram[j];
}
u0 /= w0;
}
// Calculate mean intensity value of the foreground.
if (!qFuzzyIsNull(w1))
{
for (size_t j = i; j < histogram.size(); ++j)
{
u1 += j * normalizedHistogram[j];
}
u1 /= w1;
}
const float variance = w0 * w1 * std::powf(u0 - u1, 2);
interClassVariance[i] = variance;
}
// Find maximal value of the variance
size_t maxVarianceIndex = 0;
float maxVarianceValue = 0.0f;
for (size_t i = 0; i < interClassVariance.size(); ++i)
{
if (interClassVariance[i] > maxVarianceValue)
{
maxVarianceValue = interClassVariance[i];
maxVarianceIndex = i;
}
}
return int(maxVarianceIndex);
}
} // namespace pdf

View File

@ -0,0 +1,97 @@
// Copyright (C) 2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFIMAGECONVERSION_H
#define PDFIMAGECONVERSION_H
#include "pdfglobal.h"
#include <QImage>
namespace pdf
{
/// This class facilitates various image conversions,
/// including transforming colored images into monochromatic (or bitonal) formats.
class PDF4QTLIBSHARED_EXPORT PDFImageConversion
{
public:
PDFImageConversion();
enum class ConversionMethod
{
Automatic, ///< The threshold is determined automatically using an algorithm.
Manual ///< The threshold is manually provided by the user.
};
/// Sets the image to be converted using the specified conversion method.
/// This operation resets any previously converted image and the automatic threshold,
/// thereby erasing all prior image data.
/// \param image The image to be set for conversion.
void setImage(QImage image);
/// Sets the method for image conversion. Multiple methods are available
/// for selection. If the manual method is chosen, an appropriate threshold
/// must also be set by the user.
/// \param method The conversion method to be used.
void setConversionMethod(ConversionMethod method);
/// Sets the manual threshold value. When a non-manual (e.g., automatic) conversion
/// method is in use, this function will retain the manual threshold settings,
/// but the conversion will utilize an automatically calculated threshold for the image.
/// The manually set threshold is preserved and not overwritten. Therefore, if the
/// manual conversion method is later selected, the previously established manual
/// threshold will be applied.
/// \param threshold The manual threshold value to be set.
void setThreshold(int threshold);
/// This method converts the image into a bitonal (monochromatic) format. If
/// the automatic threshold calculation is enabled, it executes Otsu's 1D algorithm
/// to determine the threshold. When the manual conversion method is selected,
/// the automatic threshold calculation is bypassed, and the predefined manual threshold
/// value is utilized instead. This method returns true if the conversion is
/// successful, and false otherwise.
bool convert();
/// Returns the threshold used in image conversion. If the automatic conversion method is
/// selected, this function should be called only after executing the convert() method;
/// otherwise, it may return invalid data. The automatic threshold calculation is
/// performed within the convert() method.
/// \returns The threshold value used in image conversion.
int getThreshold() const;
/// Returns the converted image. This method should only be called after
/// the convert() method has been executed, and additionally, only if the
/// convert() method returns true. If these conditions are not met, the result
/// is undefined.
QImage getConvertedImage() const;
private:
int calculateOtsu1DThreshold() const;
static constexpr int DEFAULT_THRESHOLD = 128;
QImage m_image;
QImage m_convertedImage;
ConversionMethod m_conversionMethod = ConversionMethod::Automatic;
int m_automaticThreshold = DEFAULT_THRESHOLD;
int m_manualThreshold = DEFAULT_THRESHOLD;
};
} // namespace pdf
#endif // PDFIMAGECONVERSION_H