Как автоматизировать выделение штрихового рисунка из фона с помощью кода. Есть ли способ? - PullRequest
1 голос
/ 09 января 2020

Sample Image

Это то, что мне нравится делать с кодом. Ничего не делается вручную в процессе с Photoshop, так что я думаю, что есть способ? но не могу понять.


Это то, что я сделал в Python:

from PIL import Image
im_rgb = Image.open('lena.jpg')
im_a = Image.open('frame.png').convert('L').resize(im_rgb.size)
im_rgba = im_rgb.copy()
im_rgba.putalpha(im_a)
im_rgba.save('final.png')

, но я ищу решение в Java / Kotlin on Android Studio, хотя я мог бы жить с образцом в Dart или C ++.

Ответы [ 2 ]

0 голосов
/ 09 января 2020

Какой ОП описан, я знаю из GIMP , где он называется Цвет в альфа .

Пока Время от времени я использовал эту команду, пытаясь представить, как это можно реализовать.

Мне приходит в голову несколько подходов:

  • очень просто: сравнить каждый пиксель с осью color и установите альфа в 0 в случае совпадения
  • на основе порогового значения: определите евклидово расстояние цвета пикселя до точки поворота в пространстве RGB (как трехмерное пространство) и установите альфа в соответствии с расстоянием когда под заданным порогом
  • на основе порога в пространстве HSV: аналогичный подход, как и выше, но применяется к пространству HSV (для лучшего согласования цветов).

Из любопытства я написал пример приложения, чтобы попробовать это.

Сначала код C ++ для преобразования цвета в альфа:

imageColorToAlpha.h:

#ifndef IMAGE_COLOR_TO_ALPHA_H
#define IMAGE_COLOR_TO_ALPHA_H

// standard C++ header:
#include <cstdint>
#include <functional>

// convenience types
typedef std::uint32_t Pixel;
typedef std::uint8_t Comp;

// convenience constants
const int ShiftR = 16;
const int ShiftG = 8;
const int ShiftB = 0;
const int ShiftA = 24;

const Pixel MaskR = 0xff << ShiftR;
const Pixel MaskG = 0xff << ShiftG;
const Pixel MaskB = 0xff << ShiftB;
const Pixel MaskA = 0xff << ShiftA;

const Pixel MaskRGB = MaskR | MaskG | MaskB;

// convenience functions
inline Comp getR(Pixel pixel) { return Comp(pixel >> ShiftR); }
inline Comp getG(Pixel pixel) { return Comp(pixel >> ShiftG); }
inline Comp getB(Pixel pixel) { return Comp(pixel >> ShiftB); }
inline Comp getA(Pixel pixel) { return Comp(pixel >> ShiftA); }

inline void setR(Pixel &pixel, Comp r)
{
  pixel &= ~MaskR;
  pixel |= r << ShiftR;
}
inline void setG(Pixel &pixel, Comp g)
{
  pixel &= ~MaskG;
  pixel |= g << ShiftG;
}
inline void setB(Pixel &pixel, Comp b)
{
  pixel &= ~MaskB;
  pixel |= b << ShiftB;
}
inline void setA(Pixel &pixel, Comp r)
{
  pixel &= ~MaskA;
  pixel |= r << ShiftA;
}
inline void set(Pixel &pixel, Comp r, Comp g, Comp b)
{
  pixel &= ~MaskRGB;
  pixel |= r << ShiftR | g << ShiftG | b << ShiftB;
}
inline void set(Pixel &pixel, Comp r, Comp g, Comp b, Comp a)
{
  pixel = r << ShiftR | g << ShiftG | b << ShiftB | a << ShiftA;
}

extern void transformImage(
  size_t w, size_t h, // width and height
  size_t bytesPerRow, // bytes per row (to handle row alignment)
  const Pixel *imgSrc, // source image
  Pixel *imgDst, // destination image
  std::function<Pixel(Pixel)> transform);

// color to alpha (very simple)
extern Pixel colorToAlpha(Pixel pixel, Pixel color);

// color to alpha (with threshold)
extern Pixel colorToAlpha(
  Pixel pixel, Pixel color, unsigned threshold);

// convenience functions for image

inline void colorToAlphaSimple(
  size_t w, size_t h, // width and height
  size_t bytesPerRow, // bytes per row (to handle row alignment)
  const Pixel *imgSrc, // source image
  Pixel *imgDst, // destination image
  Pixel color) // pivot color
{
  transformImage(w, h, bytesPerRow, imgSrc, imgDst,
    [color](Pixel pixel) { return colorToAlpha(pixel, color); });
}

inline void colorToAlphaThreshold(
  size_t w, size_t h, // width and height
  size_t bytesPerRow, // bytes per row (to handle row alignment)
  const Pixel *imgSrc, // source image
  Pixel *imgDst, // destination image
  Pixel color, // pivot color
  unsigned threshold) // threshold
{
  transformImage(w, h, bytesPerRow, imgSrc, imgDst,
    [color, threshold](Pixel pixel) {
      return colorToAlpha(pixel, color, threshold);
    });
}

inline void fillRGB(
  size_t w, size_t h, // width and height
  size_t bytesPerRow, // bytes per row (to handle row alignment)
  Pixel *img, // image to modify
  Pixel color) // fill color (alpha ignored)
{
  color &= MaskRGB;
  transformImage(w, h, bytesPerRow, img, img,
    [color](Pixel pixel) {
      pixel &= ~MaskRGB; pixel |= color; return pixel;
    });
}

#endif // IMAGE_COLOR_TO_ALPHA_H

и соответствующий imageColorToAlpha.cc:

// standard C++ header:
#include <cmath>

// header of this module:
#include "imageColorToAlpha.h"

void transformImage(
  size_t w, size_t h, // width and height
  size_t bytesPerRow, // bytes per row (to handle row alignment)
  const Pixel *imgSrc, // source image
  Pixel *imgDst, // destination image
  std::function<Pixel(Pixel)> transform)
{
  for (size_t y = 0; y < h; ++y) {
    const Pixel *pixelSrc = (const Pixel*)((const Comp*)imgSrc + y * bytesPerRow);
    Pixel *pixelDst = (Pixel*)((Comp*)imgDst + y * bytesPerRow);
    for (size_t x = 0; x < w; ++x) pixelDst[x] = transform(pixelSrc[x]);
  }
}

Pixel colorToAlpha(Pixel pixel, Pixel color)
{
  // eliminate current alpha values from pixel and color
  pixel &= MaskRGB; color &= MaskRGB;
  // compare pixel with color
  const int match = pixel == color;
  // set alpha according to match of pixel and color
  setA(pixel, ~(match * 0xff));
  // done
  return pixel;
}

Pixel colorToAlpha(Pixel pixel, Pixel color, unsigned threshold)
{
  // delta values per component
  const int dR = (int)getR(pixel) - (int)getR(color);
  const int dG = (int)getG(pixel) - (int)getG(color);
  const int dB = (int)getB(pixel) - (int)getB(color);
  // square Euclidean distance
  const unsigned dSqr = dR * dR + dG * dG + dB * dB;
  // compute alpha
  Comp a = 0xff;
  if (dSqr < threshold * threshold) { // distance below threshold?
    // compute alpha weighted by distance
    const double d = sqrt((double)dSqr);
    const double f = d / threshold;
    a = (Comp)(f * 0xff);
  }
  // done
  setA(pixel, a);
  return pixel;
}

Этот код для работы с изображениями основан на C ++ std только библиотека. Это сделано для того, чтобы сделать код как можно более примерным и использовать его повторно.

Однако код для декодирования форматов файлов изображений часто не является ни коротким, ни простым. Поэтому я написал приложение-оболочку в Qt , чтобы показать это в действии. Qt обеспечивает поддержку изображений, а также работу с фреймами для настольного приложения, и мне показалось, что она наиболее подходит для этой задачи (помимо того, что у меня есть некоторый опыт работы с ней).

Приложение-оболочка Qt testQImageColorToAlpha.cc:

// Qt header:
#include <QtWidgets>

// own header:
#include "imageColorToAlpha.h"
#include "qColorButton.h"

// convenience functions
QPixmap fromImage(const QImage &qImg)
{
  QPixmap qPixmap;
  qPixmap.convertFromImage(qImg);
  return qPixmap;
}

QPixmap fromAlphaImage(
  const QImage &qImg,
  QColor qColor1 = Qt::darkGray,
  QColor qColor2 = Qt::gray,
  int whCell = 32)
{
  QPixmap qPixmap(qImg.width(), qImg.height());
  { QPainter qPainter(&qPixmap);
    // draw chessboard
    qPixmap.fill(qColor1);
    for (int y = 0; y < qImg.height(); y += 2 * whCell) {
      for (int x = 0; x < qImg.width(); x += 2 * whCell) {
        qPainter.fillRect(x, y, whCell, whCell, qColor2);
        qPainter.fillRect(x + whCell, y + whCell, whCell, whCell, qColor2);
      }
    }
    // overlay with image
    qPainter.drawImage(0, 0, qImg);
  } // close Qt painter
  // done
  return qPixmap;
}

enum {
  SingleValue,
  RGBRange
};

QImage colorToAlphaSimple(
  const QImage &qImgSrc, QColor qColor,
  bool fill, QColor qColorFill)
{
  // ensure expected format for input image
  QImage qImg = qImgSrc.convertToFormat(QImage::Format_ARGB32);
  const int w = qImg.width(), h = qImg.height(), bpr = qImg.bytesPerLine();
  // allocate storage for output image
  QImage qImgDst(w, h, QImage::Format_ARGB32);
  colorToAlphaSimple(w, h, bpr,
    (const Pixel*)qImg.constBits(), (Pixel*)qImgDst.bits(), qColor.rgba());
  // override RGB if required
  if (fill) fillRGB(w, h, bpr, (Pixel*)qImgDst.bits(), qColorFill.rgba());
  // done
  return qImgDst;
}

QImage colorToAlphaThreshold(
  const QImage &qImgSrc, QColor qColor, unsigned threshold,
  bool fill, QColor qColorFill)
{
  // ensure expected format for input image
  QImage qImg = qImgSrc.convertToFormat(QImage::Format_ARGB32);
  const int w = qImg.width(), h = qImg.height(), bpr = qImg.bytesPerLine();
  // allocate storage for output image
  QImage qImgDst(w, h, QImage::Format_ARGB32);
  colorToAlphaThreshold(w, h, bpr,
    (const Pixel*)qImg.constBits(), (Pixel*)qImgDst.bits(), qColor.rgba(), threshold);
  // override RGB if required
  if (fill) fillRGB(w, h, bpr, (Pixel*)qImgDst.bits(), qColorFill.rgba());
  // done
  return qImgDst;
}

// main application
int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup data
  QImage qImgIn("cat.drawn.png");
  QImage qImgOut(qImgIn);
  // setup GUI
  // main window
  QWidget qWin;
  qWin.setWindowTitle(QString::fromUtf8("Color to Alpha"));
  QGridLayout qGrid;
  // input image
  QHBoxLayout qHBoxLblIn;
  QLabel qLblIn(QString::fromUtf8("Input Image:"));
  qHBoxLblIn.addWidget(&qLblIn);
  qHBoxLblIn.addStretch(1);
  QPushButton qBtnLoad(QString::fromUtf8("Open..."));
  qHBoxLblIn.addWidget(&qBtnLoad);
  qGrid.addLayout(&qHBoxLblIn, 0, 0);
  QLabel qLblImgIn;
  qLblImgIn.setPixmap(fromImage(qImgIn));
  qGrid.addWidget(&qLblImgIn, 1, 0);
  // config. color to alpha
  QGroupBox qBoxCfg(QString::fromUtf8("Configuration:"));
  QFormLayout qFormCfg;
  QComboBox qMenuColorToAlpha;
  qMenuColorToAlpha.addItem(QString::fromUtf8("Single Value"));
  qMenuColorToAlpha.addItem(QString::fromUtf8("With Threshold"));
  qFormCfg.addRow(QString::fromUtf8("Color to Transparency:"), &qMenuColorToAlpha);
  QColorButton qBtnColor(Qt::white);
  qFormCfg.addRow(QString::fromUtf8("Pivot Color:"), &qBtnColor);
  qBoxCfg.setLayout(&qFormCfg);
  QSpinBox qEditRange;
  qEditRange.setRange(1, 255);
  qFormCfg.addRow(QString::fromUtf8("Range:"), &qEditRange);
  QFrame qHSepCfg;
  qHSepCfg.setFrameStyle(QFrame::HLine | QFrame::Plain);
  qFormCfg.addRow(&qHSepCfg);
  QHBoxLayout qHBoxFill;
  QCheckBox qTglFill;
  qTglFill.setChecked(false);
  qHBoxFill.addWidget(&qTglFill);
  QColorButton qBtnColorFill(Qt::black);
  qHBoxFill.addWidget(&qBtnColorFill, 1);
  qFormCfg.addRow(QString::fromUtf8("Fill Color:"), &qHBoxFill);
  qGrid.addWidget(&qBoxCfg, 1, 1);
  // output image
  QHBoxLayout qHBoxLblOut;
  QLabel qLblOut(QString::fromUtf8("Output Image:"));
  qHBoxLblOut.addWidget(&qLblOut);
  qHBoxLblOut.addStretch(1);
  QColorButton qBtnBgColor1(QString::fromUtf8("Color 1"), Qt::darkGray);
  qHBoxLblOut.addWidget(&qBtnBgColor1);
  QColorButton qBtnBgColor2(QString::fromUtf8("Color 2"), Qt::gray);
  qHBoxLblOut.addWidget(&qBtnBgColor2);
  qGrid.addLayout(&qHBoxLblOut, 0, 2);
  QLabel qLblImgOut;
  qLblImgOut.setPixmap(fromAlphaImage(qImgOut));
  qGrid.addWidget(&qLblImgOut, 1, 2);
  // main window
  qWin.setLayout(&qGrid);
  qWin.show();
  // helper
  auto update = [&]() {
    const int algo = qMenuColorToAlpha.currentIndex();
    switch (algo) {
      case SingleValue:
        qImgOut
          = colorToAlphaSimple(qImgIn, qBtnColor.color(),
            qTglFill.isChecked(), qBtnColorFill.color());
        break;
      case RGBRange:
        qImgOut
          = colorToAlphaThreshold(qImgIn, qBtnColor.color(), qEditRange.value(),
            qTglFill.isChecked(), qBtnColorFill.color());
        break;
    }
    qEditRange.setEnabled(algo >= RGBRange);
    qBtnColorFill.setEnabled(qTglFill.isChecked());
    qLblImgOut.setPixmap(
      fromAlphaImage(qImgOut, qBtnBgColor1.color(), qBtnBgColor2.color()));
  };
  // install signal handlers
  QObject::connect(
    &qBtnLoad, &QPushButton::clicked,
    [&]() {
      QString filePath
        = QFileDialog::getOpenFileName(
          &qWin, QString::fromUtf8("Open Image File"),
          QString(),
          QString::fromUtf8(
            "Image Files (*.png *.jpg *.jpeg);;"
            "PNG Files (*.png);;"
            "JPEG Files (*.jpg *.jpeg);;"
            "All Files (*)"));
      if (filePath.isEmpty()) return; // choice aborted
      QImage qImg;
      qImg.load(filePath);
      if (qImg.isNull()) return; // file loading failed
      qImgIn = qImg;
      qLblImgIn.setPixmap(fromImage(qImgIn));
      update();
    });
  QObject::connect(
    &qMenuColorToAlpha,
    QOverload<int>::of(&QComboBox::currentIndexChanged),
    [&](int) { update(); });
  QObject::connect(&qBtnColor, &QPushButton::clicked,
    [&]() { qBtnColor.chooseColor(); update(); });
  QObject::connect(
    &qEditRange, QOverload<int>::of(&QSpinBox::valueChanged),
    [&](int) { update(); });
  QObject::connect(&qTglFill, &QCheckBox::toggled,
    [&](bool) { update(); });
  QObject::connect(&qBtnColorFill, &QPushButton::clicked,
    [&]() { qBtnColorFill.chooseColor(); update(); });
  QObject::connect(&qBtnBgColor1, &QPushButton::clicked,
    [&]() { qBtnBgColor1.chooseColor(); update(); });
  QObject::connect(&qBtnBgColor2, &QPushButton::clicked,
    [&]() { qBtnBgColor2.chooseColor(); update(); });
  // runtime loop
  update();
  return app.exec();
}

и вспомогательный класс qColorButton.h:

// borrowed from https://stackoverflow.com/a/55889624/7478597

#ifndef Q_COLOR_BUTTON_H
#define Q_COLOR_BUTTON_H

// Qt header:
#include <QColorDialog>
#include <QPushButton>

// a Qt push button for color selection
class QColorButton: public QPushButton {
  private:
    QColor _qColor;

  public:
    explicit QColorButton(
      const QString &text = QString(), const QColor &qColor = Qt::black,
      QWidget *pQParent = nullptr):
      QPushButton(text, pQParent)
    {
      setColor(qColor);
    }
    explicit QColorButton(
      const QColor &qColor = Qt::black,
      QWidget *pQParent = nullptr):
      QColorButton(QString(), qColor, pQParent)
    { }
    virtual ~QColorButton() = default;

    QColorButton(const QColorButton&) = delete;
    QColorButton& operator=(const QColorButton&) = delete;

    const QColor& color() const { return _qColor; }
    void setColor(const QColor &qColor)
    {
      _qColor = qColor;
      QFontMetrics qFontMetrics(font());
      const int h = qFontMetrics.height();
      QPixmap qPixmap(h, h);
      qPixmap.fill(_qColor);
      setIcon(qPixmap);
    }

    QColor chooseColor()
    {
      setColor(QColorDialog::getColor(_qColor, this, text()));
      return _qColor;
    }
};

#endif // Q_COLOR_BUTTON_H

При запуске загружается изображение по умолчанию и применяется простое сопоставление:

Snapshot of testQImageColorToAlpha.exe (Single Value)

Я скачал образец изображения с jloog.com / images / .

Результат выглядит немного плохим. Белый фон совпадает, но вокруг черного рисунка появляются белые артефакты. Это результат выборки , когда пиксели, которые покрывали рисунок, а также фон, получили соответствующие оттенки серого.

Таким образом, лучший подход состоит в том, чтобы превратить расстояние от основного цвета в соответствующую альфа значение, при котором пороговое значение определяет диапазон, а также предел, до которого должны учитываться цвета:

Snapshot of testQImageColorToAlpha.exe (With Threshold)

Это выглядит лучше.

Теперь мне стало интересно, насколько хорошо это работает на «настоящих» фотографиях:

Snapshot of testQImageColorToAlpha.exe (photo, With Threshold)

Результат лучше, когда я боюсь.

Тем не менее, он показывает ограничения подхода, который я получил до сих пор.


Обновление:

Пока я исследовал сеть, чтобы получить точное преобразование из RGB в HSV, я узнал много о различных HSL и HSV моделях, о которых я не знал раньше. Наконец, я наткнулся на Разница в цвете , где я нашел несколько интересных утверждений:

Поскольку большинство определений цветовых различий представляют собой расстояния в цветовом пространстве, стандарт средством определения расстояния является евклидово расстояние. Если в настоящее время один имеет кортеж RGB (красный, зеленый, синий) и хочет найти разницу в цвете, в вычислительном отношении одним из самых простых является назвать линейные измерения R, G, B, определяющие цветовое пространство.

Существует ряд формул цветового расстояния, которые пытаются использовать цветовые пространства, такие как HSV, с оттенком в виде круга, размещая различные цвета в трехмерном пространстве либо цилиндра, либо конуса, но большинство из них - просто модификации RGB; без учета различий в восприятии цвета человеком они будут стремиться быть на одном уровне с простым евклидовым метри c.

Итак, я отбросил идею сопоставления в пространстве ВПГ.

Вместо этого я сделал очень простое расширение, которое, по моему мнению, значительно улучшило монохромные рисунки:

Пиксели со смешанным рисунком и фоном преобразуются в оттенки альфа, но значения RGB остаются нетронутыми. Это не совсем правильно, потому что на самом деле он должен стать цветом переднего плана (цвет карандаша), смешанным с альфа-каналом. Чтобы исправить это, я добавил опцию переопределения значений RGB для вывода выбранным цветом.

Это результат с переопределенным цветом:

Snapshot of testQImageColorToAlpha.exe (With Threshold and Fill)

Кстати. это дает приятный небольшой дополнительный эффект - цвет карандаша может быть изменен:

Snapshot of testQImageColorToAlpha.exe (With Threshold and Fill)

(Приведенный выше пример исходного кода был обновлен с учетом последних изменений .)


Для построения примера можно использовать любой CMake с CMakeLists.txt:

project(QImageColorToAlpha)

cmake_minimum_required(VERSION 3.10.0)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt5Widgets CONFIG REQUIRED)

include_directories(
  "${CMAKE_SOURCE_DIR}")

add_executable(testQImageColorToAlpha
  testQImageColorToAlpha.cc
  qColorButton.h # qColorButton.cc
  imageColorToAlpha.h imageColorToAlpha.cc)

target_link_libraries(testQImageColorToAlpha
  Qt5::Widgets)

# define QT_NO_KEYWORDS to prevent confusion between of Qt signal-slots and
# other signal-slot APIs
target_compile_definitions(testQImageColorToAlpha PUBLIC QT_NO_KEYWORDS)

, который я использовал для его сборки в VS2017.

В качестве альтернативы, минимальный файл проекта Qt testQImageColorToAlpha.pro:

SOURCES = testQImageColorToAlpha.cc imageColorToAlpha.cc

QT += widgets

, который я тестировал в cygwin :

$ qmake-qt5 testQImageColorToAlpha.pro 

$ make && ./testQImageColorToAlpha
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQImageColorToAlpha.o testQImageColorToAlpha.cc
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o imageColorToAlpha.o imageColorToAlpha.cc
g++  -o testQImageColorToAlpha.exe testQImageColorToAlpha.o imageColorToAlpha.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
Qt Version: 5.9.4

Snapshot of testQImageColorToAlpha (built in cygwin)

0 голосов
/ 09 января 2020
from PIL import Image
im_rgb = Image.open('lena.jpg')
im_a = Image.open('frame.png').convert('L').resize(im_rgb.size)
im_rgba = im_rgb.copy()
im_rgba.putalpha(im_a)
im_rgba.save('final.png')

`

Я сам понял это на Python. но это не так полно, как я хотел, чтобы это было изначально. Я все еще хотел бы знать, как сделать это с Java / Kotlin на Android Studio или с C ++ или Dart.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...