97

過去数週間、私はObjective-cで画像を操作していて、多くの奇妙な動作に気づいていました。まず、他の多くの人と同じように、カメラで撮影した画像(または他の人のカメラで撮影してMMSを使用した画像)が90度回転するという問題が発生しています。なぜこれが世界で起こっているのかはわかりませんでしたが(したがって私の質問です)、安価な回避策を思いつくことができました。

今回の私の質問は、なぜこれが起こっているのかということです。Appleが画像を回転させるのはなぜですか?カメラを表向きにして写真を撮るときは、上記のコードを実行しない限り、写真を保存すると回転して保存されます。さて、私の回避策は数日前までは大丈夫でした。

私のアプリケーションは、画像の個々のピクセル、特にPNGのアルファチャネルを変更します(そのため、私のシナリオでは、JPEG変換はウィンドウの外にスローされます)。数日前、回避策コードのおかげで画像がアプリに正しく表示されていても、アルゴリズムが画像の個々のピクセルを変更すると、画像が回転していると見なされることに気付きました。したがって、画像の上部のピクセルを変更する代わりに、画像の側面のピクセルを変更します(回転する必要があると考えられるため)。メモリ内の画像を回転させる方法がわかりません。理想的には、そのimageOrientationフラグをまとめて消去することをお勧めします。

これも私を困惑させている他の何かです...私が写真を撮るとき、imageOrientationは3に設定されています。私の回避策コードはこれを実現してユーザーが気付かないようにそれを裏返すのに十分賢いです。さらに、画像をライブラリに保存するコードはこれを認識し、反転してから保存して、カメラロールに正しく表示されるようにします。

そのコードは次のようになります。

NSData* pngdata = UIImagePNGRepresentation (self.workingImage); //PNG wrap 
UIImage* img = [self rotateImageAppropriately:[UIImage imageWithData:pngdata]];   
UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);

この新しく保存された画像をアプリにロードすると、これimageOrientationは0になります。これはまさに私が見たいものであり、回転の回避策を実行する必要もありません(注:カメラで撮影した画像ではなく、インターネットから画像をロードする場合、imageOrientationは常に0であるため、完全な動作になります)。imageOrientation何らかの理由で、私の保存コードはこのフラグを一掃しているようです。ユーザーが写真を撮ってアプリに追加するとすぐに、そのコードを盗んでそれを使用してimageOrientationを消去したいと思っていましたが、機能していないようです。UIImageWriteToSavedPhotosAlbum何か特別なことをしimageOrientationますか?

imageOrientationこの問題の最善の解決策は、ユーザーが画像の撮影を終えたらすぐに吹き飛ばすことです。Appleはある理由で回転動作を行っていると思いますよね?何人かの人々はこれがアップルの欠陥であると提案しました。

(...まだ迷っていない場合...注2:横向きの写真を撮ると、インターネットから撮った写真と同じように、すべてが完璧に機能しているように見えます)

編集:

いくつかの画像とシナリオが実際にどのように見えるかを次に示します。これまでのコメントからすると、この奇妙な振る舞いは単なるiPhoneの振る舞いではなく、良いと思います。

これは私が携帯電話で撮った写真の写真です(正しい向きに注意してください)。写真を撮ったときの携帯電話とまったく同じように表示されます。

iPhoneで撮影した実際の写真

メールで送信した後のGmailの画像は次のようになります(Gmailで適切に処理されているようです)。

Gmailに表示される写真

Windowsでのサムネイルとしての画像は次のようになります(適切に処理されていないようです)。

Windowsサムネイル

そして、Windowsフォトビューアーで開いたときの実際の画像は次のようになります(まだ適切に処理されていません)。

Windowsフォトビューアーバージョン

この質問に対するすべてのコメントの後、私が考えていることは次のとおりです... iPhoneは画像を取得し、「これを正しく表示するには、90度回転させる必要があります」と言います。この情報はEXIFデータに含まれます。(デフォルトで真っ直ぐに垂直にするのではなく、なぜ90度回転する必要があるのか​​わかりません)。ここから、GmailはそのEXIFデータを読み取って分析し、適切に表示するのに十分スマートです。ただし、WindowsはEXIFデータを読み取るほどスマートではないため、画像が不適切に表示されます。私の仮定は正しいですか?

4

13 に答える 13

54

カメラから画像を取得するときに同じ問題が発生したため、次のコードを追加して修正しました.ここからメソッドscaleAndRotateImageを追加しました

- (void) imagePickerController:(UIImagePickerController *)thePicker didFinishPickingMediaWithInfo:(NSDictionary *)imageInfo {
            // Images from the camera are always in landscape, so rotate
                    UIImage *image = [self scaleAndRotateImage: [imageInfo objectForKey:UIImagePickerControllerOriginalImage]];
    //then save the image to photo gallery or wherever  
        }


- (UIImage *)scaleAndRotateImage:(UIImage *) image {
    int kMaxResolution = 320;

    CGImageRef imgRef = image.CGImage;

    CGFloat width = CGImageGetWidth(imgRef);
    CGFloat height = CGImageGetHeight(imgRef);


    CGAffineTransform transform = CGAffineTransformIdentity;
    CGRect bounds = CGRectMake(0, 0, width, height);
    if (width > kMaxResolution || height > kMaxResolution) {
        CGFloat ratio = width/height;
        if (ratio > 1) {
            bounds.size.width = kMaxResolution;
            bounds.size.height = bounds.size.width / ratio;
        }
        else {
            bounds.size.height = kMaxResolution;
            bounds.size.width = bounds.size.height * ratio;
        }
    }

    CGFloat scaleRatio = bounds.size.width / width;
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
    CGFloat boundHeight;
    UIImageOrientation orient = image.imageOrientation;
    switch(orient) {

        case UIImageOrientationUp: //EXIF = 1
            transform = CGAffineTransformIdentity;
            break;

        case UIImageOrientationUpMirrored: //EXIF = 2
            transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            break;

        case UIImageOrientationDown: //EXIF = 3
            transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationDownMirrored: //EXIF = 4
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
            transform = CGAffineTransformScale(transform, 1.0, -1.0);
            break;

        case UIImageOrientationLeftMirrored: //EXIF = 5
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationLeft: //EXIF = 6
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationRightMirrored: //EXIF = 7
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeScale(-1.0, 1.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        case UIImageOrientationRight: //EXIF = 8
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        default:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];

    }

    UIGraphicsBeginImageContext(bounds.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
        CGContextScaleCTM(context, -scaleRatio, scaleRatio);
        CGContextTranslateCTM(context, -height, 0);
    }
    else {
        CGContextScaleCTM(context, scaleRatio, -scaleRatio);
        CGContextTranslateCTM(context, 0, -height);
    }

    CGContextConcatCTM(context, transform);

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
    UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return imageCopy;
}
于 2012-05-15T13:59:07.617 に答える
50

私はそれについて研究開発を行い、すべての画像ファイルにメタデータ プロパティがあることを発見しました。メタデータが画像の向きを指定している場合、Mac 以外の他の OS では通常無視されます。撮影された画像のほとんどは、メタ データ プロパティが直角に設定されています。そのため、Mac では 90 度回転して表示されます。Windows OS では同じ画像が正しく表示されます。

詳細については、この回答をお読みください http://graphicssoft.about.com/od/digitalphotography/f/sideways-pictures.htm

http://www.exifviewer.org/ 、またはhttp://regex.info/exif.cgi、またはhttp://www.addictivetips.com/internet-tips/view-complete-exifで画像のexifを読んでみてください-any-jpeg-image-onlineのメタデータ情報/

于 2012-05-15T12:58:19.093 に答える
23

今回の私の質問は、なぜこれが起こっているのですか?Apple が画像を回転させるのはなぜですか?

これに対する答えは非常に簡単です。Apple は画像を回転させていません。そこが混乱の元です。

CCDカメラは回転しないので、常に横向きで撮影しています。

Apple は非常に賢明なことを行いました。画像を回転させるために常に時間を費やす代わりに、何メガバイトものデータをシャッフルして、写真がどのように撮影されたかをタグ付けするだけです。

OpenGL は非常に簡単に変換を行うため、データがシャッフルされることはありません。描画方法だけです。

したがって、方向メタデータ。

これは、トリミングやサイズ変更などを行う場合に問題になりますが、何が起こっているかがわかれば、マトリックスを定義するだけですべてがうまくいきます。

于 2016-01-20T01:06:34.993 に答える
17

Dilipの回答の安全性チェックを備えたSwift 4バージョン。

public static func rotateCameraImageToProperOrientation(imageSource : UIImage, maxResolution : CGFloat = 320) -> UIImage? {

    guard let imgRef = imageSource.cgImage else {
        return nil
    }

    let width = CGFloat(imgRef.width)
    let height = CGFloat(imgRef.height)

    var bounds = CGRect(x: 0, y: 0, width: width, height: height)

    var scaleRatio : CGFloat = 1
    if (width > maxResolution || height > maxResolution) {

        scaleRatio = min(maxResolution / bounds.size.width, maxResolution / bounds.size.height)
        bounds.size.height = bounds.size.height * scaleRatio
        bounds.size.width = bounds.size.width * scaleRatio
    }

    var transform = CGAffineTransform.identity
    let orient = imageSource.imageOrientation
    let imageSize = CGSize(width: CGFloat(imgRef.width), height: CGFloat(imgRef.height))

    switch(imageSource.imageOrientation) {
    case .up:
        transform = .identity
    case .upMirrored:
        transform = CGAffineTransform
            .init(translationX: imageSize.width, y: 0)
            .scaledBy(x: -1.0, y: 1.0)
    case .down:
        transform = CGAffineTransform
            .init(translationX: imageSize.width, y: imageSize.height)
            .rotated(by: CGFloat.pi)
    case .downMirrored:
        transform = CGAffineTransform
            .init(translationX: 0, y: imageSize.height)
            .scaledBy(x: 1.0, y: -1.0)
    case .left:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: 0, y: imageSize.width)
            .rotated(by: 3.0 * CGFloat.pi / 2.0)
    case .leftMirrored:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: imageSize.height, y: imageSize.width)
            .scaledBy(x: -1.0, y: 1.0)
            .rotated(by: 3.0 * CGFloat.pi / 2.0)
    case .right :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: imageSize.height, y: 0)
            .rotated(by: CGFloat.pi / 2.0)
    case .rightMirrored:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(scaleX: -1.0, y: 1.0)
            .rotated(by: CGFloat.pi / 2.0)
    }

    UIGraphicsBeginImageContext(bounds.size)
    if let context = UIGraphicsGetCurrentContext() {
        if orient == .right || orient == .left {
            context.scaleBy(x: -scaleRatio, y: scaleRatio)
            context.translateBy(x: -height, y: 0)
        } else {
            context.scaleBy(x: scaleRatio, y: -scaleRatio)
            context.translateBy(x: 0, y: -height)
        }

        context.concatenate(transform)
        context.draw(imgRef, in: CGRect(x: 0, y: 0, width: width, height: height))
    }

    let imageCopy = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return imageCopy
}
于 2016-12-01T13:50:40.633 に答える
13

iPhone / iPadで生成された画像はすべて、実際の向きを指定するEXIF Orientationタグ(Exif.Image.Orientation)が付いたLandscapeLeftとして保存されます。

次の値があります。1:横向き左6:縦向き通常3:横向き右4:縦向き逆さま

IOSでは、EXIF情報が正しく読み取られ、画像は撮影されたのと同じ方法で表示されます。ただし、Windowsでは、EXIF情報は使用されません。

これらの画像の1つをGIMPで開くと、画像に回転情報があると表示されます。

于 2012-12-13T13:43:22.803 に答える
9

Xamarin を使用している他のユーザーのために、ここに Dilip の素晴らしい回答の C# 翻訳を示します。Swift の翻訳については thattyson に感謝します。

public static UIImage RotateCameraImageToProperOrientation(UIImage imageSource, nfloat maxResolution) {

    var imgRef = imageSource.CGImage;

    var width = (nfloat)imgRef.Width;
    var height = (nfloat)imgRef.Height;

    var bounds = new CGRect(0, 0, width, height);

    nfloat scaleRatio = 1;

    if (width > maxResolution || height > maxResolution) 
    {
        scaleRatio = (nfloat)Math.Min(maxResolution / bounds.Width, maxResolution / bounds.Height);
        bounds.Height = bounds.Height * scaleRatio;
        bounds.Width = bounds.Width * scaleRatio;
    }

    var transform = CGAffineTransform.MakeIdentity();
    var orient = imageSource.Orientation;
    var imageSize = new CGSize(imgRef.Width, imgRef.Height);
    nfloat storedHeight;

    switch(imageSource.Orientation) {
        case UIImageOrientation.Up:
            transform = CGAffineTransform.MakeIdentity();
            break;

        case UIImageOrientation.UpMirrored :
            transform = CGAffineTransform.MakeTranslation(imageSize.Width, 0.0f);
            transform = CGAffineTransform.Scale(transform, -1.0f, 1.0f);
            break;

        case UIImageOrientation.Down :
            transform = CGAffineTransform.MakeTranslation(imageSize.Width, imageSize.Height);
            transform = CGAffineTransform.Rotate(transform, (nfloat)Math.PI);
            break;

        case UIImageOrientation.DownMirrored :
            transform = CGAffineTransform.MakeTranslation(0.0f, imageSize.Height);
            transform = CGAffineTransform.Scale(transform, 1.0f, -1.0f);
            break;

        case UIImageOrientation.Left:
            storedHeight = bounds.Height;
            bounds.Height = bounds.Width;
            bounds.Width = storedHeight;
            transform = CGAffineTransform.MakeTranslation(0.0f, imageSize.Width);
            transform = CGAffineTransform.Rotate(transform, 3.0f * (nfloat)Math.PI / 2.0f);
            break;

        case UIImageOrientation.LeftMirrored :
            storedHeight = bounds.Height;
            bounds.Height = bounds.Width;
            bounds.Width = storedHeight;
            transform = CGAffineTransform.MakeTranslation(imageSize.Height, imageSize.Width);
            transform = CGAffineTransform.Scale(transform, -1.0f, 1.0f);
            transform = CGAffineTransform.Rotate(transform, 3.0f * (nfloat)Math.PI / 2.0f);
            break;

        case UIImageOrientation.Right :
            storedHeight = bounds.Height;
            bounds.Height = bounds.Width;
            bounds.Width = storedHeight;
            transform = CGAffineTransform.MakeTranslation(imageSize.Height, 0.0f);
            transform = CGAffineTransform.Rotate(transform, (nfloat)Math.PI / 2.0f);
            break;

        case UIImageOrientation.RightMirrored :
            storedHeight = bounds.Height;
            bounds.Height = bounds.Width;
            bounds.Width = storedHeight;
            transform = CGAffineTransform.MakeScale(-1.0f, 1.0f);
            transform = CGAffineTransform.Rotate(transform, (nfloat)Math.PI / 2.0f);
            break;

        default :
            break;
    }

    UIGraphics.BeginImageContext(bounds.Size);
    var context = UIGraphics.GetCurrentContext();

    if (orient == UIImageOrientation.Right || orient == UIImageOrientation.Left) {
        context.ScaleCTM(-scaleRatio, scaleRatio);
        context.TranslateCTM(-height, 0);
    } else {
        context.ScaleCTM(scaleRatio, -scaleRatio);
        context.TranslateCTM(0, -height);
    }

    context.ConcatCTM(transform);
    context.DrawImage(new CGRect(0, 0, width, height), imgRef);

    var imageCopy = UIGraphics.GetImageFromCurrentImageContext();
    UIGraphics.EndImageContext();

    return imageCopy;
}
于 2016-02-11T00:38:51.993 に答える
4

同様の問題を抱えていたが、Swiftを使用していたため、この質問に出くわしました。他のSwift開発者にとって私のために働いた答えにリンクしたかっただけです: https://stackoverflow.com/a/26676578/3904581

問題を効率的に修正する Swift スニペットを次に示します。

let orientedImage = UIImage(CGImage: initialImage.CGImage, scale: 1, orientation: initialImage.imageOrientation)!

超シンプル。1 行のコード。問題が解決しました。

于 2015-01-04T07:46:09.910 に答える
0

Swift 3 用にすばやくリファクタリングしました (誰かがテストして、すべてが正常に動作することを確認できますか?):

static func rotateCameraImageToProperOrientation(imageSource : UIImage, maxResolution : CGFloat) -> UIImage {
    let imgRef = imageSource.cgImage

    let width = CGFloat(imgRef!.width)
    let height = CGFloat(imgRef!.height)

    var bounds = CGRect(x: 0, y: 0, width: width, height: height)

    var scaleRatio : CGFloat = 1
    if width > maxResolution || height > maxResolution {

        scaleRatio = min(maxResolution / bounds.size.width, maxResolution / bounds.size.height)
        bounds.size.height = bounds.size.height * scaleRatio
        bounds.size.width = bounds.size.width * scaleRatio
    }

    var transform = CGAffineTransform.identity
    let orient = imageSource.imageOrientation
    let imageSize = CGSize(width: imgRef!.width, height: imgRef!.height)

    switch imageSource.imageOrientation {
    case .up :
        transform = CGAffineTransform.identity

    case .upMirrored :
        transform = CGAffineTransform(translationX: imageSize.width, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)

    case .down :
        transform = CGAffineTransform(translationX: imageSize.width, y: imageSize.height)
        transform = transform.rotated(by: CGFloat.pi)

    case .downMirrored :
        transform = CGAffineTransform(translationX: 0, y: imageSize.height)
        transform = transform.scaledBy(x: 1, y: -1)

    case .left :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = storedHeight
        transform = CGAffineTransform(translationX: 0, y: imageSize.width)
        transform = transform.rotated(by: 3.0 * CGFloat.pi / 2.0)

    case .leftMirrored :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = storedHeight
        transform = CGAffineTransform(translationX: imageSize.height, y: imageSize.width)
        transform = transform.scaledBy(x: -1, y: 1)
        transform = transform.rotated(by: 3.0 * CGFloat.pi / 2.0)

    case .right :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = storedHeight
        transform = CGAffineTransform(translationX: imageSize.height, y: 0)
        transform = transform.rotated(by: CGFloat.pi / 2.0)

    case .rightMirrored :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = storedHeight
        transform = CGAffineTransform(scaleX: -1, y: 1)
        transform = transform.rotated(by: CGFloat.pi / 2.0)

    }

    UIGraphicsBeginImageContext(bounds.size)
    let context = UIGraphicsGetCurrentContext()

    if orient == .right || orient == .left {

        context!.scaleBy(x: -scaleRatio, y: scaleRatio)
        context!.translateBy(x: -height, y: 0)
    } else {
        context!.scaleBy(x: scaleRatio, y: -scaleRatio)
        context!.translateBy(x: 0, y: -height)
    }

    context!.concatenate(transform)
    context!.draw(imgRef!, in: CGRect(x: 0, y: 0, width: width, height: height))

    let imageCopy = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return imageCopy!
}
于 2016-09-01T09:54:43.777 に答える
0

私はあなたの問題が何であるかを正確に知っています。あなたはUIImagePickerを使用していますが、これはあらゆる意味で奇妙です。カメラには AVFoundation を使用することをお勧めします。これにより、向きと品質が柔軟になります。AVCaptureSession を使用します。ここでコードを取得できますAVFoundation を使用して撮影した写真をフォト アルバムに保存する方法は?

于 2013-10-08T10:29:21.463 に答える