使用C++使用Qt制作我自己的照片拼接应用程序

Sah*_*een 5 c++ qt image-processing qt-creator genetic-algorithm

这是我到目前为止所做的输出:
在此输入图像描述

虽然这只显示黑白图像,但代码也适用于彩色图像.代码基本上使用来自小型数据库的较小图像填充较大的图像.
所以这就是我被困住的地方,相当无能为力.
我怎样才能得到像这样的效果.或者这个.
Qn1:我需要拍摄另一个输入(大)图像*(其中一个效果可见)*并合并它们,但是如何?
Qn2:我怎样才能评估照片拼接的好坏?我有一个为此编写的遗传算法,但我无法修复适应度函数,(突变和交叉工作完美).

这就是我能想到的(对于Qn1):
1.取上面显示的图像的交替像素和必须制作马赛克的图像.
2.取上述像素值的平均值和输入图像,必须为其制作马赛克.
但是没有任何线索来评估善意.

Rei*_*ica 6

下面是一个自包含的草图.拼接算法是通过优秀参考中实现的算法的中途.我认为它适用于两个小时的工作.我试图使代码合理地正确,有两个警告,正如他们所说,留给读者的练习.

  1. 我没有跟踪工作线程 - 如果您在工作人员处于活动状态时尝试退出应用程序,则预计会在退出时崩溃.这不好,但除此之外是良性的,不会影响整体功能.磁盘上可能存在一些损坏的图像,但重新加载时应忽略这些图像.

  2. 标签中显示的图像没有缩放比例.窗口将调整为图像大小.

平铺图像数据库可以填充来自imgur的随机图像,您也可以通过将它们自己存储在磁盘上来填充自己的图像.它位于标准的应用程序数据路径后缀/so-photomosaic/image.在那里添加获取的图像.启动时,图像数据库将在后台从磁盘重新填充 - 这就是您自己的磁贴图像的加载方式.实际上,所有图像处理都是在非GUI线程中完成的.在一个相当不起眼的5岁Core 2 OS X系统上,磁盘映像加载以大约5000张图像/秒进行.从imgur请求的图像是它们的小尺寸,或90x90.

使用4x4细分网格(divs参数to calcPropsFor)完成切片匹配.将图像下采样为4×4马赛克,并且将该网格中的连续像素的RGB颜色值存储在Props矢量中.这些向量的元素差异的平方和是拟合的度量.对于要替换的每个图块,图像根据它们的适合度进行分类,并且随机拾取一个最好的图像.随机性参数是从中随机选择图像的样本大小的幂.

它使用Qt 5和C++ 11.长度:300行,其中64个是随机图像源,25个是磁盘图像数据库,88个实际上是与马赛克有关.如果使用OpenCV或Eigen代替valarray/QImage,图像处理代码可能看起来和表现更好,但是哦.

此外,所有这些都可能是Mathematica中的50行:)

截图

# main.pro
# Make sure to re-run quake once this is set.
TEMPLATE = app
QT += widgets network concurrent
CONFIG += c++11
SOURCES += main.cpp
TARGET = photomosaic
Run Code Online (Sandbox Code Playgroud)
#include <QApplication>
#include <QLabel>
#include <QSlider>
#include <QPushButton>
#include <QCheckBox>
#include <QBoxLayout>
#include <QFileDialog>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRegularExpression>
#include <QImage>
#include <QPainter>
#include <QColor>
#include <QAtomicInt>
#include <QMutex>
#include <QtConcurrent>
#include <QStandardPaths>
#include <algorithm>
#include <functional>
#include <valarray>

/// Provides random images. There may be more than one response per request.
class RandomImageSource : public QObject {
  Q_OBJECT
  int m_parallelism;
  bool m_auto;
  QNetworkAccessManager m_mgr;
  QSet<QNetworkReply*> m_replies;
  QList<QUrl> m_deferred;
  QRegularExpression m_imgTagRE, m_imgUrlRE;
  QUrl m_randomGallery;
  void get(const QUrl & url) {
    if (m_replies.count() < m_parallelism) {
      QNetworkRequest req(url);
      req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
      m_replies.insert(m_mgr.get(req));
    } else
      m_deferred << url;
  }
  void finishReply(QNetworkReply * reply) {
    m_replies.remove(reply);
    if (reply) reply->deleteLater();
    if (! m_deferred.isEmpty()) get(m_deferred.takeLast());
    while (m_deferred.isEmpty() && m_auto) get(m_randomGallery);
  }
  Q_SLOT void rsp(QNetworkReply * reply) {
    auto loc = reply->header(QNetworkRequest::LocationHeader);
    if (loc.isValid()) {
      get(loc.toUrl()); // redirect
    } else {
      auto ct = reply->header(QNetworkRequest::ContentTypeHeader).toString();
      if (ct.startsWith("text/html"))
        foreach (QUrl url, parseImageUrls(reply->readAll()))
          get(url);
      else if (ct.startsWith("image")) {
        auto img = QImage::fromData(reply->readAll());
        img.setText("filename", m_imgUrlRE.match(reply->url().toString()).captured(1));
        if (!img.isNull()) emit rspImage(img);
      }
    }
    finishReply(reply);
  }
  QList<QUrl> parseImageUrls(const QByteArray & html) {
    QList<QUrl> urls;
    auto it = m_imgTagRE.globalMatch(QString::fromUtf8(html));
    while (it.hasNext()) { auto match = it.next(); // get small images
      urls << QUrl("http:" + match.captured(1) + "s" + match.captured(2)); }
    return urls;
  }
public:
  RandomImageSource(QObject * parent = 0) : QObject (parent),
    m_parallelism(20), m_auto(false),
    m_imgTagRE("<img src=\"(//i\\.imgur\\.com/[^.]+)(\\.[^\"]+)\""),
    m_imgUrlRE("http://i\\.imgur\\.com/(.+)$"),
    m_randomGallery("http://imgur.com/gallery/random")
  {
    connect(&m_mgr, SIGNAL(finished(QNetworkReply*)), SLOT(rsp(QNetworkReply*)));
  }
  Q_SLOT void reqImages(int count) {
    while (count--) get(m_randomGallery);
  }
  Q_SIGNAL void rspImage(const QImage &);
  bool automatic() const { return m_auto; }
  Q_SLOT void setAutomatic(bool a) { if ((m_auto = a)) finishReply(0); }
  int parallelism() const { return m_parallelism; }
  Q_SLOT void setParallelism(int p) { m_parallelism = p; if (m_auto) finishReply(0); }
};

/// Stores images on disk, and loads them in the background.
class ImageStorage : public QObject {
  Q_OBJECT
  QString const m_path;
public:
  ImageStorage() :
    m_path(QStandardPaths::writableLocation(QStandardPaths::DataLocation)
           + "/images/")
  { QDir().mkpath(m_path); }
  Q_SLOT void addImage(const QImage & img) {
    QString path = img.text("filename");
    if (path.isEmpty()) return;
    path.prepend(m_path);
    QtConcurrent::run([img, path]{ img.save(path); });
  }
  Q_SLOT void retrieveAll() {
    QString const path = m_path;
    QtConcurrent::run([this, path] {
      QStringList const images = QDir(path).entryList(QDir::Files);
      foreach (QString image, images) QtConcurrent::run([this, image, path] {
        QImage img; if (img.load(path + image)) emit retrieved(img);
      });
    });
  }
  Q_SIGNAL void retrieved(const QImage &);
};

/// A memory database of images. Finds best match to a given image.
class ImageDatabase : public QObject {
  Q_OBJECT
  typedef std::valarray<qreal> Props;
  typedef QPair<QImage, Props> ImageProps;
  QMutex mutable m_mutex;
  QList<ImageProps> m_images;
  static void inline addProps(Props & p, int i, QRgb rgb) {
    QColor const c = QColor::fromRgb(rgb);
    p[i+0] += c.redF(); p[i+1] += c.greenF(); p[i+2] += c.blueF();
  }
  static Props calcPropsFor(const QImage & img, int divs = 4) {
    Props props(0.0, 3 * divs * divs);
    std::valarray<int> counts(0, divs * divs);
    QSize div = img.size() / divs;
    for (int y = 0; y < img.height(); ++y)
      for (int x = 0; x < img.width(); ++x) {
        int slice = x/div.width() + (y*divs/div.height());
        if (slice >= divs*divs) continue;
        addProps(props, slice*3, img.pixel(x, y));
        counts[slice] ++;
      }
    for (size_t i = 0; i < props.size(); ++i) props[i] /= counts[i/3];
    return props;
  }
public:
  Q_SIGNAL void newImageCount(int);
  Q_SLOT void addImage(const QImage & img) {
    QtConcurrent::run([this, img]{
      Props props = calcPropsFor(img);
      QMutexLocker lock(&m_mutex);
      m_images << qMakePair(img, props);
      int count = m_images.count();
      lock.unlock();
      emit newImageCount(count);
    });
  }
  ImageProps bestMatchFor(const QImage & img, int randLog2) const {
    QMutexLocker lock(&m_mutex);
    QList<ImageProps> const images = m_images;
    lock.unlock();
    Props const props = calcPropsFor(img);
    typedef QPair<qreal, const ImageProps *> Match;
    QList<Match> matches; matches.reserve(images.size());
    std::transform(images.begin(), images.end(), std::back_inserter(matches),
                   [props](const ImageProps & prop){
      return qMakePair(pow(props - prop.second, 2).sum(), &prop);
    });
    std::sort(matches.begin(), matches.end(),
              [](Match a, Match b) { return b.first < a.first; });
    randLog2 = 1<<randLog2;
    return *(matches.end()-randLog2+qrand()%randLog2)->second;
  }
};

QImage getMosaic(QImage img, const ImageDatabase & db, int size, int randLog2)
{
  QPainter p(&img);
  for (int y = 0; y < img.height(); y += size)
    for (int x = 0; x < img.width(); x += size) {
      QImage r = db.bestMatchFor(img.copy(x, y, size, size), randLog2).first
          .scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
      p.drawImage(x, y, r);
    }
  return img;
}

class MosaicGenerator : public QObject {
  Q_OBJECT
  QPointer<ImageDatabase> m_db;
  int m_size, m_randLog2;
  QAtomicInt m_busy;
  QImage m_image;
  void update() {
    if (m_image.isNull() || m_busy.fetchAndAddOrdered(1)) return;
    QImage image = m_image;
    QtConcurrent::run([this, image]{ while (true) {
        emit hasMosaic(getMosaic(image, *m_db, m_size, m_randLog2));
        if (m_busy.testAndSetOrdered(1, 0)) return;
        m_busy.fetchAndStoreOrdered(1);
      }});
  }
public:
  MosaicGenerator(ImageDatabase * db) : m_db(db), m_size(16), m_randLog2(0) {}
  Q_SLOT void setImage(const QImage & img) { m_image = img; update(); }
  Q_SLOT void setSize(int s) { m_size = s; update(); }
  Q_SLOT void setRandLog2(int r) { m_randLog2 = r; update(); }
  Q_SIGNAL void hasMosaic(const QImage &);
};

class Window : public QWidget {
  Q_OBJECT
  bool m_showSource;
  QImage m_source, m_mosaic;
  QBoxLayout m_layout;
  QSlider m_parallelism, m_cellSize, m_randomness;
  QLabel m_imgCount, m_parCount, m_image;
  QPushButton m_add, m_load, m_toggle;
  MosaicGenerator m_gen;
  Q_SIGNAL void newSource(const QImage &);
  void updateImage() {
    const QImage & img = m_showSource ? m_source : m_mosaic;
    m_image.setPixmap(QPixmap::fromImage(img));
  }
public:
  Window(ImageDatabase * db, QWidget * parent = 0) : QWidget(parent),
    m_showSource(true), m_layout(QBoxLayout::TopToBottom, this),
    m_parallelism(Qt::Horizontal), m_cellSize(Qt::Horizontal),
    m_randomness(Qt::Horizontal), m_add("Fetch Images"),
    m_load("Open for Mosaic"), m_toggle("Toggle Mosaic"), m_gen(db)
  {
    QBoxLayout * row = new QBoxLayout(QBoxLayout::LeftToRight);
    row->addWidget(new QLabel("Images in DB:"));
    row->addWidget(&m_imgCount);
    row->addWidget(new QLabel("Fetch parallelism:"));
    row->addWidget(&m_parallelism);
    row->addWidget(&m_parCount);
    row->addWidget(&m_add);
    m_parallelism.setRange(1, 100);
    m_layout.addLayout(row);
    m_layout.addWidget(&m_image);
    row = new QBoxLayout(QBoxLayout::LeftToRight);
    row->addWidget(new QLabel("Cell Size:"));
    row->addWidget(&m_cellSize);
    row->addWidget(new QLabel("Randomness:"));
    row->addWidget(&m_randomness);
    m_cellSize.setRange(4, 64); m_cellSize.setTracking(false);
    m_randomness.setRange(0,6); m_randomness.setTracking(false);
    m_layout.addLayout(row);
    row = new QBoxLayout(QBoxLayout::LeftToRight);
    row->addWidget(&m_load);
    row->addWidget(&m_toggle);
    m_layout.addLayout(row);
    m_add.setCheckable(true);
    m_parCount.connect(&m_parallelism, SIGNAL(valueChanged(int)), SLOT(setNum(int)));
    connect(&m_add, SIGNAL(clicked(bool)), SIGNAL(reqAutoFetch(bool)));
    connect(&m_parallelism, SIGNAL(valueChanged(int)), SIGNAL(reqParallelism(int)));
    m_gen.connect(&m_cellSize, SIGNAL(valueChanged(int)), SLOT(setSize(int)));
    m_gen.connect(&m_randomness, SIGNAL(valueChanged(int)), SLOT(setRandLog2(int)));
    m_parallelism.setValue(20);
    m_cellSize.setValue(16);
    m_randomness.setValue(4);
    connect(&m_load, &QPushButton::clicked, [this]{
      QString file = QFileDialog::getOpenFileName(this);
      QtConcurrent::run([this, file]{
        QImage img; if (!img.load(file)) return;
        emit newSource(img);
      });
    });
    connect(this, &Window::newSource, [this](const QImage &img){
      m_source = m_mosaic = img; updateImage(); m_gen.setImage(m_source);
    });
    connect(&m_gen, &MosaicGenerator::hasMosaic, [this](const QImage &img){
      m_mosaic = img; updateImage();
    });
    connect(&m_toggle, &QPushButton::clicked, [this]{
      m_showSource = !m_showSource; updateImage();
    });
  }
  Q_SLOT void setImageCount(int n) { m_imgCount.setNum(n); }
  Q_SIGNAL void reqAutoFetch(bool);
  Q_SIGNAL void reqParallelism(int);
};

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  a.setOrganizationDomain("stackoverflow.com");
  a.setApplicationName("so-photomosaic");
  RandomImageSource src;
  ImageDatabase db;
  ImageStorage stg;
  Window ui(&db);
  db.connect(&src, SIGNAL(rspImage(QImage)), SLOT(addImage(QImage)));
  stg.connect(&src, SIGNAL(rspImage(QImage)), SLOT(addImage(QImage)));
  db.connect(&stg, SIGNAL(retrieved(QImage)), SLOT(addImage(QImage)));
  ui.connect(&db, SIGNAL(newImageCount(int)), SLOT(setImageCount(int)));
  src.connect(&ui, SIGNAL(reqAutoFetch(bool)), SLOT(setAutomatic(bool)));
  src.connect(&ui, SIGNAL(reqParallelism(int)), SLOT(setParallelism(int)));
  stg.retrieveAll();
  ui.show();
  return a.exec();
}

#include "main.moc"
Run Code Online (Sandbox Code Playgroud)