Skip to content

Commit

Permalink
1.0.19: colors: wiener denoiser
Browse files Browse the repository at this point in the history
  • Loading branch information
zvezdochiot committed Aug 12, 2023
1 parent c575887 commit 15fe51d
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 5 deletions.
22 changes: 18 additions & 4 deletions src/core/filters/output/ColorCommonOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@

namespace output {
ColorCommonOptions::ColorCommonOptions()
: m_fillOffcut(true), m_fillMargins(true), m_normalizeIllumination(false), m_fillingColor(FILL_BACKGROUND) {}
: m_fillOffcut(true), m_fillMargins(true), m_normalizeIllumination(false), m_fillingColor(FILL_BACKGROUND), m_wienerCoef(0.0), m_wienerWindowSize(5) {}

ColorCommonOptions::ColorCommonOptions(const QDomElement& el)
: m_fillOffcut(el.attribute("fillOffcut") == "1"),
m_fillMargins(el.attribute("fillMargins") == "1"),
m_normalizeIllumination(el.attribute("normalizeIlluminationColor") == "1"),
m_fillingColor(parseFillingColor(el.attribute("fillingColor"))),
m_posterizationOptions(el.namedItem("posterization-options").toElement()) {}
m_posterizationOptions(el.namedItem("posterization-options").toElement()),
m_wienerCoef(el.attribute("wienerCoef").toDouble()),
m_wienerWindowSize(el.attribute("wienerWinSize").toInt()) {
if (m_wienerCoef < 0.0 || m_wienerCoef > 1.0) {
m_wienerCoef = 0.0;
}
if (m_wienerWindowSize < 3) {
m_wienerWindowSize = 5;
}
}

QDomElement ColorCommonOptions::toXml(QDomDocument& doc, const QString& name) const {
QDomElement el(doc.createElement(name));
Expand All @@ -23,13 +32,18 @@ QDomElement ColorCommonOptions::toXml(QDomDocument& doc, const QString& name) co
el.setAttribute("normalizeIlluminationColor", m_normalizeIllumination ? "1" : "0");
el.setAttribute("fillingColor", formatFillingColor(m_fillingColor));
el.appendChild(m_posterizationOptions.toXml(doc, "posterization-options"));
el.setAttribute("wienerCoef", m_wienerCoef);
el.setAttribute("wienerWinSize", m_wienerWindowSize);
return el;
}

bool ColorCommonOptions::operator==(const ColorCommonOptions& other) const {
return (m_normalizeIllumination == other.m_normalizeIllumination) && (m_fillMargins == other.m_fillMargins)
&& (m_fillOffcut == other.m_fillOffcut) && (m_fillingColor == other.m_fillingColor)
&& (m_posterizationOptions == other.m_posterizationOptions);
&& (m_posterizationOptions == other.m_posterizationOptions)
&& (m_posterizationOptions == other.m_posterizationOptions)
&& (m_wienerCoef == other.m_wienerCoef)
&& (m_wienerWindowSize == other.m_wienerWindowSize);
}

bool ColorCommonOptions::operator!=(const ColorCommonOptions& other) const {
Expand Down Expand Up @@ -91,4 +105,4 @@ bool ColorCommonOptions::PosterizationOptions::operator==(const ColorCommonOptio
bool ColorCommonOptions::PosterizationOptions::operator!=(const ColorCommonOptions::PosterizationOptions& other) const {
return !(*this == other);
}
} // namespace output
} // namespace output
20 changes: 20 additions & 0 deletions src/core/filters/output/ColorCommonOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ class ColorCommonOptions {

void setNormalizeIllumination(bool val);

double wienerCoef() const;
void setWienerCoef(double val);
int wienerWindowSize() const;
void setWienerWindowSize(int val);

FillingColor getFillingColor() const;

void setFillingColor(FillingColor fillingColor);
Expand All @@ -89,6 +94,8 @@ class ColorCommonOptions {
bool m_fillOffcut;
bool m_fillMargins;
bool m_normalizeIllumination;
double m_wienerCoef;
int m_wienerWindowSize;
FillingColor m_fillingColor;
PosterizationOptions m_posterizationOptions;
};
Expand Down Expand Up @@ -118,6 +125,19 @@ inline void ColorCommonOptions::setNormalizeIllumination(bool val) {
m_normalizeIllumination = val;
}

inline double ColorCommonOptions::wienerCoef() const {
return m_wienerCoef;
}
inline void ColorCommonOptions::setWienerCoef(double val) {
m_wienerCoef = val;
}
inline int ColorCommonOptions::wienerWindowSize() const {
return m_wienerWindowSize;
}
inline void ColorCommonOptions::setWienerWindowSize(int val) {
m_wienerWindowSize = val;
}

inline const ColorCommonOptions::PosterizationOptions& ColorCommonOptions::getPosterizationOptions() const {
return m_posterizationOptions;
}
Expand Down
22 changes: 22 additions & 0 deletions src/core/filters/output/OptionsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ void OptionsWidget::updateColorsDisplay() {
posterizeCB->setEnabled(true);
posterizeOptionsWidget->setEnabled(colorCommonOptions.getPosterizationOptions().isEnabled());
}
wienerCoef->setValue(colorCommonOptions.wienerCoef());
wienerWindowSize->setValue(colorCommonOptions.wienerWindowSize());
colorSegmentationCB->setChecked(blackWhiteOptions.getColorSegmenterOptions().isEnabled());
reduceNoiseSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getNoiseReduction());
redAdjustmentSB->setValue(blackWhiteOptions.getColorSegmenterOptions().getRedThresholdAdjustment());
Expand Down Expand Up @@ -764,6 +766,24 @@ void OptionsWidget::originalBackgroundToggled(bool checked) {
emit reloadRequested();
}

void OptionsWidget::wienerCoefChanged(double value) {
ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions();
colorCommonOptions.setWienerCoef(value);
m_colorParams.setColorCommonOptions(colorCommonOptions);
m_settings->setColorParams(m_pageId, m_colorParams);

m_delayedReloadRequest.start(750);
}

void OptionsWidget::wienerWindowSizeChanged(int value) {
ColorCommonOptions colorCommonOptions = m_colorParams.colorCommonOptions();
colorCommonOptions.setWienerWindowSize(value);
m_colorParams.setColorCommonOptions(colorCommonOptions);
m_settings->setColorParams(m_pageId, m_colorParams);

m_delayedReloadRequest.start(750);
}

void OptionsWidget::colorSegmentationToggled(bool checked) {
BlackWhiteOptions blackWhiteOptions = m_colorParams.blackWhiteOptions();
BlackWhiteOptions::ColorSegmenterOptions segmenterOptions = blackWhiteOptions.getColorSegmenterOptions();
Expand Down Expand Up @@ -908,6 +928,8 @@ void OptionsWidget::setupUiConnections() {
CONNECT(pictureShapeSensitivitySB, SIGNAL(valueChanged(int)), this, SLOT(pictureShapeSensitivityChanged(int)));
CONNECT(higherSearchSensitivityCB, SIGNAL(clicked(bool)), this, SLOT(higherSearchSensivityToggled(bool)));

CONNECT(wienerCoef, SIGNAL(valueChanged(double)), this, SLOT(wienerCoefChanged(double)));
CONNECT(wienerWindowSize, SIGNAL(valueChanged(int)), this, SLOT(wienerWindowSizeChanged(int)));
CONNECT(colorSegmentationCB, SIGNAL(clicked(bool)), this, SLOT(colorSegmentationToggled(bool)));
CONNECT(reduceNoiseSB, SIGNAL(valueChanged(int)), this, SLOT(reduceNoiseChanged(int)));
CONNECT(redAdjustmentSB, SIGNAL(valueChanged(int)), this, SLOT(redAdjustmentChanged(int)));
Expand Down
4 changes: 4 additions & 0 deletions src/core/filters/output/OptionsWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ class OptionsWidget : public FilterOptionsWidget, private Ui::OptionsWidget {

void higherSearchSensivityToggled(bool checked);

void wienerCoefChanged(double value);

void wienerWindowSizeChanged(int value);

void colorSegmentationToggled(bool checked);

void reduceNoiseChanged(int value);
Expand Down
40 changes: 40 additions & 0 deletions src/core/filters/output/OptionsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,46 @@
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<layout class="QGridLayout" name="filterColorOptions">
<item row="0" column="0">
<widget class="QLabel" name="wienerLabel">
<property name="text">
<string>Wiener denoiser</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="wienerCoef">
<property name="toolTip">
<string>Value is 0.0 .. 1.0..</string>
</property>
<property name="minimum">
<double>0.0</double>
</property>
<property name="maximum">
<double>1.0</double>
</property>
<property name="singleStep">
<double>0.01</double>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="wienerWindowSize">
<property name="toolTip">
<string>The dimensions of a pixel neighborhood to consider.</string>
</property>
<property name="minimum">
<number>3</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="colorSegmentationCB">
<property name="toolTip">
Expand Down
7 changes: 7 additions & 0 deletions src/core/filters/output/OutputGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <imageproc/PolygonUtils.h>
#include <imageproc/Posterizer.h>
#include <imageproc/SkewFinder.h>
#include <imageproc/WienerFilter.h>

#include <QColor>
#include <QDebug>
Expand Down Expand Up @@ -1267,6 +1268,9 @@ std::unique_ptr<OutputImage> OutputGenerator::Processor::processWithoutDewarping
m_dbg->add(maybeNormalized, "maybeNormalized");
}

const ColorCommonOptions& colorCommonOptions = m_colorParams.colorCommonOptions();
wienerColorFilterInPlace(maybeNormalized, QSize(colorCommonOptions.wienerWindowSize(), colorCommonOptions.wienerWindowSize()), colorCommonOptions.wienerCoef());

if (m_renderParams.normalizeIllumination()) {
m_outsideBackgroundColor
= BackgroundColorCalculator::calcDominantBackgroundColor(maybeNormalized, m_outCropAreaInWorkingCs);
Expand Down Expand Up @@ -1554,6 +1558,9 @@ std::unique_ptr<OutputImage> OutputGenerator::Processor::processWithDewarping(Zo

m_status.throwIfCancelled();

const ColorCommonOptions& colorCommonOptions = m_colorParams.colorCommonOptions();
wienerColorFilterInPlace(normalizedOriginal, QSize(colorCommonOptions.wienerWindowSize(), colorCommonOptions.wienerWindowSize()), colorCommonOptions.wienerCoef());

// Picture mask (white indicate a picture) in the same coordinates as
// warpedGrayOutput. Only built for Mixed mode.
BinaryImage warpedBwMask;
Expand Down
3 changes: 2 additions & 1 deletion src/imageproc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(sources
PolygonRasterizer.cpp PolygonRasterizer.h
HoughLineDetector.cpp HoughLineDetector.h
GaussBlur.cpp GaussBlur.h
WienerFilter.cpp WienerFilter.h
Sobel.h
MorphGradientDetect.cpp MorphGradientDetect.h
PolynomialLine.cpp PolynomialLine.h
Expand Down Expand Up @@ -57,4 +58,4 @@ add_library(imageproc STATIC ${sources})
target_link_libraries(imageproc PUBLIC foundation math)
target_include_directories(imageproc PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

add_subdirectory(tests)
add_subdirectory(tests)
157 changes: 157 additions & 0 deletions src/imageproc/WienerFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) 2015 Joseph Artsimovich <joseph.artsimovich@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cstdint>
#include <cassert>
#include <cmath>
#include <stdexcept>
#include <QSize>
#include <QtGlobal>
#include "GrayImage.h"
#include "IntegralImage.h"
#include "WienerFilter.h"

namespace imageproc{

GrayImage wienerFilter(GrayImage const& image, QSize const& window_size, double const noise_sigma) {
GrayImage dst(image);
wienerFilterInPlace(dst, window_size, noise_sigma);
return dst;
}

void wienerFilterInPlace(GrayImage& image, QSize const& window_size, double const noise_sigma) {
if (window_size.isEmpty()) {
throw std::invalid_argument("wienerFilter: empty window_size");
}
if (noise_sigma < 0) {
throw std::invalid_argument("wienerFilter: negative noise_sigma");
}
if (image.isNull()) {
return;
}

int const w = image.width();
int const h = image.height();
double const noise_variance = noise_sigma * noise_sigma;

IntegralImage<uint32_t> integral_image(w, h);
IntegralImage<uint64_t> integral_sqimage(w, h);

uint8_t* image_line = image.data();
int const image_stride = image.stride();

for (int y = 0; y < h; ++y) {
integral_image.beginRow();
integral_sqimage.beginRow();
for (int x = 0; x < w; ++x) {
uint32_t const pixel = image_line[x];
integral_image.push(pixel);
integral_sqimage.push(pixel * pixel);
}
image_line += image_stride;
}

int const window_lower_half = window_size.height() >> 1;
int const window_upper_half = window_size.height() - window_lower_half;
int const window_left_half = window_size.width() >> 1;
int const window_right_half = window_size.width() - window_left_half;

image_line = image.data();
for (int y = 0; y < h; ++y) {
int const top = ((y - window_lower_half) < 0) ? 0 : (y - window_lower_half);
int const bottom = ((y + window_upper_half) < h) ? (y + window_upper_half) : h; // exclusive

for (int x = 0; x < w; ++x) {
int const left = ((x - window_left_half) < 0) ? 0 : (x - window_left_half);
int const right = ((x + window_right_half) < w) ? (x + window_right_half) : w; // exclusive
int const area = (bottom - top) * (right - left);
assert(area > 0); // because window_size > 0 and w > 0 and h > 0

QRect const rect(left, top, right - left, bottom - top);
double const window_sum = integral_image.sum(rect);
double const window_sqsum = integral_sqimage.sum(rect);

double const r_area = 1.0 / area;
double const mean = window_sum * r_area;
double const sqmean = window_sqsum * r_area;
double const variance = sqmean - mean * mean;

if (variance > 1e-6) {
double const src_pixel = (double) image_line[x];
double const dst_pixel = mean + (src_pixel - mean) *
(((variance - noise_variance) < 0.0) ? 0.0 : (variance - noise_variance)) / variance;
image_line[x] = (uint8_t) ((dst_pixel < 0.0) ? 0.0 : ((dst_pixel < 255.0) ? dst_pixel : 255.0));
}
}
image_line += image_stride;
}
}

QImage wienerColorFilter(QImage const& image, QSize const& window_size, double const coef) {
QImage dst(image);
wienerColorFilterInPlace(dst, window_size, coef);
return dst;
}

void wienerColorFilterInPlace(QImage& image, QSize const& window_size, double const coef) {
if (image.isNull()) {
return;
}
if (window_size.isEmpty()) {
throw std::invalid_argument("wienerFilter: empty window_size");
}

if (coef > 0.0) {
int const w = image.width();
int const h = image.height();
uint8_t* image_line = (uint8_t*) image.bits();
int const image_bpl = image.bytesPerLine();
unsigned int const cnum = image_bpl / w;

GrayImage gray = GrayImage(image);
uint8_t* gray_line = gray.data();
int const gray_bpl = gray.stride();
GrayImage wiener(wienerFilter(gray, window_size, 255.0 * coef));
uint8_t* wiener_line = wiener.data();
int const wiener_bpl = wiener.stride();

for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float const origin = gray_line[x];
float color = wiener_line[x];

float const colscale = (color + 1.0f) / (origin + 1.0f);
float const coldelta = color - origin * colscale;
for (unsigned int c = 0; c < cnum; ++c)
{
int const indx = x * cnum + c;
float origcol = image_line[indx];
float val = origcol * colscale + coldelta;
val = (val < 0.0f) ? 0.0f : (val < 255.0f) ? val : 255.0f;
image_line[indx] = (uint8_t) (val + 0.5f);
}
}
image_line += image_bpl;
gray_line += gray_bpl;
wiener_line += wiener_bpl;
}
}
}

} // namespace imageproc
Loading

0 comments on commit 15fe51d

Please sign in to comment.