【问题标题】:How to merge a lot of square images via OpenCV?如何通过 OpenCV 合并大量方形图像?
【发布时间】:2019-12-21 07:29:38
【问题描述】:

如何使用 OpenCV 将如下图像合并为单个图像(水平和垂直都可以有任意数量)?有没有内置的解决方案可以做到这一点?

附加部分:

【问题讨论】:

  • 你在问如何解决这个难题?
  • 看来你需要某种图像拼接。 OpenCV 中有一些拼接功能,但据我所知,图像应该有点重叠。

标签: opencv opencv3.0


【解决方案1】:

好吧,看来我已经完成了拼图:

主要步骤:

  1. 比较每对图像(拼图)以了解相对位置(findRelativePositionsgetPosition)。
  2. 构建一张知道棋子相对位置的地图(buildPuzzlebuilfForPiece
  3. 创建最终的拼贴画,将每个图像放在正确的位置(buildPuzzle 的最后部分)。

第 1 步中的 A 和 B 之间的比较是检查以下之间的相似性(绝对差异之和):

  • B 是 A 的 NORTH:A 第一行和 B 最后一行;
  • B 位于 A 的南端:A 最后一行,B 第一行;
  • B 位于 A 的西边:A 最后一列,B 第一列;
  • B 是东到 A:A 第一列和 B 最后一列。

由于图像不重叠,但我们可以假设限制行(列)非常相似,关键方面是使用(ad-hoc)阈值来区分限制件与否。这在函数getPosition 中处理,阈值参数threshold

这里是完整的代码。如果有什么不清楚的地方请告诉我。

#include <opencv2\opencv.hpp>
#include <algorithm>
#include <set>

using namespace std;
using namespace cv;

enum Direction
{
    NORTH = 0,
    SOUTH,
    WEST,
    EAST
};


int getPosition(const Mat3b& A, const Mat3b& B, double& cost)
{
    Mat hsvA, hsvB;
    cvtColor(A, hsvA, COLOR_BGR2HSV);
    cvtColor(B, hsvB, COLOR_BGR2HSV);

    int threshold = 1000;

    // Check NORTH
    Mat3b AN = hsvA(Range(0, 1), Range::all());
    Mat3b BS = hsvB(Range(B.rows - 1, B.rows), Range::all());

    Mat3b AN_BS;
    absdiff(AN, BS, AN_BS);
    Scalar scoreN = sum(AN_BS);

    // Check SOUTH
    Mat3b AS = hsvA(Range(A.rows - 1, A.rows), Range::all());
    Mat3b BN = hsvB(Range(0, 1), Range::all());

    Mat3b AS_BN;
    absdiff(AS, BN, AS_BN);
    Scalar scoreS = sum(AS_BN);

    // Check WEST
    Mat3b AW = hsvA(Range::all(), Range(A.cols - 1, A.cols));
    Mat3b BE = hsvB(Range::all(), Range(0, 1));

    Mat3b AW_BE;
    absdiff(AW, BE, AW_BE);
    Scalar scoreW = sum(AW_BE);

    // Check EAST
    Mat3b AE = hsvA(Range::all(), Range(0, 1));
    Mat3b BW = hsvB(Range::all(), Range(B.cols - 1, B.cols));

    Mat3b AE_BW;
    absdiff(AE, BW, AE_BW);
    Scalar scoreE = sum(AE_BW);

    vector<double> scores{ scoreN[0], scoreS[0], scoreW[0], scoreE[0] };
    int idx_min = distance(scores.begin(), min_element(scores.begin(), scores.end()));
    int direction = (scores[idx_min] < threshold) ? idx_min : -1;
    cost = scores[idx_min];
    return direction;
}

void resolveConflicts(Mat1i& positions, Mat1d& costs)
{
    for (int c = 0; c < 4; ++c)
    {
        // Search for duplicate pieces in each column

        set<int> pieces;
        set<int> dups;

        for (int r = 0; r < positions.rows; ++r)
        {
            int label = positions(r, c);
            if (label >= 0)
            {
                if (pieces.count(label) == 1)
                {
                    dups.insert(label);
                }
                else
                {
                    pieces.insert(label);
                }
            }
        }

        if (dups.size() > 0)
        {
            int min_idx = -1;
            for (int duplicate : dups)
            {
                // Find minimum cost position
                Mat1d column = costs.col(c);
                min_idx = distance(column.begin(), min_element(column.begin(), column.end()));

                // Keep only minimum cost position
                for (int ir = 0; ir < positions.rows; ++ir)
                {
                    int label = positions(ir, c);
                    if ((label == duplicate) && (ir != min_idx))
                    {
                        positions(ir, c) = -1;
                    }
                }
            }
        }
    }
}

void findRelativePositions(const vector<Mat3b>& pieces, Mat1i& positions)
{
    positions = Mat1i(pieces.size(), 4, -1);
    Mat1d costs(pieces.size(), 4, DBL_MAX);

    for (int i = 0; i < pieces.size(); ++i)
    {
        for (int j = i + 1; j < pieces.size(); ++j)
        {
            double cost;
            int pos = getPosition(pieces[i], pieces[j], cost);

            if (pos >= 0)
            {
                if (costs(i, pos) > cost)
                {
                    positions(i, pos) = j;
                    costs(i, pos) = cost;
                    switch (pos)
                    {
                    case NORTH:
                        positions(j, SOUTH) = i;
                        costs(j, SOUTH) = cost;
                        break;
                    case SOUTH:
                        positions(j, NORTH) = i;
                        costs(j, NORTH) = cost;
                        break;
                    case WEST:
                        positions(j, EAST) = i;
                        costs(j, EAST) = cost;
                        break;
                    case EAST:
                        positions(j, WEST) = i;
                        costs(j, WEST) = cost;
                        break;
                    }
                }
            }
        }
    }

    resolveConflicts(positions, costs);
}


void builfForPiece(int idx_piece, set<int>& posed, Mat1i& labels, const Mat1i& positions)
{
    Point pos(-1, -1);

    // Find idx_piece on grid;
    for (int r = 0; r < labels.rows; ++r)
    {
        for (int c = 0; c < labels.cols; ++c)
        {
            if (labels(r, c) == idx_piece)
            {
                pos = Point(c, r);
                break;
            }
        }
        if (pos.x >= 0) break;
    }

    if (pos.x < 0) return;


    // Put connected pieces
    for (int c = 0; c < 4; ++c)
    {
        int next = positions(idx_piece, c);
        if (next > 0)
        {
            switch (c)
            {
            case NORTH:
                labels(Point(pos.x, pos.y - 1)) = next;
                posed.insert(next);
                break;
            case SOUTH:
                labels(Point(pos.x, pos.y + 1)) = next;
                posed.insert(next);
                break;
            case WEST:
                labels(Point(pos.x + 1, pos.y)) = next;
                posed.insert(next);
                break;
            case EAST:
                labels(Point(pos.x - 1, pos.y)) = next;
                posed.insert(next);
                break;
            }
        }
    }
}


Mat3b buildPuzzle(const vector<Mat3b>& pieces, const Mat1i& positions, Size sz)
{
    int n_pieces = pieces.size();
    set<int> posed;
    set<int> todo;
    for (int i = 0; i < n_pieces; ++i) todo.insert(i);

    Mat1i labels(n_pieces * 2 + 1, n_pieces * 2 + 1, -1);

    // Place first element in the center
    todo.erase(0);
    labels(Point(n_pieces, n_pieces)) = 0;
    posed.insert(0);
    builfForPiece(0, posed, labels, positions);

    // Build puzzle starting from the already placed elements
    while (todo.size() > 0)
    {
        auto it = todo.begin();
        int next = -1;
        do
        {
            next = *it;
            ++it;
        } while (posed.count(next) == 0 && it != todo.end());


        todo.erase(next);
        builfForPiece(next, posed, labels, positions);
    }

    // Posed all pieces, now collage!

    vector<Point> pieces_position;
    Mat1b mask = labels >= 0;
    findNonZero(mask, pieces_position);
    Rect roi = boundingRect(pieces_position);

    Mat1i lbls = labels(roi);
    Mat3b collage(roi.height * sz.height, roi.width * sz.width, Vec3b(0, 0, 0));

    for (int r = 0; r < lbls.rows; ++r)
    {
        for (int c = 0; c < lbls.cols; ++c)
        {
            if (lbls(r, c) >= 0)
            {
                Rect rect(c*sz.width, r*sz.height, sz.width, sz.height);
                pieces[lbls(r, c)].copyTo(collage(rect));
            }
        }
    }

    return collage;
}


int main()
{
    // Load images

    vector<String> filenames;
    glob("D:\\SO\\img\\puzzle*", filenames);

    vector<Mat3b> pieces(filenames.size());

    for (int i = 0; i < filenames.size(); ++i)
    {
        pieces[i] = imread(filenames[i], IMREAD_COLOR);
    }

    // Find Relative positions
    Mat1i positions;
    findRelativePositions(pieces, positions);

    // Build the puzzle
    Mat3b puzzle = buildPuzzle(pieces, positions, pieces[0].size());

    imshow("Puzzle", puzzle);
    waitKey();

    return 0;
}

注意

  1. 不,没有执行此操作的内置解决方案。由于图像没有重叠,因此无法进行图像拼接。
  2. 我不能保证这适用于所有谜题,但应该适用于大多数谜题。
  3. 我可能应该工作这几个小时,但这很有趣 :D

编辑

在以前的代码版本中添加更多拼图会产生错误的结果。这是由于(错误的)假设至多一个片段足够好 可以与给定片段连接。

现在我添加了一个成本矩阵,只有最小成本块被保存为给定块的邻居。 我还添加了一个resolveConflicts 函数,可以避免一件可以与多件合并(在非冲突位置)。

这是添加更多片段的结果:


更新

增加拼图数量后的注意事项:

  • 这个解决方案依赖于块的输入顺序,因为事实证明它有一个 greedy 方法来寻找邻居。
  • 在搜索邻居时,最好比较 HSV 空间中的 H 通道。我用这个改进更新了上面的代码。
  • 最终的解决方案可能需要某种全局成本矩阵的全局最小化。这将使该方法独立于输入顺序。我会尽快回来的。

【讨论】:

  • 哈哈哈哈哈哈。 +1。最后一条关于工作的评论害死我了。
  • 感谢您的详细解答!不幸的是,它在几乎所有其他情况下都不能正常工作。例如,如果您将这张图片添加到初始图片集 -- i.imgur.com/V76ZGav.png,您会发现合并失败
  • @Miki 非常感谢!如果你给我你的 Skype / 电子邮件 btw 会很酷
  • @FrozenHeart 更新了代码和描述。告诉我。
  • @FrozenHeart 有道理,边框在这里并没有那么有区别……这使比较功能更加强大。我大概明天看看。只有当你保证不再有图片时.. ;D
【解决方案2】:

将这些图像加载为 OpenCV Mat 后,您可以使用以下方法垂直或水平连接这些 Mat:

Mat A, B; // Images that will be concatenated
Mat H; // Here we will concatenate A and B horizontally
Mat V; // Here we will concatenate A and B vertically
hconcat(A, B, H);
vconcat(A, B, V);

如果您需要连接两个以上的图像,您可以递归地使用这些方法。

顺便说一句,我认为这些方法没有包含在 OpenCV 文档中,但我过去使用过它们。

【讨论】:

  • 不,你没有正确理解我的意思。我不知道应该将哪些图像准确地连接到其他图像
  • 对不起。我想没有内置的方法可以解决这个问题。但是,将每个方格的第一/最后一行/列与其他方格的第一/最后一行/列进行比较并选择最相似的一方会很容易。
  • @FrozenHeart:我想你正在寻找“图像注册”
猜你喜欢
  • 1970-01-01
  • 2020-01-29
  • 2016-01-19
  • 2019-08-28
  • 1970-01-01
  • 1970-01-01
  • 2023-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多