如何确定和自动旋转图像?

Posted

技术标签:

【中文标题】如何确定和自动旋转图像?【英文标题】:How to Determine and Auto-Rotate Images? 【发布时间】:2014-03-23 23:57:37 【问题描述】:

我有一堆图像,其中一些图像必须旋转。

示例:

我想将此图像逆时针旋转 90°。

我用谷歌搜索知道如何旋转图像并找到许多链接和 SO 线程。但是我如何确定图像是否需要旋转? Picasa 具有自动旋转功能。我想拥有类似的功能。

任何指针都会对我很有帮助。

我找到了link,但它与 android 相关。

【问题讨论】:

非常棘手。如果她真的躺下怎么办? @RogerRowland 好一个:D。也许如果可以确定是否应该是纵向但放置为横向的图像,那么只有我们可以旋转它!我不确定我的想法是否正确。 也许某些图像格式在元数据中有线索?我似乎想起了 EXIF 中的一些东西(大声思考)...... @RogerRowland 我找到了这个链接***.com/questions/12726860/…它处理EXIF,但它是关于Android的。 没问题,你已经发布了答案,请接受 - 我不担心代表,只要你被排序:-) 【参考方案1】:

Roger Rowland 提供的metadata-extractor 的指针解决了这个问题。我将其发布在这里以供将来参考:

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.jpeg.JpegDirectory;

public class Main 

    private static String inFilePath = "C:\\Users\\TapasB\\Desktop\\MHIS031522.jpg";
    private static String outFilePath = "C:\\Users\\TapasB\\Desktop\\MHIS031522-rotated.jpg";

    public static void main(String[] args) throws Exception 
        File imageFile = new File(inFilePath);
        BufferedImage originalImage = ImageIO.read(imageFile);

        Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
        ExifIFD0Directory exifIFD0Directory = metadata.getDirectory(ExifIFD0Directory.class);
        JpegDirectory jpegDirectory = (JpegDirectory) metadata.getDirectory(JpegDirectory.class);

        int orientation = 1;
        try 
            orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
         catch (Exception ex) 
            ex.printStackTrace();
        

        int width = jpegDirectory.getImageWidth();
        int height = jpegDirectory.getImageHeight();

        AffineTransform affineTransform = new AffineTransform();

        switch (orientation) 
        case 1:
            break;
        case 2: // Flip X
            affineTransform.scale(-1.0, 1.0);
            affineTransform.translate(-width, 0);
            break;
        case 3: // PI rotation
            affineTransform.translate(width, height);
            affineTransform.rotate(Math.PI);
            break;
        case 4: // Flip Y
            affineTransform.scale(1.0, -1.0);
            affineTransform.translate(0, -height);
            break;
        case 5: // - PI/2 and Flip X
            affineTransform.rotate(-Math.PI / 2);
            affineTransform.scale(-1.0, 1.0);
            break;
        case 6: // -PI/2 and -width
            affineTransform.translate(height, 0);
            affineTransform.rotate(Math.PI / 2);
            break;
        case 7: // PI/2 and Flip
            affineTransform.scale(-1.0, 1.0);
            affineTransform.translate(-height, 0);
            affineTransform.translate(0, width);
            affineTransform.rotate(3 * Math.PI / 2);
            break;
        case 8: // PI / 2
            affineTransform.translate(0, width);
            affineTransform.rotate(3 * Math.PI / 2);
            break;
        default:
            break;
               

        AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);  
        BufferedImage destinationImage = new BufferedImage(originalImage.getHeight(), originalImage.getWidth(), originalImage.getType());
        destinationImage = affineTransformOp.filter(originalImage, destinationImage);
        ImageIO.write(destinationImage, "jpg", new File(outFilePath));
    

【讨论】:

我认为 API 在元数据提取器的当前版本(撰写本文时为 2.8.1)中发生了变化:metadata.getDirectory 现在是 metadata.getFirstDirectoryOfType【参考方案2】:

我在让某些开关盒正常工作时遇到了一些问题。即使没有要进行旋转,AffineTransform 也会在图像中创建一个带有黑色空间的新图像,并会切掉一些尺寸。在这里接受了答案,我使用元数据提取器类来确定方向应该是什么。然后我使用Imgscalr 库进行缩放和旋转。

对我有用的完整解决方案如下所示。感谢 Tapas Bose 提供原始解决方案。我希望这对任何人都有帮助!

BufferedImage originalImage = Utils.prepareBufferedImage(fileUpload.getFile_data(), fileUpload.getFile_type());
                    BufferedImage scaledImg = Scalr.resize(originalImage, 200);

                    // ---- Begin orientation handling ----
                    Metadata metadata = ImageMetadataReader.readMetadata(fileUpload.getFile_data());
                    ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);

                    int orientation = Integer.parseInt(id);
                    try 
                        orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
                     catch (Exception ex) 
                        logger.debug("No EXIF information found for image: " + fileUpload.getFile_name());
                    

                    switch (orientation) 
                    case 1:
                        break;
                    case 2: // Flip X
                        scaledImg = Scalr.rotate(scaledImg, Rotation.FLIP_HORZ);
                        break;
                    case 3: // PI rotation
                        scaledImg = Scalr.rotate(scaledImg, Rotation.CW_180);
                        break;
                    case 4: // Flip Y
                        scaledImg = Scalr.rotate(scaledImg, Rotation.FLIP_VERT);
                        break;
                    case 5: // - PI/2 and Flip X
                        scaledImg = Scalr.rotate(scaledImg, Rotation.CW_90);
                        scaledImg = Scalr.rotate(scaledImg, Rotation.FLIP_HORZ);
                        break;
                    case 6: // -PI/2 and -width
                        scaledImg = Scalr.rotate(scaledImg, Rotation.CW_90);
                        break;
                    case 7: // PI/2 and Flip
                        scaledImg = Scalr.rotate(scaledImg, Rotation.CW_90);
                        scaledImg = Scalr.rotate(scaledImg, Rotation.FLIP_VERT);
                        break;
                    case 8: // PI / 2
                        scaledImg = Scalr.rotate(scaledImg, Rotation.CW_270);
                        break;
                    default:
                        break;
                           
                    // ---- End orientation handling ----

                    if(fileUpload.getFile_type().toLowerCase().contains("jpeg"))
                        ImageIO.write(scaledImg, "jpeg", fileUpload.getFile_data());
                        user.setProfile_picture_ext("jpg");
                    
                    else
                        Sanselan.writeImage(scaledImg, fileUpload.getFile_data(), ImageFormat.IMAGE_FORMAT_PNG, null);
                        user.setProfile_picture_ext("png");
                    

【讨论】:

【参考方案3】:

有时您没有使用相机拍摄的图像,也没有 EXIF 数据可供使用。就我而言,我有一个项目要扫描并导入我们的数字存储库中的数万张老式明信片,其中大部分正面是横向的,只有一小部分是纵向的(即使在这些上,背面仍然是横向的)。为了最大限度地减少扫描、纠偏和裁剪所花费的时间,它们每次扫描和横向(总是)完成 3 次。

我来这个问题是为了寻找自动检测和旋转这些纵向卡片扫描的答案。

有很多关于基于相机元数据执行此操作的讨论。有一些示例说明如何使用机器学习来自动将未将相机固定在地面/水平线上的照片调平。我没有找到任何对我的情况有帮助的东西(这并不是说没有,但如果有的话,由于其他情况很难找到)......

编辑 2019 年 3 月 22 日:这是https://d4nst.github.io/2017/01/12/image-orientation/

...,所以我确实想出了一个我要尝试的答案:

对于每张卡片正面(使用 ImageMagick 和简单脚本进行批处理):

    制作较小的 jpeg 版本的图片(因为我们不想使用 200MB 的图片) 再制作三个较小的 jpeg 副本,每个副本都应用额外的 90 度旋转 将这四个方向中的每一个都提交给云机器学习 API(我过去曾遇到过 Microsoft's 的好运) 分析响应。选择最详细和/或最有信心的方向作为正确方向 将原始全尺寸扫描旋转适当的量并删除四个较小的 jpeg。

我已经通过一次扫描对此进行了测试,在我 n=1 的情况下,API 具有更长的标签列表和更好(和更长)的正确方向建议标题。

潜在问题:

    云提供商可能会停止使用 API 或开始收取超出我们承受能力的费用(当我使用一批此类卡编写元数据创建测试脚本时,使用水平保持在免费类别中)。 我认为如果 Microsoft 开始将更通用的元数据 AI 应用到所有方向,那么这可能会停止工作(尽管,一个希望他们会在响应中添加一个键以进行最佳方向猜测)。 (在我的情况下)明信片上的字迹通常与图像不相符(摄影工作室名称等)。如果他们没有像我怀疑的那样执行上述操作,那么在一次轮换中更好的 OCR 可能会欺骗脚本。如果这被证明是一个问题,人们可能不得不忽略 OCR 结果。 使用带宽。

【讨论】:

伟大的想法。您也可以尝试使用 Amazon Rekognition 来分析图像。 @TapasBose 谢谢!我还没试过那个。我已经测试过 Microsoft、Google 和 IBM 的产品。微软似乎是目前最好的。 @TapasBose 我在 Powershell 中写了这个:gist.github.com/pr3sidentspence/… 大部分都是正确的。 这是出于自私的原因,手动检查每张明信片很麻烦。 :) @TapasBose 现在我正在安装 tensorflow...谢谢 :)

以上是关于如何确定和自动旋转图像?的主要内容,如果未能解决你的问题,请参考以下文章

当PHP中的宽度大于高度时自动旋转图像

如何裁剪带有旋转矩形的图像?

在 C# 中旋转图像时如何防止剪切?

如何在 iOS 上使用 Xamarin 旋转文件中的图像

opencv图像旋转,该怎么解决

如何检测将显微镜图像旋转对齐到模板的良好特征