Camera4Kivy竖屏拍照时图片逆时针旋转90°问题求助
问题解决:Camera4Kivy竖屏拍摄图片旋转90度的问题
环境依赖
- kivy: 2.2.1
- kivymd: 1.2.0dev0
- camera4kivy: 0.3.0
- python3
问题描述
应用强制竖屏(buildozer.spec中orientation=portrait),使用Camera4Kivy拍摄时,竖屏状态下捕获的图片会逆时针旋转90°,横屏拍摄则显示正常。尝试用pyjnius区分横竖屏状态但未成功。

解决方案
核心思路是读取图片的Exif方向信息,根据信息修正图片旋转角度后再保存。修改PictureMDScreen类中的capture_path方法,在移动文件后添加图片旋转处理逻辑:
修改后的关键代码片段
def capture_path(self,file_path): ## called when clicking on the camera button if platform == "android": source = file_path destination = join(app_storage_path(), "app", self.picture_name + ".png") self.source = destination try: os.rename(source,destination) # move a file from source to destination # 新增:处理图片旋转 im = Image.open(destination) exif_data = im._getexif() if exif_data: orientation = exif_data.get(274) # 274对应Exif中的Orientation字段 # 根据Exif方向修正图片 if orientation == 6: # 图片需要顺时针旋转90度(修正逆时针旋转90度的问题) im = im.rotate(-90, expand=True) elif orientation == 8: # 图片需要逆时针旋转90度 im = im.rotate(90, expand=True) elif orientation == 3: # 图片需要旋转180度 im = im.rotate(180, expand=True) # 保存修正后的图片 im.save(destination) except Exception as e: log("error : " + str(e)) time.sleep(1) elif platform in ["win","macos"]: self.source = self.picture_name + ".jpg" try: self.tile.source = self.source self.tile.ids.image.reload() # reload image from cache except Exception as e: log("error" + str(e))
补充说明
- Exif的
Orientation字段(标记为274)定义了图片的拍摄方向,不同数值对应不同旋转需求:- 1:正常方向
- 6:顺时针旋转90度(对应你遇到的逆时针旋转90度的情况,需要反向旋转修正)
- 8:逆时针旋转90度
- 3:旋转180度
expand=True参数确保旋转后的图片不会被裁剪,保持完整尺寸。- 原代码中的
checkExifOrientation方法存在错误(直接用im["274"]访问Exif信息),修正后应先获取exif_data字典再取值。
完整代码
from os import listdir import time import os from kivymd.uix.imagelist import MDSmartTile from kivymd.app import MDApp from kivy.lang import Builder from kivymd.uix.screenmanager import MDScreenManager from kivy.core.window import Window from kivy.properties import StringProperty, ObjectProperty from kivy.clock import Clock from kivy.utils import platform from kivy.logger import Logger from android_permissions import AndroidPermissions from camera4kivy import Preview from kivymd.uix.screen import MDScreen from kivy.cache import Cache from toast import Toast from os.path import join from PIL import Image if platform == "android": from jnius import autoclass from android.storage import app_storage_path, primary_external_storage_path from android import mActivity, api_version Environment = autoclass('android.os.Environment') def log(msg): Logger.info(msg) KV = ''' <MyMDSmartTile>: radius: 24 box_radius: 0, 0, 24, 24 box_color: 0, 0, 0, .5 size_hint: 1, None height: "160dp" # à faire varier mipmap: False # if True OpenGL mipmapping to be applied to the texture MDIconButton: icon: "camera" on_release: app.root.ids._picture_screen.tile = root app.root.ids._picture_screen.picture_name = root.text app.root.current = "picture_screen" MDLabel: text: root.text bold: True font_style: "Caption" opposite_colors: True MyMDScreenManager: MDScreen: name: "home_screen" MDScrollView: MDGridLayout: id: _mybox cols: 2 adaptive_height: True padding:10 spacing:10 PictureMDScreen: id: _picture_screen name: "picture_screen" root: root photo_preview: _preview MDFloatLayout: Preview: id: _preview MDIconButton: icon: "camera" pos_hint: {"center_x": .5, "center_y": .5} on_release: _preview.capture_photo(location = "private", name = _picture_screen.picture_name) # or capture_screenshot() MDIconButton: icon: "close" pos_hint: {"center_x": .8, "center_y": .5} on_release: _picture_screen.close() ''' class PictureMDScreen(MDScreen): tile = ObjectProperty() root = ObjectProperty() picture_name = StringProperty() photo_preview = ObjectProperty(None) # store the widget instance Preview source = StringProperty() def on_enter(self): self.photo_preview.connect_camera(filepath_callback= self.capture_path) # self.photo_preview.preview.orientation = 90 # trying to have portrait orientation def on_pre_leave(self): print("preleaving") self.photo_preview.disconnect_camera() def capture_path(self,file_path): ## called when clicking on the camera button if platform == "android": source = file_path destination = join(app_storage_path(), "app", self.picture_name + ".png") self.source = destination try: os.rename(source,destination) # move a file from source to destination # 处理图片旋转 im = Image.open(destination) exif_data = im._getexif() if exif_data: orientation = exif_data.get(274) if orientation == 6: im = im.rotate(-90, expand=True) elif orientation == 8: im = im.rotate(90, expand=True) elif orientation == 3: im = im.rotate(180, expand=True) im.save(destination) except Exception as e: log("error : " + str(e)) time.sleep(1) elif platform in ["win","macos"]: self.source = self.picture_name + ".jpg" try: self.tile.source = self.source self.tile.ids.image.reload() # reload image from cache except Exception as e: log("error" + str(e)) def close(self): if platform == "android": self.photo_preview.disconnect_camera() self.root.current = 'home_screen' def checkExifOrientation(self,im): ''' Exif is information inscrusted in an image, we can check orientation thanks to im._getexif()["274"] just for information : PIL.ExifTags.TAGS[274] gives "Orientation" ''' exif_data = im._getexif() if exif_data and exif_data.get("274"): # check in exif dictionary if 274 in log("has exif in it") if exif_data["274"] == 6: log("image is portrait") return "portrait" elif exif_data["274"] == 1: log("image is landscape") return "landscape" else: log("we don't know, image is portrait") return "portrait" else: log("there is no exif 274 so image is portrait") return "portrait" def app_name(self): context = mActivity.getApplicationContext() appinfo = context.getApplicationInfo() if appinfo.labelRes: name = context.getString(appinfo.labelRes) else: name = appinfo.nonLocalizedLabel.toString() return name class MyMDSmartTile(MDSmartTile): text = StringProperty() class MyMDScreenManager(MDScreenManager): def __init__(self, **kwargs): super().__init__(**kwargs) def on_kv_post(self, base_widget): #Create the tiles for i in range(10): self.ids._mybox.add_widget(MyMDSmartTile(text = str(i))) class CameraCuisto(MDApp): def build(self): return Builder.load_string(KV) def on_start(self): self.dont_gc = AndroidPermissions(self.start_app) def start_app(self): self.dont_gc = None def on_start(self, **kwargs): if platform == "android": from android.permissions import request_permissions, Permission request_permissions([Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE, Permission.CAMERA]) log('Perm requested') if __name__ == "__main__": CameraCuisto().run()
内容的提问来源于stack exchange,提问作者xavier cattelain
相关产品推荐
相关产品推荐

