2014-03-26 2 views
5

를 사용하여 내 자신의 사진 모자이크 응용 프로그램 만들기 :
enter image description hereQt는 이것은 내가 지금까지 한 일의 출력 C++

이것은 단지 흑백 이미지를 보여줍니다하지만이 코드가 너무 컬러 이미지에 적용됩니다. 코드는 기본적으로 작은 데이터베이스의 작은 이미지를 사용하여 큰 이미지를 채 웁니다.
그래서 여기에 내가 붙어 있고, 우둔한 곳입니다.
How can I get an effect like this one. 또는 this one.
Qn1 : 다른 입력 (큰 이미지) * (효과를 볼 수있는 이미지) *을 가져 와서 병합해야하지만 어떻게해야합니까?
Qn2 : 사진 모자이크의 장점을 어떻게 평가할 수 있습니까? 나는 이것을 위해 작성된 유전 알고리즘을 가지고 있지만, 적합성 함수 (돌연변이와 크로스 오버 작업은 완벽하게)를 고칠 수 없다.
1. 위의 이미지와 모자이크 만들 수있다있는 이미지의 대체 픽셀을 가지고 :

이것은 내가 (QN1에 대한) 생각할 수있는 것입니다.
2. 모자이크가 만들어 져야하는 위와 입력 이미지의 픽셀 값의 평균을 취합니다.
그러나 장점을 평가할 단서가 없습니다.

+1

이 멋진 도전이 될 것입니다 암호. 완전한 프로젝트가 아주 작을 수 있다고 믿기 때문에 실제로 Qt의 힘을 보여줄 것입니다. 물론 200 라인 (모든 것) 아래! –

+0

@KubaOber : 해결할 수있는 몇 가지 힌트/알고리즘을 제공해 주시겠습니까? 제 자신의 코드를 시도해 볼 수 있습니다. 200 라인 아래에서 놀라운 소리! –

답변

4

다음은 자체 포함 스케치입니다. 모자이크 처리 알고리즘은 an excellent reference에 구현 된 알고리즘의 중간 단계에 있습니다. 그것은 2 시간의 일 동안 충분히 잘 작동한다고 생각합니다. 저는 코드가 합리적으로 정확하도록 노력했습니다. 두 가지주의 사항은 독자의 연습 문제로 남았습니다.

  1. 나는 작업자 스레드를 추적하고 있지 않다 - 당신은 노동자가 활성화되어있는 동안, 출구에 충돌이 예상되는 응용 프로그램을 종료하려고하면. 이것은 좋지는 않지만 그렇지 않으면 전반적인 기능에 영향을주지 않습니다. 디스크에 손상된 이미지가 약간 남아있을 수 있지만 다시로드 할 때 무시해야합니다.

  2. 레이블에 표시된 이미지의 크기 조정이 없습니다. 창 크기가 이미지 크기로 조정됩니다.

타일 이미지 데이터베이스는 imgur에서 임의의 이미지로 채울 수 있으며 직접 디스크에 저장하여 이미지로 채울 수도 있습니다. 접미사가 표준 응용 프로그램 데이터 경로 인 /so-photomosaic/image에 있습니다. 가져온 이미지가 거기에 추가됩니다. 시작시, 이미지 데이터베이스는 백그라운드에서 디스크로부터 다시 채워집니다 - 이것이 자신의 타일 이미지가로드되는 방식입니다. 실제로 모든 이미지 처리는 GUI가 아닌 스레드에서 수행됩니다. 오히려 겸손한 5 년 된 Core 2 OS X 시스템에서 디스크 이미지로드는 약 5000 이미지/s에서 진행됩니다. imgur에서 요청한 이미지는 크기가 작거나 90x90입니다.

타일 일치는 4x4 서브 디비전 그리드 (divs 매개 변수는 calcPropsFor)로 수행됩니다. 이미지는 4x4 모자이크로 다운 샘플링되며 해당 그리드의 연속 픽셀의 RGB 색상 값은 Props 벡터에 저장됩니다. 벡터의 요소들의 차이의 제곱 된 합은 적합성의 척도입니다. 교체 할 각 타일에 대해 이미지는 적합도에 따라 정렬되고 가장 좋은 타일 중 하나가 무작위로 선택됩니다. 무작위 매개 변수는 이미지가 무작위로 선택되는 표본 크기의 배율입니다.

Qt 5와 C++ 11을 사용합니다. 길이 : 300 라인, 그 중 64 개는 무작위 이미지 소스, 25 개는 디스크 이미지 데이터베이스, 88 개는 실제로 모자이크와 관련이 있습니다.이미지 처리 코드는 valarray/QImage 대신 OpenCV 또는 Eigen을 사용하면 더 잘 보이고 잘 작동하지만 오히려 잘 수행됩니다. 라인의 최소한의 대답을 줄 수 있습니다

또한,이 모든 티카에서 아마 50 선 :

screenshot

# 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 
#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" 
+0

awesomeness에 경례 !!!!! 놀랄 만한! : D –

+0

@SahilSareen이 프로젝트를 사용하려면 * 정확히 * 두 개의 파일이 필요합니다. .pro 파일과 main.cpp 파일. MainWindow에는 * 아무 것도 없습니다. 당신은 거기에없는 것들을 추측함으로써 인생을 매우 복잡하게 만듭니다. 그것은 작동합니다. 이 두 파일을이 복사물을 복사하여 새 폴더에 붙여넣고 Qt Creator에서 열고 Qt 버전을 선택하고 Ctrl-R (또는 Cmd-R)을 누릅니다. 그것은 문자 그대로입니다. 방금 시도 했어. –

+0

Workeddddddddd !!!!!!!!! 감사! –

관련 문제