• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers

How do I automatically straighten the horizon in a photo?

Explorer ,
Jan 11, 2023 Jan 11, 2023

Copy link to clipboard

Copied

Hello. Please help me solve the problem.

 

I have a lot of studio photos taken without a tripod, so the horizon is often tilted at different angles.
There is a clear contrast between the wall and the floor.

 

I need to automatically straighten the horizon in these photos, no change in perspective, no cropping. Just the angle of the slope.

Lightroom, camera raw are not up to the task.


The manual method, using a ruler, is not for me either.

Perhaps there is some Script that can help me solve my problem?

 

*Sample photo added.

IMG_20230112_005742_828.jpg

TOPICS
Actions and scripting

Views

371

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Expert , Jan 13, 2023 Jan 13, 2023
quote

I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)


By @Andry_J

 

Your ideas sound reasonable, but don't forget that we are limited by the runtime environment. Photoshop's scripting environment severely limits our ability to analyze images.

 

There are several ways to solve your problem, but all of them have some disadvantages and depend heavily on how the rest of the images w

...

Likes

Translate

Translate
Explorer ,
Jan 11, 2023 Jan 11, 2023

Copy link to clipboard

Copied

IMG_20230112_014128_981.jpg

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

The Upright feature in Camera Raw may be your best bet for two reasons: It can quickly correct multiple selected images in one click with no need for a script or action, and it can analyze each image and apply a different correct solution for each image. You need the second feature because the error is different in each picture.

 

And the error variations are challenging enough that only one Upright option I tried could fix both pictures with no manual intervention: The Full option. For some reason, the Level and Vertical options did something wrong on at least one of the images. But with the Full option, both images were corrected in one click, as shown in the demo below. Hopefully this will work as well on many more images than just these two. Then you could select any number of images and correct them all in one click.

 

Camera Raw Upright full.gif

 

The images were opened into Camera Raw from Adobe Bridge. Of course this works only if JPEG files are enabled for Camera Raw. (In Camera Raw preferences, File Handling, JPEG, HEIC, and TIFF Handling.) Camera Raw is also available as a filter in Photoshop, but using the filter version is not recommended because it works only for a layer in the current document, so you can’t load multiple images into the Camera Raw filter.

 

The Upright feature works the same way in Lightroom Classic, and you can batch-apply it using its Sync or Auto Sync features.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

Hi. Thanks for the answers, but I wrote in the description that this method doesn't work for me.

 

I will explain why. There are several reasons:

  1. Straightening Camera Raw and Lightroom crops the image. I can't allow any part of the image to be lost, because often the legs will be cropped.
    2. Using this method, the picture is distorted, it changes the perspective. I need the picture to remain original and only change the angle of the picture.

 

I'm even fine with leaving transparent areas on the picture that appear when I tilt the image.

bad good.jpg

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

quote

I'm even fine with leaving transparent areas on the picture that appear when I tilt the image.

By @Andry_J

 

Oh, OK. Originally I thought that would be a big problem because in the picture of the female with no margin at the bottom of the frame, any rotation-only solution would either cut off the feet (to avoid transparent areas) or leave a transparent area (to avoid cutting off the feet). But if you are OK with transparent areas, then a rotation-only solution could work. And maybe the transparent area could be filled in with Content-Aware Fill, if it’s just the floor.

 

But the challenge now is in automating all of that so you don’t have to do much manually. I am not good with scripting so unless someone like Stephen knows how, your solution might be outside Adobe software, such as the examples you found on the Internet.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

Yes, that's why transparent parts are not a problem. Just with Content-Aware Fill I made an action that removes empty areas🙂

 

Thank you for your answers!

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

@Andry_J – I thought that due to the volume of images, you were looking for an automated method, however, it appears that you are OK with doing this manually on each image.

 

Sorry to ask again, but you didn't reply as to why the ruler + straighten option doesn't work for you.

 

You can make the Background image into a floating layer and expand the canvas a little if needed. Then run the ruler tool over the horizon. Then run the Straighten.jsx script from the ruler toolbar button. Next use Image/Trim to remove transparent pixels. Then content aware fill etc. All of this can be recorded into an action to semi-automate the task.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

Sorry, I answered that question in another post.

 

The ruler tool really doesn't work for me, as I need full automation.


I get hundreds of photos a day, and even semi-automation is very time consuming.

 

I want to use my straightening as one step in full automation. That's why I don't want an option that crops out parts of the original image, changes perspective, or alters the original image in any way.

The only thing that works for me is to change the angle of the picture without losing any of its parts.

 

My algorithm would look like this:
- Changing the angle of the image (without any cropping)
- Transparent holes are restored with Content-Aware Fill.
- Automatic cropping of the model, with the required distances on all sides of the canvas. Model centering.
- some automatic manipulations with color.
- saving the finished picture with the desired name, format, size, to the desired location.

 

This is all in one action that I will apply to batch processing hundreds of photos every day.
I already have all of these steps implemented except one. Except for that first step with alignment!🤯

 

That's why I got so caught up in this topic)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

There are limited options. The automated methods are not acceptable to you. The manual method is not acceptable due to the volume of images. So I'm out of ideas. Good luck!

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2023 Jan 13, 2023

Copy link to clipboard

Copied

Hopefully if you get hundreds of these photos a day, the quality is better for most of them. I didn't touch on this because its off-topic but the easiest process is to get it right in camera.

Maybe its time to go back to whomever is sending you these photos and begin rejecting the ones that are not up to spec. There is no automated method to do what you want, period.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2023 Jan 13, 2023

Copy link to clipboard

Copied

I agree.  I've never had success with automated straightening. Most often, it's a fiddly manual process.

 

 

Nancy O'Shea, Product User & Community Expert
Alt-Web Design & Publishing ~ Web : Print : Graphics : Media

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 11, 2023 Jan 11, 2023

Copy link to clipboard

Copied

quote

Lightroom, camera raw are not up to the task.

 

By @Andry_J

 

Lr, ACR and the CR Filter in Photoshop was going to be my first and only suggestion (Geometry/Upright/Level).

 

Why isn't it up to the task?

 

quoteThe manual method, using a ruler, is not for me either.

 

Why?

 

quote

Perhaps there is some Script that can help me solve my problem?


Scripts can't perform magic, they need to leverage underlying features to correct geometry. For example, it is possible to script the Geometry/Upright/Vertical in the CR Filter in Photoshop. This would not use static values, it would be adaptive/variable for each image.

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)

 

Here's my guess:

1. To align the image, we need to find 2 points, connect them with a line, and find out what is the angle between this line and the horizon. This value will be the angle by which we need to rotate the original image.

1. Угол наклона.jpg

 

2. A similar method, we find 2 points and compare the distance from them to the edge of the canvas. The scrip should find the difference in distance and rotate the original image by half of this difference? (L1-L2)\2.
"I'm very bad at geometry too :), so don't judge me harshly."

2. Расстояние до края картинки.jpg

 

3. I found the third method on the Internet.
It uses several methods, such as:
- Canny edge detector (https://en.wikipedia.org/wiki/Canny_edge_detector)
- Hough transform (https://en.wikipedia.org/wiki/Hough_transform)

In the original this article is in another language, so I tried to translate it and saved it in a pdf.
It describes step by step how this algorithm is implemented.

https://drive.google.com/drive/folders/14KnwoBWpd7lpVNgUBb-KTHGLJpKOTY3u?usp=sharing 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

I think there are many ways in which we can find the extremes of "A" and "B."


For example:


1. Cut out the image inside except for a few pixels on the sides.
2. Using channels and levels to achieve a mask. Remove everything except the floor parts. And with the help of selection, make a stroke around these areas. As a result, we will have vector points, the coordinates of which can be used to calculate everything we need (?).

That's like the first option I had in my head to find vector coordinates, maybe I'm wrong about something.

 

1.png2.png

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2023 Jan 13, 2023

Copy link to clipboard

Copied

quote

I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)


By @Andry_J

 

Your ideas sound reasonable, but don't forget that we are limited by the runtime environment. Photoshop's scripting environment severely limits our ability to analyze images.

 

There are several ways to solve your problem, but all of them have some disadvantages and depend heavily on how the rest of the images will be similar to the samples you provided.

 

I follow this logic:

  1. In order not to waste time on channel-by-channel processing, we translate the image into grayscale
  2. We get two full-height strips with a width of 1 pixel on the left and right of the image
  3. Save each strip to a separate raw file
  4. Read the brightness of the pixels from these files
  5. We assume that the composition of the rest of the shots is similar to your samples: 80-90% is the wall, the rest is the floor. We sum up the brightness of all the pixels in the strip and find the average. Since the wall takes up most of the image, the average brightness will always be closer to it, and the floor will be different.
  6. Find the color difference of each pixel between the average brightness value of each strip and build a bit mask based on this (1 - the color is different, 0 - the color is not different)
  7. Since the floor is always below, we count how many pixels from the bottom differ from the average (with an error margin of no more than 5 pixels in a row)
  8. As a result, we get two points - left and right, find the angle between them and rotate the image to it 

 

The script is not perfect, but it works well with the examples above.

 

var apl = new AM('application'),
    doc = new AM('document'),
    lr = new AM('layer'),
    floorY = [];
const ERR_RATE = 5;
try {
    if (apl.getProperty('numberOfDocuments')) {
        var docRes = doc.getProperty('resolution'),
            docW = doc.getProperty('width') * docRes / 72,
            docH = doc.getProperty('height') * docRes / 72;
        activeDocument.suspendHistory('Find floor line', 'function() {}')
        activeDocument.suspendHistory('Get left stripe', 'measureDocument(floorY)')
        doc.stepBack();
        activeDocument.suspendHistory('Get right stripe', 'measureDocument(floorY)')
        doc.stepBack();
        activeDocument.suspendHistory('Rotate Document', 'rotateDocument()')
    }
} catch (e) { alert('A lot of things can go wrong in this script. :(\n\n' + e) }
function measureDocument(floor) {
    doc.flatten()
    doc.convertToGrayscale();
    floor.length ? doc.selectStrip(0, docW - 1, docH, docW) : doc.selectStrip(0, 0, docH, 1)
    doc.crop()
    var f = new File(Folder.temp + '/colors.raw');
    doc.saveToRAW(f)
    floor.push(findFloor(f));
}
function rotateDocument() {
    var title = lr.getProperty('name'),
        id = lr.getProperty('layerID')
    lr.duplicate(title)
    lr.deleteLayer(id)
    lr.rotate(Math.atan2(floorY[1] - floorY[0], docW) * 180 / Math.PI)
}
function findFloor(f) {
    var content = '';
    if (f.exists) {
        f.open('r');
        f.encoding = "BINARY";
        content = f.read();
        f.close();
        f.remove();
        var colors = function (s) {
            var m = 0, c = [];
            for (var i = 0; i < s.length; i++) {
                var k = s.charCodeAt(i); m += k; c.push(k)
            };
            return { colors: c, median: m / s.length }
        }(content),
            dE = function (c) {
                var m = 0; d = [];
                for (var i = 0; i < c.colors.length; i++) {
                    var k = Math.sqrt(Math.pow(c.median - c.colors[i], 2)); d.push(k); m += k
                };
                return { dE: d, median: m / c.colors.length }
            }(colors),
            threshold = function (dE) {
                var t = [];
                for (var i = 0; i < dE.dE.length; i++)
                    t.push(dE.dE[i] > dE.median ? 1 : 0);
                return t.reverse();
            }(dE);
        var height = 0,
            err = 0;
        for (var i = 0; i < threshold.length; i++) {
            height++
            if (threshold[i] == 1) {
                err = 0
            } else {
                err++
                if (err >= ERR_RATE) {
                    height -= err;
                    break;
                }
            }
        }
    }
    return height;
}
function AM(target) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;
    target = target ? s2t(target) : null;
    this.getProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        return getDescValue(executeActionGet(r), property)
    }
    this.hasProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        return executeActionGet(r).hasKey(property)
    }
    this.convertToGrayscale = function () {
        (d = new ActionDescriptor()).putClass(s2t("to"), s2t("grayscaleMode"));
        executeAction(s2t("convertMode"), d, DialogModes.NO);
    }
    this.selectStrip = function (top, left, bottom, right) {
        (r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        (d1 = new ActionDescriptor()).putUnitDouble(s2t("top"), s2t("pixelsUnit"), top);
        d1.putUnitDouble(s2t("left"), s2t("pixelsUnit"), left);
        d1.putUnitDouble(s2t("bottom"), s2t("pixelsUnit"), bottom);
        d1.putUnitDouble(s2t("right"), s2t("pixelsUnit"), right);
        d.putObject(s2t("to"), s2t("rectangle"), d1);
        executeAction(s2t("set"), d, DialogModes.NO);
    }
    this.flatten = function () {
        executeAction(s2t("flattenImage"), new ActionDescriptor(), DialogModes.NO);
    }
    this.crop = function () {
        (d = new ActionDescriptor()).putBoolean(s2t("delete"), true);
        executeAction(s2t("crop"), d, DialogModes.NO);
    }
    this.saveToRAW = function (f) {
        (d = new ActionDescriptor()).putBoolean(s2t('copy'), true);
        (d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("rawFormat"), d);
        d1.putPath(s2t("in"), f);
        executeAction(s2t("save"), d1, DialogModes.NO);
    }
    this.stepBack = function () {
        (r = new ActionReference()).putEnumerated(charIDToTypeID('HstS'), s2t('ordinal'), s2t('previous'));
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        executeAction(s2t('select'), d, DialogModes.NO);
    }
    this.duplicate = function (title) {
        (r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        d.putString(s2t("name"), title);
        executeAction(s2t("duplicate"), d, DialogModes.NO);
    }
    this.deleteLayer = function (id) {
        (r = new ActionReference()).putIdentifier(s2t("layer"), id);
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        executeAction(s2t("delete"), d, DialogModes.NO);
    }
    this.rotate = function (angle) {
        (r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        d.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
        d.putUnitDouble(s2t("angle"), s2t("angleUnit"), angle);
        d.putEnumerated(s2t("interfaceIconFrameDimmed"), s2t("interpolationType"), s2t("bicubic"));
        executeAction(s2t("transform"), d, DialogModes.NO);
    }
    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
}

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 13, 2023 Jan 13, 2023

Copy link to clipboard

Copied

Sir, this is awesome! 🤩

I tested the script on 10 photos and it worked 100% of the time. It works even with a different background (There's a wooden floor and a gray wall).
I admire your skilfulness! It works much better for me than the standard Photoshop and Lightroom methods.
I am very grateful to you! 🤗

 

I really regret that I don't have the knowledge to do such useful things.

As soon as I will have time, I will test this script on a lot of pictures, then I will be able to describe my impression and tell about errors (if any).

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2023 Jan 13, 2023

Copy link to clipboard

Copied

LATEST

There will certainly be errors. The algorithm will fail if the contrast between the wall or floor is low or if there are large objects on the wall or floor that are very different from the general background, however I hope that my code will allow you to correct most of the photos.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

Well, you can't. Either something gets chopped off or there is distortion. You may be able to manually adjust and have transparent areas on the corners, that would require manual fill. Or you can manually use Perspective/Puppet Warp, those are powerful but can be tricky and slow.

 

Next time hold your camera straight and give plenty of room around the subject for cropping if needed.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

I chose an unfortunate example (the picture with the girl). Don't take it as a rule.
99.9% of photos don't have cropped legs 🙂
In this case, you don't need to pay attention to it.

The transparent parts I can easily remove with action.

The main thing I'm interested in now is just to rotate the picture, without cropping, without deformation and distortion 😞

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 12, 2023 Jan 12, 2023

Copy link to clipboard

Copied

Next time hold your camera straight and give plenty of room around the subject for cropping if needed.

By @Lumigraphics

 

These photos are not taken by me and I have no influence on their quality, unfortunately 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines