简体   繁体   中英

Algorithm for horizontally tiling portrait (w<h) images into 16:9 ratio landscape montages

I want to write a program takes a group of portrait oriented (ie width < height) image files with the same height but different widths, horizontally tiles them into N number of composite images, in such a way that each composite image is as close to a 16:9 ratio as possible. Is this even possible? The closest algorithm I could find to what I want is the https://en.wikipedia.org/wiki/Knapsack_problem .

I don't have enough rep to comment so I'll post this as an answer.

You have a set of images with the same height so you know the width that you're targeting for each composite image (let's just say each image has 900 height, so you're going for 1600 width).

If we set our cost function as the sum of the difference of each (composite image's width - 1600) then this turns into a minimization type of problem where you're trying to find the set of composite images that has the lowest cost. Depending on how many images you have it may be feasible to simply attempt all possible combinations (with some limit on how many images can go into a single composite) and choose the best resulting score.

For something simple we can take a sort of greedy-alg approach to the problem: Add images to a composite until it overflows (>1600). Once it overflows, take out the last image and swap in the next one. Do this for every remaining image and record the score (composite_width - 1600). Choose the best composite and remove those images from the pool. Repeat this process until you run out of images.

This is an interesting problem. If I have some free time later I'll explore it more seriously, but there's a lot of literature on min-cut problems so there's probably some official algorithm that's similar or can be adapted for this.

Edit:

Here's a quick and dirty implementation of the greedy algorithm I described. I also have a second greedy-alg that assumes a pre-sorted (low to high) list by width. The first one has an average error of 143.15 for 100 images with widths between [200, 1200]. The second one has an average error of 64.19 with the same parameters.

import random

# returns average error of a list of compositions
def getError(target, comps):
    total = 0;
    for comp in comps:
        total += abs(target - sum(comp));
    total /= len(comps);
    return round(total, 2);

# randomly generate 'n' images within bounds
def getImgs(num, low, high):
    imgs = [];
    for a in range(num):
        imgs.append(random.randint(low, high));
    return imgs;

# greedy approach
def greedy1(target, imgs):
    # end containers
    comps = [];
    copy = imgs[:];
    while copy:
        # go until overflow or empty
        comp = [];
        total = 0;
        while total < target and copy:
            comp.append(copy[0]);
            total += copy[0];
            copy = copy[1:];

        # remove the last element
        copy.append(comp[-1]);
        total -= comp[-1];
        comp = comp[:-1];

        # start evaluating
        scores = [];
        best_score = 1000000000000; # A REALLY BIG NUMBER
        best_index = -1;
        for a in range(len(copy)):
            curr_score = abs(target - (total + copy[a]));
            if curr_score < best_score:
                best_score = curr_score;
                best_index = a;

        # choose best
        comp.append(copy[best_index]);
        del copy[best_index];
        comps.append(comp);
    return comps;

# another greedy approach (assumes sorted images low->high)
def greedy2(target, imgs):
    # end containers
    comps = [];
    copy = imgs[:];
    while copy:
        # go until overflow or empty
        comp = [];
        total = 0;
        while total < target and copy:
            comp.append(copy[-1]);
            total += copy[-1];
            copy = copy[:-1];

        # remove the last element
        copy.append(comp[-1]);
        total -= comp[-1];
        comp = comp[:-1];

        # go until overflow or past index
        index = 0;
        while index < len(copy) and total + copy[index] < target:
            index += 1;
        if index >= len(copy):
            index = len(copy) - 1;
        if copy:
            comp.append(copy[index]);
            del copy[index];
        comps.append(comp);
    return comps;


# target
target = 1600;

# randomly generate "images"
num = 100;
low = 200;
high = 1200;

# run iterations
iters = 10000;
avg_error1 = 0;
avg_error2 = 0;
for a in range(iters):
    # new set of images
    imgs = getImgs(num, low, high);

    # attempt to fill
    comps = greedy1(target, imgs);
    error1 = getError(target, comps);
    avg_error1 += error1;

    # attemp to fill 2
    imgs.sort();
    comps = greedy2(target, imgs);
    error2 = getError(target, comps);
    avg_error2 += error2;

    # sanity progress check
    if a%200 == 0:
        print(a);

# average
avg_error1 /= iters;
avg_error1 = round(avg_error1, 2);
avg_error2 /= iters;
avg_error2 = round(avg_error2, 2);
print("Average 1: " + str(avg_error1));
print("Average 2: " + str(avg_error2));

# Results for 100 imgs over 10000 iters
# Average 1: 143.15 
# Average 2: 64.19

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM