MicrocosmWorksDijital Kozmosu Yenilikçi ve Mimari Olarak Tasarlamak
Hakkındaİletişim
MicrocosmWorksDijital Kozmosu Yenilikçi ve Mimari Olarak İnşa Etmek

Önemli BT çözümleri sunuyoruz. Teknoloji, güvenlik ve işletmelerin güvenilir, yenilikçi BT altyapısı ile büyümesine yardımcı olmaktan tutkuluyuz.

[email protected]
+91 7011868196
New Delhi, India

AI Büyüme Merkezi

AI MerkeziStartup İnovasyonuKurumsal Hızlandırıcı

Çözümler

Tüm ÇözümlerSağlık ve Fitness UygulamalarıAI Video PlatformuAI Ajan Geliştirme

Kaynaklar

ÖngörülerSektör RehberleriKullanım Durumu ŞablonlarıMimari KalıplarVaka Çalışmaları

Şirket

HakkımızdaİletişimÇalışmalarımız

Hizmetler

Dijital DanışmanlıkBulut AltyapısıSaaS GeliştirmeYapay Zeka GeliştirmeVideo Teknolojisi
ERP GeliştirmeZoho ÖzelleştirmeOdoo GeliştirmeSalesforce EntegrasyonuÖzel CRM Geliştirme
QuickBooks EntegrasyonuIoT ÇözümleriBlokzincir Geliştirme
Siber Güvenlik DanışmanlığıIT Desteği - L3

© 2026 MicrocosmWorks. Tüm hakları saklıdır.

Gizlilik PolitikasıHizmet Şartları
Öngörülere Geri Dön
IoT Development

Flutter'da 360 Video Yeniden Çerçeveleme: Küresel Projeksiyon, Quaternion Anahtar Kareler ve GPU Dışa Aktarma

Flutter'da eş dikdörtgen 360 görüntüleri küresel projeksiyon, quaternion anahtar kareler ve GPU hızlandırmalı dışa aktarma kullanarak yeniden çerçeveleme.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 10, 2026
•
Güncellendi August 12, 2026
•
5 min read
Developer using Flutter to edit and reframe a 360° video with spherical projection.webp
5 min read

Flutter'da 360° Video Yeniden Çerçeveleme: Küresel Projeksiyon, Quaternion Anahtar Kareler ve GPU Dışa Aktarma

Ham eş dikdörtgen 360° görüntüleri, tamamen bir Flutter uygulaması içinde nasıl pürüzsüz, çerçeveli, düz videoya dönüştürdüğümüzü.

Kimsenin sizi uyarmadığı sorun

Normal bir videonun size göstereceği bariz bir şey vardır: kameranın baktığı kare. 360° videonun böyle bir şeyi yoktur. Kamera her şeyi kaydeder — X ekseninin boylam, Y ekseninin enlem olduğu 2:1 oranında düz bir eş dikdörtgen olarak depolanan tam bir piksel küresi. Bu görüntüleri Instagram, YouTube veya bir telefon ekranına koymadan önce, kameranın kasten cevaplamadığı bir soruyu birinin cevaplaması gerekir:

İzleyici nereye ve ne zaman bakmalı?

"Yeniden çerçeveleme" (Reframing), o kürenin içinde zamanla sanal bir kamera uçurma — kaydırma, eğme, yakınlaştırma — ve normal düz bir 16:9 (veya 9:16, veya 2.35:1) video oluşturma eylemidir. Bu özellik GoPro Player, Insta360 Studio ve Adobe Premiere'in GoPro VR Reframe eklentisinin temelini oluşturur.

Bunu tamamen Flutter'da inşa ettik. Bu yazı, gerçekten zor olan kısımları ele alıyor: projeksiyon matematiği, gimbal kilitlenmesi olmayan enterpolasyon, ±180° birleşme yeri ve gerçek Android kod çözücülerinde ayakta kalması gereken bir GPU dışa aktarma hattı.

Uçtan uca akış:

360 video yakalama/içe aktarma
  → hafif bir önizleme proxy'sini dönüştürme (transcode)
  → kullanıcı bir konuya dokunur / çizer / otomatik algılar
  → nesne takibi bir yol üretir
  → anahtar kareler oluşturma + düzleştirme
  → kare başına kamera yönünü SLERP-enterpolasyon (canlı önizleme)
  → düz bir videoya yerel GPU dışa aktarma
  → FFmpeg son işlem (moov atomu + A/V senkronizasyon düzeltmesi)

Mimarinin genel bakışı

Sistemi üç temiz katmanda tuttuk, böylece matematik Flutter'dan bağımsız olarak test edilebilir:

KatmanYerSorumluluk
Modellerlib/data/models/reframe/SphericalCoordinates/Quaternion, CameraOrientation/MotionConstraints, ReframeKeyframe, ReframeProject
Servislerlib/services/reframe/SphericalProjection (ray casting), CoordinateConverter, KeyframeInterpolator/Generator/Smoother, ReframeExportService
Sunumlib/presentation/.../reframe/Riverpod ReframeNotifier, yerel OpenGL Spherical360Player, bbox-çizim yerleşimi, anahtar kare zaman çizelgesi

Altın kural: Tüm trigonometri, Flutter importları olmadan saf Dart'tır. Bu, her projeksiyon ve enterpolasyon fonksiyonunun bir widget ağacı olmadan birim testine tabi tutulabileceği ve aynı matematiğin hem canlı önizlemeyi hem de dışa aktarmayı yönlendirdiği anlamına gelir.

1. İşin kalbi: eş dikdörtgen ⟷ küresel

Her şey tek bir eşlemeyle başlar. Eş dikdörtgen bir çerçeve, sadece şunların olduğu bir dikdörtgendir:

  • X (0 → genişlik) boylamı (yaw) −180°'den +180°'ye kadar tarar
  • Y (0 → yükseklik) enlemi (pitch) +90° (üst/zenit) ila −90° (alt/nadir) arasında tarar

SphericalCoordinates bilerek küçüktür — sadece yaw ve pitch içerir. Roll, FOV ve zoom yöne değil, kameraya aittir. İşte tüm özelliğin dayandığı dönüşüm:

factory SphericalCoordinates.fromEquirectangular({
  required double x, required double y,
  required double videoWidth, required double videoHeight,
}) {
  final normalizedX = x / videoWidth;
  final normalizedY = y / videoHeight;
  // X maps to yaw: 0->-180°, 0.5->0°, 1.0->180°
  final yaw = (normalizedX - 0.5) * 360.0;
  // Y maps to pitch: 0->90° (top), 0.5->0° (horizon), 1.0->-90° (bottom)
  final pitch = (0.5 - normalizedY) * 180.0;
  return SphericalCoordinates(
    yaw: yaw.clamp(-180.0, 180.0),
    pitch: pitch.clamp(-90.0, 90.0),
  );
}

Tersi ayna görüntüsüdür (normalizedX = yaw/360 + 0.5) ve bunu sürekli olarak, izlenen bir sınırlayıcı kutuyu oynatıcıya geri çizmek için kullanırız.

"Gördüğünüze dokunun" — perspektif ışın izleme (ray casting)

Kullanıcı, eş dikdörtgen dikdörtgen ile etkileşime girmez. Onlar, oluşturulmuş bir perspektif görünümle etkileşime girer. Bu nedenle, oynatıcıda bir noktaya dokunduklarında, sanal bir iğne deliği kameradan bir ışın fırlatarak kürede hangi yöne isabet ettiklerini bulmamız gerekir. Bu, SphericalProjection.screenToSpherical'dır:

final ndcX = (2.0 * screenPoint.dx / playerSize.width) - 1.0;
final ndcY = 1.0 - (2.0 * screenPoint.dy / playerSize.height);
final aspectRatio = playerSize.width / playerSize.height;
final tanHalfFov = math.tan(cameraFov * math.pi / 360.0); // half-angle
double rayX = ndcX * aspectRatio * tanHalfFov;
double rayY = ndcY * tanHalfFov;
double rayZ = 1.0;
// ...normalize, rotate the ray by pitch (around X) then yaw (around Y)...
final sphericalYaw   = math.atan2(finalRayX, finalRayZ) * 180.0 / math.pi;
final sphericalPitch = math.asin(finalRayY.clamp(-1.0, 1.0)) * 180.0 / math.pi;

The .clamp(-1.0, 1.0) before asin looks paranoid, but floating-point drift will hand you 1.0000001 and asin will return NaN — and a single NaN poisons every downstream frame. Defensive clamping around inverse trig is a recurring theme in this codebase.

2. Akıcı kamera hareketi: Euler açıları değil, kuaterniyonlar

Basit yaklaşım, her anahtar karede yaw/pitch değerlerini depolamak ve sayıları doğrusal olarak enterpolasyon yapmaktır. Bu korkunç görünür. Euler açılarını enterpolasyon yapmak, gimbal kilitlenmesi, düzensiz açısal hız ve kutuplara yakın çirkin atlamalar (snaps) sağlar.

Yönü SLERP (küresel doğrusal enterpolasyon) kullanarak bir kuaterniyon olarak enterpolasyon yaparız. Her yön, bir ZYX Euler konvansiyonu aracılığıyla bir kuaterniyona dönüşür:

Quaternion toQuaternion() {
  final yawRad = yaw * math.pi / 180.0;
  final pitchRad = pitch * math.pi / 180.0;
  final cy = math.cos(yawRad * 0.5); final sy = math.sin(yawRad * 0.5);
  final cp = math.cos(pitchRad * 0.5); final sp = math.sin(pitchRad * 0.5);
  return Quaternion(w: cy * cp
, x: cy * sp
, y: sy * cp
, z: -sy * sp);
}

Ve SLERP'in kendisi, her uygulamanın ihtiyaç duyduğu iki üretim sınıfı güvenlik valfine sahiptir:

static Quaternion slerp(Quaternion a, Quaternion b, double t) {
  a = a.normalize(); b = b.normalize();
  var dotProduct = a.dot(b);
  if (dotProduct < 0.0) { b = -b; dotProduct = -dotProduct; }   // take the shortest hemisphere
  if (dotProduct > 0.9995) {                                     // near-parallel -> fall back to LERP
    return Quaternion(w: a.w + t*(b.w-a.w)
, x: a.x + t*(b.x-a.x)
, y: a.y + t*(b.y-a.y)
, z: a.z + t*(b.z-a.z)).normalize();
  }
  final theta0 = math.acos(dotProduct);
  final theta = theta0 * t;
  final sinTheta = math.sin(theta);
  final sinTheta0 = math.sin(theta0);
  final s0 = math.cos(theta) - dotProduct * sinTheta / sinTheta0;
  final s1 = sinTheta / sinTheta0;
  return Quaternion(w: s0*a.w + s1*b
.w, /* ...*/).normalize();
}

Göz ardı edilemez iki ayrıntı:

  1. En kısa yarım küre — q ve −q aynı yönü temsil eder. Nokta çarpımı negatifse, bir girişi olumsuzlarsınız veya kameranız kürenin etrafından uzun yoldan gider.
  2. Neredeyse paralel LERP geri dönüşü — iki anahtar kare neredeyse aynı olduğunda, sinTheta0 → 0 olur ve neredeyse sıfıra bölersiniz. Dot > 0.9995'in üzerindeki düz LERP'ye geri dönmek patlamayı önler.

İnce ama önemli bir tasarım kararı: yönü SLERP ile enterpolasyon yaparız, ancak FOV/zoom'u LERP ile ve roll'u açısal LERP ile. Küresel enterpolasyon yön için doğru araçtır; bir zoom sadece bir skalerdir ve doğrusal olarak hareket etmelidir:

final qInterp = Quaternion.slerp(from.direction.toQuaternion(), to.direction.toQuaternion(), t);
final interpDirection = SphericalCoordinates.fromQuaternion(qInterp).normalize();
final interpFov  = _lerp(from.fov, to
.fov, t);
final interpRoll = _lerpAngle(from.roll, to
.roll, t); // wraps across ±180
final interpZoom = _lerp(from.zoom, to
.zoom, t);

💡 Bir kuaterniyondan yaw/pitch'e geri dönerken, "sayısal kararlılık için (asin etki alanı sorunlarını önler)" asin yerine atan2 kullanırız. Bir SLERP'den sonra, kuaterniyon çok hafifçe denormalize olabilir ve asin'in NaN attığı durumlarda atan2 zarif bir şekilde bozulur.

Kolaylaştırma (Easing): manuel olarak yazılmış, gelen geçişte

Her anahtar kare, kendisine geçiş için kolaylaştırma eğrisine sahiptir. Eğriler, her platformda aynı olacak şekilde manuel olarak yazılmıştır:

case KeyframeEasing.linear:    return t;
case KeyframeEasing.easeInOut: return t * t * (3 - 2 * t); // smoothstep S-curve
case KeyframeEasing.easeIn:    return t * t;               // quadratic
case KeyframeEasing.easeOut:   return 1 - (1 - t) * (1 - t);

3. ±180° birleşme yeri: her 360 özelliğini rahatsız eden hata

İşte tuzak. Yaw bir daire üzerinde yaşar: +179° ve −179° 358° değil, 2° aralıklıdır. Konunuz kürenin arkasından geçer geçmez, basit matematik kamerayı dünyanın yarısı kadar döndürür. Bu tek sorun sistemin dört farklı katmanında ortaya çıkar ve her biri kendi çözümüne ihtiyaç duyar.

Katman 1 — primitifler. Tüm kod tabanının dayandığı iki küçük yardımcı:

static double normalizeYaw(double yaw) {
  while (yaw > 180) { yaw -= 360; }
  while (yaw < -180) { yaw += 360; }
  return yaw;
}

// "more reliable than MediaMetadataRetriever for 360° videos
//  which often use unusual codecs (HEVC, high-res)"
final maxSafeWidth = 2048, maxSafeHeight = 1080;
// 2:1 4K (4096×2048) is unsafe -> trigger transcodeForPlayback()

// "CRITICAL for seam-safe tracking ... avoids 180° snaps"
static double shortestYawDelta(double from, double to) {
  double delta = to - from;
  if (delta > 180) delta -= 360;
  if (delta < -180) delta += 360;
  return delta;
}

Katman 2 — anahtar kare üretimi, sürekli (sınırsız) bir yaw biriktirir, böylece SLERP her zaman kısa yoldan enterpolasyon yapar. Her örneği ±180°'ye sıkıştırmak yerine, en kısa deltaları toplarız ve çalışan değerin kasıtlı olarak 180°'yi geçmesine izin veririz:

final normalizedContinuousYaw = SphericalCoordinates.normalizeYaw(continuousYaw);
final yawDelta = SphericalCoordinates.shortestYawDelta(normalizedContinuousYaw, rawCoords.yaw);
continuousYaw = continuousYaw + yawDelta;            // may exceed ±180 on purpose
coords = SphericalCoordinates(yaw: continuousYaw, pitch: rawCoords.pitch);

Katman 3 — sınırlayıcı kutu yeniden yapılandırma. Kullanıcı birleşme yerini geçen bir kutu çizdiğinde, dört köşe +170°, +175°, −175°, −170° gibi yaw değerleri bildirir. SphericalProjection bunu algılar (herhangi bir köşe > 90° ve herhangi bir köşe < −90°) ve açıklığını ölçmeden önce negatifleri +360° kaydırarak sürekli-yaw "birleşme yeri güvenli" bir bbox yeniden oluşturur.

Katman 4 — canlı oynatıcı shortestYawDelta kullanarak hedefine doğru pürüzsüzleşir, böylece birleşme yeri üzerinden sürükleme asla sarsılmaz.

360 araçları geliştirme hakkında bir şeyi hatırlamanız gerekirse: birleşme yeri bir uç durum değil, çapraz kesen bir endişedir. Yaw'ın göründüğü her yerde buna bütçe ayırın.

4. Insta360/GoPro gibi hissettirmek

Akıcı enterpolasyon size teknik olarak doğru bir kamera verir. Ancak iyi bir kamera vermez. "His mühendisliği"nin üç parçası bu boşluğu kapatır.

Hareket kısıtlamaları — hız ve ivmeyi sınırlama (clamp). Ham takip verileri titrek olabilir. Anahtar kareleri, sanal kameranın ne kadar hızlı ve ani hareket edebileceğini sınırlayan MotionConstraints'ten geçiririz (örn. maxYawSpeed = 120°/s, maxPitchSpeed = 90°/s, maxAcceleration = 180°/s²), cinematic(), responsive() ve production() ön ayarlarıyla. Bu, "bir sineği takip eden güvenlik kamerası" ile "sakin ellere sahip bir operatör" arasındaki farktır.

Adaptif FOV — konuyu sığdırmak için yakınlaştırma. Konunun eş dikdörtgen uzaydaki açısal boyutunu ölçeriz ve çerçevenin hedef bir bölümünü dolduracak bir FOV seçeriz:

final angularWidth  = (bboxWidth  / videoWidth)  * 360.0;
final angularHeight = (bboxHeight / videoHeight) * 180.0;
final objectAngularSize = math.max(angularWidth, angularHeight);
final idealFov = objectAngularSize / targetFillRatio; // targetFillRatio ≈ 0.4
return idealFov.clamp(minFov, maxFov);                 // 45°–120°

Hareket önceliği (Motion lead) + yüz→vücut harmanlaması. Çerçevelemeyi, hareket yönünde bbox genişliğinin ~%15'i kadar öne iteriz, böylece konu yürüdüğü kenara sıkışmaz. Ve yüz algılama devre dışı kaldığında, vücut merkezine aniden geçmeyiz — son bilinen yüz merkezinden vücut merkezine yaklaşık 10 kare boyunca lerp yaparız.

5. İki video numarası: önizleme proxy'si ve tam çözünürlüklü orijinal

Bu, her şeyi gerçek telefonlarda kullanılabilir kılan mimari karardır.

Bir 360° GoPro/Insta360 dosyası genellikle 4096×2048 HEVC'dir. Bütçe dostu Android kod çözücüler, bunun üzerine nesne takibi çalıştırırken bir yana, akıcı bir şekilde oynatmaya çalışırken tıkanır. Bu nedenle takip ve canlı önizlemeyi dönüştürülmüş bir proxy üzerinde (yaklaşık 1440×720, libx264 -preset veryfast -crf 28) çalıştırırız ve tam çözünürlüklü orijinali yalnızca son dışa aktarma için saklarız.

// "more reliable than MediaMetadataRetriever for 360° videos
//  which often use unusual codecs (HEVC, high-res)"
final maxSafeWidth = 2048, maxSafeHeight = 1080;
// 2:1 4K (4096×2048) is unsafe -> trigger transcodeForPlayback()

Ele almanız gereken tuzak: anahtar kare sınırlayıcı kutular proxy koordinatlarında hesaplanır ve dışa aktarmadan önce orijinal koordinatlara ölçeklenmelidir. Bu ölçeklendirmeyi yanlış yaparsanız, dışa aktarımınız kullanıcının önizlediğinden biraz farklı bir bölgeyi çerçeveler. (Meta veri ve ilk kare çıkarma için MediaMetadataRetriever yerine ffprobe kullanırız — Android'in yerleşik retriever'ı 360 kameraların çıkardığı sıra dışı kodeklerde kararsızdır.)

6. GPU dışa aktarma: MethodChannel üzerinden OpenGL + MediaCodec

Bir telefon CPU'sunda 4K bir kürenin her karesini FFmpeg'in v360 filtresi aracılığıyla yeniden projekte etmek çok yavaştır. (v360 dizesini saklarız — projeksiyon semantiğini düzenli bir şekilde belgelemektedir, v360=e:flat:... "eş dikdörtgen giriş, düz çıkış" anlamına gelir — ancak bu canlı yol değildir.)

Gerçek dışa aktarma, anahtar kareleri bir MethodChannel üzerinden yerel bir OpenGL ES + MediaCodec işleyicisine teslim eder:

final result = await _methodChannel.invokeMethod('exportVideoGPU', {
  'videoPath': resolvedVideoPath,
  'outputPath': outputPath,
  'keyframes': keyframesList,
  'initialOrientation': initialOrientationMap,
  'width': settings.exportWidth,
  'height': settings.exportHeight,
  'fps': settings.outputFps
.toInt(),
  'includeAudio': settings.includeAudio,
  if (isTrimmed) 'trimStartMs': trimStartMs,
  if (isTrimmed) 'trimEndMs': trimEndMs
,
});

Bunu bir yetenek kontrolünün (Android API 21+, bir donanım H.264 kodlayıcı, OpenGL ES 3.0) arkasına saklarız ve ilerlemeyi bir EventChannel üzerinden geri aktarırız. Birkaç zor kazanılmış tuzak:

  • −90° yaw ofseti. Dart konvansiyonumuz yaw = 0'ı eş dikdörtgen görüntünün merkezine (u = 0.5) yerleştirir; OpenGL işleyicisi yaw = 0'ı u = 0.75'e yerleştirir. Bu nedenle her anahtar kare yaw'ı, kanal boyunca −90° ile yeniden eşlenir (ve ±180°'ye yeniden sarılır) — hem dışa aktarma kanalına hem de canlı oynatıcı kanalına simetrik olarak uygulanır, böylece önizleme ve dışa aktarım uyumlu olur.
  • Dejenere anahtar kare sayıları. Sıfır anahtar kare → başlangıç oryantasyonundan iki özdeş anahtar kare sentezleyin (statik dışa aktarım). Bir anahtar kare → onu çoğaltın. İşleyici her zaman en az bir başlangıç ve bir bitiş ister.
  • Kırpma, zaman damgalarını yeniden ayarlar. Kırpılmış bir dışa aktarım, her anahtar kareyi t − trimStartMs kadar kaydırır.

Kimsenin beklemediği FFmpeg son işlem adımı

GPU dışa aktarmanın bitiş çizgisi olduğunu düşünebilirsiniz. Değil — çünkü Android'in MediaMuxer'ı moov atomunu dosyanın sonuna yazar ve kaynağın ses PTS ofsetini taşıyabilir. Sonuç: ExoPlayer (ve Flutter'ın video_player'ı) saat çalışmaya devam ederken görüntüyü dondurur. Bu nedenle GPU işleminden sonra, FFmpeg hızlı, kayıpsız bir temizlik yapar:

// copy video (no re-encode -> zero quality loss), re-encode audio to fix PTS,
// move moov atom to the front for instant playback.
-c:v copy -af "aresample=async=1:first_pts=0" -movflags +faststart

Ve bunu koruyan acımasız küçük bir tuzak: önce bir ses parçası (audio track) ararız, çünkü hiç ses parçası olmayan bir videoya ses bayrakları geçirmek bazı cihazlarda FFmpeg'in sonsuza kadar takılmasına neden olur. Tüm son işlem adımı, zaman aşımında iptal ile 60 saniyelik bir zaman aşımı içinde sarılır.

7. Canlı önizleme de yerel bir oynatıcıdır

Editör içi önizleme bir Flutter CustomPaint değildir — kendi görünüme özel method/event channel'ı olan, AndroidView ( 'your_app/spherical360player' gibi bir viewType ile) aracılığıyla yerleştirilmiş yerel bir OpenGL yüzeyidir. Dart Spherical360PlayerController, yaw/pitch/roll/fov değerlerini yansıtır ve göndermeden önce aynı −90° ofseti uygular.

Keyifli kılan his detayları:

  • Kare hızı bağımsız yumuşatma — factor = 1 - exp(-speed * dt) böylece kolaylaştırma 60fps ve 90fps'de aynı görünür, birleşme yeri güvenli en kısa yol yaw'ı dahil olmak üzere.
  • Fırlatmadan sonra momentum/atalet, sürtünmeye dayalı bozunma ile — oynatma sırasında devre dışı, çünkü oynatma sırasında kamera kullanıcının son hareketine değil, anahtar karelere uymalıdır.
  • Tek bir GestureDetector, onScaleUpdate işlevi kaydırma ve sıkıştırarak yakınlaştırmayı birlikte yapar; çift dokunma oynat/duraklat arasında geçiş yapar. FOV, kontrolör sınırında 30°–120°'ye, pitch ±90°'ye sıkıştırılır.

Küçük ama anlamlı bir UX düzeltmesi: "takip"ten "anahtar kare oluşturma"ya geçmeden önce, Flutter'ın gerçekten "100%" boyamasını sağlamak için 120 ms'lik bir gecikme ekleriz. Bu olmadan, son ilerleme karesi ve durum değişikliği aynı mikro görev grubuna düşer ve kullanıcı tamamlanmayı asla göremez.

Kısaca durum makinesi

Tüm bunlar, bir durum enum'unu yönlendiren bir Riverpod ReframeNotifier tarafından koordine edilir:

boşta → video yükleniyor → dönüştürülüyor
    → (algılanıyor | nesne seçiliyor | oynatıcıda bbox çiziliyor | oynatıcıda otomatik algılanıyor)
    → takip ediliyor → anahtar kareler oluşturuluyor → düzenleniyor → dışa aktarılıyor → tamamlandı | hata

Birden çok giriş noktası aynı takipçiyi besler: dokunarak algılama (YOLO yerleşimi), manuel bbox veya Insta360 tarzı doğrudan oynatıcı üzerinde çizim hareketi. Hepsi KeyframeGenerator.production().generate(...) → KeyframeSmoother.smooth(...) üzerinde birleşir ve oradan enterpolasyoncu her önizlenen ve dışa aktarılan kareyi yönlendirir.

Öğrenilen dersler

  1. Matematiği saf tutun. Projeksiyon/enterpolasyon katmanında Flutter importları olmasın. Bu, test edilebilir olmasının tek nedeniydi.
  2. Yön için Quaternion SLERP; skalerler için LERP. Euler açılarını enterpolasyon yapmayın ve zoom'unuzu SLERP etmeyin.
  3. ±180° birleşme yeri, bir uç durum değil, çapraz kesen bir endişedir. Primitiflerde, üretimde, projeksiyonda ve oynatıcıda yeniden ortaya çıkar.
  4. Konvansiyonlarınızı bir kez, simetrik olarak uzlaştırın. Dart ve OpenGL arasındaki −90° yaw ofseti, hem kanal sınırlarında uygulanır, böylece önizleme == dışa aktarma olur.
  5. Etkileşim için proxy, çıktı için orijinal. Sadece koordinatlarınızı ikisi arasında ölçeklendirmeyi unutmayın.
  6. "Oluşturma tamamlandı" ≠ "doğru oynuyor." Moov atomu yerleşimini ve ses PTS'sini düzeltmek için bir FFmpeg son işlem adımı için bütçe ayırın ve ses bayrakları eklemeden önce kontrol edin.
  7. His bir özelliktir. Hareket kısıtlamaları, adaptif FOV ve hareket önceliği, "teknik olarak yeniden çerçevelenmiş" olanı "bir profesyonelin çektiği gibi görünmekten" ayıran şeylerdir.

Sonuç: Tamamen cihaz üzerinde çalışan bir 360° yeniden çerçeveleme editörü — bir konuya dokunun, takip etmesine izin verin, anahtar kareleri ayarlayın ve temiz, düz bir video dışa aktarın — Dart'ın yeterince hızlı yapamayacağı işi yapan ince bir yerel GPU katmanı ile Flutter'da geliştirilmiştir.

Flutter360VideoOpenGLQuaternions
Saurav Kumar Gupta's image

Yazar Hakkında

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

Building innovative AI-powered solutions and helping businesses transform through cutting-edge technology.

Daha fazla bilgi edinmek ister misiniz?

Bu çözümleri işletmeniz için nasıl uygulayabileceğimizi tartışmak için bizimle iletişime geçin.

İletişime Geçin

Sıkça Sorulan Sorular

360° video reframing is the process of converting equirectangular 360° footage into a standard flat video by controlling a virtual camera's direction, zoom, and field of view over time.

Quaternions enable smooth camera rotations without gimbal lock or abrupt transitions, making them ideal for creating natural-looking camera movements in 360° video reframing.

Flutter uses spherical projection for live previews, quaternion-based interpolation for camera motion, and a native OpenGL GPU pipeline to export high-quality reframed videos efficiently.

Accurate reframing requires handling spherical projection, seamless rotation across the ±180° boundary, smooth camera interpolation, and synchronized GPU rendering to produce stable, cinematic video output.

GPU acceleration significantly reduces rendering time by processing projection and video encoding on the graphics hardware, making high-resolution 360° video export practical on mobile devices.

Comments (0)

Share your thoughts and join the conversation

Leave a Comment

Your email will not be published

No comments yet

Be the first to share your thoughts!