Agrarsense
Camera.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
6#include "Camera.h"
7
16
17#include "NiagaraComponent.h"
18#include "Components/PrimitiveComponent.h"
19#include "Materials/Material.h"
20#include "ImageUtils.h"
21#include "Async/Async.h"
22#include "HighResScreenshot.h"
23#include "ImageWriteTask.h"
24#include "ImageWriteQueue.h"
25#include "Misc/DateTime.h"
26#include "Math/Color.h"
27#include "Blueprint/UserWidget.h"
28#include "GameFramework/PlayerController.h"
29#include "GeoReferencingSystem.h"
30#include "TimerManager.h"
31#include "GameFramework/SpringArmComponent.h"
32
33ACamera::ACamera(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
34{
35 PrimaryActorTick.bCanEverTick = true;
36 PrimaryActorTick.TickGroup = TG_PostUpdateWork;
37
38 CaptureRenderTarget = CreateDefaultSubobject<UTextureRenderTarget2D>(FName(*FString::Printf(TEXT("CaptureRenderTarget"))));
39 CaptureRenderTarget->CompressionSettings = TextureCompressionSettings::TC_Default;
40 CaptureRenderTarget->SRGB = false;
41 //CaptureRenderTarget->bForceLinearGamma = true;
42 CaptureRenderTarget->bAutoGenerateMips = false;
43 CaptureRenderTarget->bGPUSharedFlag = true;
44 //CaptureRenderTarget->RenderTargetFormat = RTF_RGBA8;
45 CaptureRenderTarget->AddressX = TextureAddress::TA_Clamp;
46 CaptureRenderTarget->AddressY = TextureAddress::TA_Clamp;
47
48 CaptureComponent2D = CreateDefaultSubobject<USceneCaptureComponent2D>(FName(*FString::Printf(TEXT("SceneCaptureComponent2D"))));
49 CaptureComponent2D->SetupAttachment(RootComponent);
50 CaptureComponent2D->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_RenderScenePrimitives;
51 CaptureComponent2D->bCaptureOnMovement = false;
52 CaptureComponent2D->bCaptureEveryFrame = false;
53 CaptureComponent2D->bAlwaysPersistRenderingState = true;
54}
55
57{
58 SetupCamera(newParameters);
59}
60
61void ACamera::AddPostProcessingMaterial(const FString& Path, float Weight)
62{
63 UMaterial* PostProcessingMat = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), nullptr, *Path));
64 if (PostProcessingMat && CaptureComponent2D)
65 {
66 FPostProcessSettings& PostProcessSettings = CaptureComponent2D->PostProcessSettings;
67 PostProcessSettings.AddBlendable(PostProcessingMat, Weight);
68 }
69 else
70 {
71 const FString ErrorMessage = FString::Printf(TEXT("Failed to add post-processing material to sensor: %s"), *GetSensorName());
72 SimulatorLog::Log(ErrorMessage);
73 }
74}
75
77{
79 {
80 FPostProcessSettings& PostProcessSettings = CaptureComponent2D->PostProcessSettings;
81 PostProcessSettings.RemoveBlendable(Material);
82 }
83}
84
85void ACamera::Init(FCameraBaseParameters parameters, bool SimulateSensor)
86{
88 SetSimulateSensor(SimulateSensor);
89 SetupCamera(parameters);
90}
91
93{
94 if (IsValid(LogFile))
95 {
96 // File has already been created, return
97 return;
98 }
99
100 FLogFileSettings Settings;
103 Settings.QueueLength = MAX_int32; // Only write the log after destroying the sensor
104 Settings.KeepFileOpen = false;
105 Settings.Timestamp = false;
106 Settings.OverrideFilePath = true;
107 Settings.FilePath = FileSavePath;
108
109 LogFile = NewObject<ULogFile>(ULogFile::StaticClass());
110 if (IsValid(LogFile))
111 {
112 const FString FileName = "camera_metadata";
113 LogFile->Create(FileName, Settings);
114
115 // Write camera metadata first line to explain the values.
116 GeoReferencingSystem = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
118 {
119 WriteToLogFile("timestamp, image_name, X location, Y location, Z location, yaw rotation, pitch rotation, roll rotation, GPS latitude, GPS longitude, GPS altitude");
120 }
121 else
122 {
123 WriteToLogFile("timestamp, image_name, X location, Y location, Z location, yaw rotation, pitch rotation, roll rotation");
124 }
125 }
126}
127
129{
131 {
132#if WITH_EDITOR
133 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: CaptureComponent2D is nullptr!"));
134#endif
135 return;
136 }
137
138 CameraParameters = parameters;
139
141
143 {
145 }
146
147 const bool UsePostProcessingEffects = CameraParameters.PostProcessingEffects;
148 auto& PostProcessSettings = CaptureComponent2D->PostProcessSettings;
149
150 if (parameters.UsePhysicLensDistortionEffect)
151 {
152 if (!PhysicLensDistortion.IsValid())
153 {
154 const FString Path = "/Game/Agrarsense/Materials/PostProcessingMaterials/PhysicLensDistortion.PhysicLensDistortion";
155 PhysicLensDistortion = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), nullptr, *Path));
156
157 if (PhysicLensDistortion.IsValid())
158 {
159 PostProcessSettings.AddBlendable(PhysicLensDistortion.Get(), 1.0f);
160#if WITH_EDITOR
161 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Added physics lens distortion effect."));
162#endif
163 }
164 }
165 }
166 else if (PhysicLensDistortion.IsValid())
167 {
168 PostProcessSettings.RemoveBlendable(PhysicLensDistortion.Get());
169 PhysicLensDistortion.Reset();
170#if WITH_EDITOR
171 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Removed physics lens distortion effect."));
172#endif
173 }
174
175 if (parameters.UseIceLensEffect)
176 {
177 if (!IceMaterialInstance.IsValid())
178 {
179 const FString Path = "/Game/Agrarsense/Materials/PostProcessingMaterials/CameraPostProcessEffects/m_ice_lens_effect";
180 UMaterial* LoadedIceMaterial = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), nullptr, *Path));
181
182 // Create UMaterialInstanceDynamic from LoadedIceMaterial
183 IceMaterialInstance = UMaterialInstanceDynamic::Create(LoadedIceMaterial, nullptr);
184 if (IceMaterialInstance.IsValid())
185 {
186 // Add material to CaptureComponent2D PostProcessSettings
187 PostProcessSettings.AddBlendable(IceMaterialInstance.Get(), 1.0f);
188#if WITH_EDITOR
189 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Added ice lens effect."));
190#endif
191 }
192 }
193
194 UMaterialInstanceDynamic* IceMatInstance = IceMaterialInstance.Get();
195 if (IceMatInstance)
196 {
197 // Set IceMaterialInstance scalar parameters values
198 IceMatInstance->SetScalarParameterValue(FName("Strength"), parameters.IceLensEffectStrength);
199 IceMatInstance->SetScalarParameterValue(FName("Angle"), parameters.IceLensEffectAngle);
200#if WITH_EDITOR
201 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Changed ice lens effect parameters."));
202#endif
203 }
204 }
205 else if (IceMaterialInstance.IsValid())
206 {
207 UMaterialInstanceDynamic* IceMatInstance = IceMaterialInstance.Get();
208 if (IceMatInstance)
209 {
210 // Remove material from CaptureComponent2D PostProcessSettings
211 PostProcessSettings.RemoveBlendable(IceMatInstance);
212 IceMaterialInstance.Reset();
213
214#if WITH_EDITOR
215 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Removed ice lens effect."));
216#endif
217 }
218 }
219
222 const float TargetGamma = CameraParameters.TargetGamma;
223 const bool Enable16BitFormat = CameraParameters.Enable16BitFormat;
224 bool ForceLinearGamma = !CameraParameters.PostProcessingEffects;
225
226 ESensorTypes SensorType = GetSensorType();
227 EPixelFormat PixelFormat = Enable16BitFormat ? PF_FloatRGB : PF_B8G8R8A8;
228
230 {
231 PixelFormat = PF_B8G8R8A8;
232 ForceLinearGamma = true;
233 }
234
235 CaptureRenderTarget->InitCustomFormat(ImageWidth, ImageHeight, PixelFormat, ForceLinearGamma);
236 //CaptureRenderTarget->InitCustomFormat(ImageWidth, ImageHeight, PF_B8G8R8A8, true);
237 CaptureRenderTarget->TargetGamma = TargetGamma;
238 CaptureRenderTarget->UpdateResourceImmediate(true);
239
240 // Set PostProcessSettings override (on or off)
241 PostProcessSettings.bOverride_AutoExposureMethod = UsePostProcessingEffects;
242 PostProcessSettings.bOverride_AutoExposureBias = UsePostProcessingEffects;
243 PostProcessSettings.bOverride_AutoExposureMinBrightness = UsePostProcessingEffects;
244 PostProcessSettings.bOverride_AutoExposureMaxBrightness = UsePostProcessingEffects;
245 PostProcessSettings.bOverride_AutoExposureSpeedUp = UsePostProcessingEffects;
246 PostProcessSettings.bOverride_AutoExposureSpeedDown = UsePostProcessingEffects;
247 PostProcessSettings.bOverride_HistogramLogMin = UsePostProcessingEffects;
248 PostProcessSettings.bOverride_HistogramLogMax = UsePostProcessingEffects;
249 PostProcessSettings.bOverride_CameraShutterSpeed = UsePostProcessingEffects;
250 PostProcessSettings.bOverride_CameraISO = UsePostProcessingEffects;
251 PostProcessSettings.bOverride_DepthOfFieldFstop = UsePostProcessingEffects;
252 PostProcessSettings.bOverride_DepthOfFieldMinFstop = UsePostProcessingEffects;
253 PostProcessSettings.bOverride_DepthOfFieldBladeCount = UsePostProcessingEffects;
254 PostProcessSettings.bOverride_FilmSlope = UsePostProcessingEffects;
255 PostProcessSettings.bOverride_FilmToe = UsePostProcessingEffects;
256 PostProcessSettings.bOverride_FilmShoulder = UsePostProcessingEffects;
257 PostProcessSettings.bOverride_FilmWhiteClip = UsePostProcessingEffects;
258 PostProcessSettings.bOverride_FilmBlackClip = UsePostProcessingEffects;
259 PostProcessSettings.bOverride_MotionBlurAmount = UsePostProcessingEffects;
260 PostProcessSettings.bOverride_MotionBlurMax = UsePostProcessingEffects;
261 PostProcessSettings.bOverride_MotionBlurPerObjectSize = UsePostProcessingEffects;
262 PostProcessSettings.bOverride_WhiteTemp = UsePostProcessingEffects;
263 PostProcessSettings.bOverride_WhiteTint = UsePostProcessingEffects;
264 PostProcessSettings.bOverride_ColorContrast = UsePostProcessingEffects;
265 PostProcessSettings.bOverride_SceneFringeIntensity = UsePostProcessingEffects;
266 PostProcessSettings.bOverride_ChromaticAberrationStartOffset = UsePostProcessingEffects;
267 PostProcessSettings.bOverride_AmbientOcclusionIntensity = UsePostProcessingEffects;
268 PostProcessSettings.bOverride_AmbientOcclusionRadius = UsePostProcessingEffects;
269 PostProcessSettings.bOverride_AmbientOcclusionStaticFraction = UsePostProcessingEffects;
270 PostProcessSettings.bOverride_AmbientOcclusionFadeDistance = UsePostProcessingEffects;
271 PostProcessSettings.bOverride_AmbientOcclusionPower = UsePostProcessingEffects;
272 PostProcessSettings.bOverride_AmbientOcclusionBias = UsePostProcessingEffects;
273 PostProcessSettings.bOverride_AmbientOcclusionQuality = UsePostProcessingEffects;
274 PostProcessSettings.bOverride_BloomMethod = UsePostProcessingEffects;
275 PostProcessSettings.bOverride_BloomIntensity = UsePostProcessingEffects;
276 PostProcessSettings.bOverride_BloomThreshold = UsePostProcessingEffects;
277 PostProcessSettings.bOverride_LensFlareIntensity = UsePostProcessingEffects;
278 PostProcessSettings.bOverride_DepthOfFieldFocalDistance = UsePostProcessingEffects;
279 PostProcessSettings.bOverride_DepthOfFieldDepthBlurAmount = UsePostProcessingEffects;
280 PostProcessSettings.bOverride_DepthOfFieldDepthBlurRadius = UsePostProcessingEffects;
281
282 // Set PostProcessSettings values
283 PostProcessSettings.CameraShutterSpeed = CameraParameters.ShutterSpeed;
284 PostProcessSettings.CameraISO = CameraParameters.ISO;
285 PostProcessSettings.DepthOfFieldFstop = CameraParameters.Aperture;
286 PostProcessSettings.DepthOfFieldFocalDistance = CameraParameters.FocalDistance;
287 PostProcessSettings.DepthOfFieldDepthBlurAmount = CameraParameters.DepthBlurAmount;
288 PostProcessSettings.DepthOfFieldDepthBlurRadius = CameraParameters.DepthBlurRadius;
289 PostProcessSettings.DepthOfFieldMinFstop = CameraParameters.DofMinFStop;
290 PostProcessSettings.DepthOfFieldBladeCount = CameraParameters.DofBladeCount;
291 PostProcessSettings.FilmSlope = CameraParameters.FilmSlope;
292 PostProcessSettings.FilmToe = CameraParameters.FilmToe;
293 PostProcessSettings.FilmShoulder = CameraParameters.FilmShoulder;
294 PostProcessSettings.FilmBlackClip = CameraParameters.FilmBlackClip;
295 PostProcessSettings.FilmWhiteClip = CameraParameters.FilmWhiteClip;
296 PostProcessSettings.AutoExposureMinBrightness = CameraParameters.ExposureMinBrightness;
297 PostProcessSettings.AutoExposureMaxBrightness = CameraParameters.ExposureMaxBrightness;
298 PostProcessSettings.AutoExposureSpeedUp = CameraParameters.ExposureSpeedUp;
299 PostProcessSettings.AutoExposureSpeedDown = CameraParameters.ExposureSpeedDown;
300 PostProcessSettings.MotionBlurAmount = CameraParameters.MotionBlurIntensity;
301 PostProcessSettings.MotionBlurMax = CameraParameters.MotionBlurMax;
302 PostProcessSettings.MotionBlurPerObjectSize = CameraParameters.MotionBlurMinObjSize;
303 PostProcessSettings.LensFlareIntensity = CameraParameters.LensFlareIntensity;
304 PostProcessSettings.BloomIntensity = CameraParameters.BloomIntensity;
305 PostProcessSettings.WhiteTemp = CameraParameters.WhiteTemp;
306 PostProcessSettings.WhiteTint = CameraParameters.WhiteTint;
307 PostProcessSettings.SceneFringeIntensity = CameraParameters.ChromAberrIntensity;
308 PostProcessSettings.ChromaticAberrationStartOffset = CameraParameters.ChromAberrOffset;
309
312
314 {
316 }
317 else
318 {
319 CaptureComponent2D->MaxViewDistanceOverride = -1.0f; // Infinite
320 }
321
323 {
324 // Enable HDR on CaptureSource, only allowed for RGB Camera for now.
325 CaptureComponent2D->CaptureSource = ESceneCaptureSource::SCS_FinalToneCurveHDR;
326 }
327 else
328 {
329 CaptureComponent2D->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
330 }
331
332 CaptureComponent2D->bUseRayTracingIfEnabled = true;
333 CaptureComponent2D->UpdateContent();
334 CaptureComponent2D->Activate();
335
337
338 const FString Encoding = "bgr8";
339
340 // Create ROS Image message
341 ImageMsg.Reset();
342 ImageMsg = MakeShared<ROSMessages::sensor_msgs::Image>();
343 ImageMsg->header.frame_id = "world";
344 ImageMsg->height = ImageWidth;
345 ImageMsg->width = ImageHeight;
346 ImageMsg->encoding = Encoding;
347 ImageMsg->is_bigendian = 0;
348 ImageMsg->step = ImageWidth * 4;
349
351
352 if (!UnrealWindow.IsValid())
353 {
354 // Create and setup UnrealWindow
355 const FString WindowName = GetSensorIdentifier() + " Window";
357
358 // Set on Window closed event
360 UnrealWindow->GetSWindow()->SetOnWindowClosed(OnWindowClosedDelegate);
361
362 // try to create Camera parameter Widget from path
363 FSoftClassPath WidgetClassPath(TEXT("/Game/Agrarsense/Widgets/Camera/WBP_CameraControlsMenu.WBP_CameraControlsMenu_C"));
364 UClass* WidgetClass = WidgetClassPath.TryLoadClass<UUserWidget>();
365
366 APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
367
368 if (WidgetClass && PlayerController)
369 {
370 UCameraWidgetBase* WidgetInstance = CreateWidget<UCameraWidgetBase>(PlayerController, WidgetClass);
371 if (WidgetInstance)
372 {
373 bool ShowGuide = false;
374 WidgetInstance->Setup(this, ShowGuide);
375 this->AddWidgetToWindow(WidgetInstance);
376 }
377 }
378 }
379
380 bool Resized = false;
381 if (UnrealWindow.IsValid())
382 {
383 UnrealWindow->SetupComponent(CaptureRenderTarget);
384 if (UnrealWindow->GetWindowWidth() != ImageWidth || UnrealWindow->GetWindowHeight() != ImageHeight)
385 {
386 UnrealWindow->ResizeWindow(ImageWidth, ImageHeight);
387 Resized = true;
388 }
389 }
390
392
393 if (Resized)
394 {
396 }
397
399 {
401 }
402}
403
405{
406 Super::BeginPlay();
407
408 // Register to PreActorTick where USceneCaptureComponent2D will get rendered.
409 OnPreTickDelegate = FWorldDelegates::OnWorldPreActorTick.AddUObject(this, &ACamera::PreActorTick);
410
411 // Check if we should use ATickManager which ticks in parallel, or use Post actor tick
412 // In there we capture the screen pixels with FrameGrabber API,
413 // might do some extra processing, save image to disk and send data to ROS.
415 {
416 SetActorTickEnabled(false);
417 TickEntry = ATickManager::AddTick(this, BindTick(this, &ACamera::EndOfFrameParellel), ETickType::LateTickParallel);
418 }
419 else
420 {
421 SetActorTickEnabled(true);
422
423 if (UseReadPixels)
424 {
425 UE_LOG(LogTemp, Warning, TEXT("juu"));
426 OnPostTickDelegate = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &ACamera::PixelReadEndOfFrame);
427 }
428 else
429 {
430 OnPostTickDelegate = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &ACamera::EndOfFrame);
431 }
432 }
433
434 // Get all existing components set to be hidden for all Camera sensors (like Lidar point cloud visualization).
435 auto Components = ASensor::GetComponentsToHide();
436 for (int32 i = 0; i < Components.Num(); i++)
437 {
438 UPrimitiveComponent* Primitive = Components[i].Get();
439 if (Primitive)
440 {
441 HidePrimitiveComponent(Primitive);
442 }
443 }
444
445 OnPrimitiveAdded.AddUniqueDynamic(this, &ACamera::HidePrimitiveComponent);
446
447 GetWorld()->GetTimerManager().SetTimerForNextTick([this]()
448 {
449 if (IsValid(this))
450 {
451 DronePtr = Cast<APIDDrone>(GetAttachParentActor());
452 if (!DronePtr)
453 {
454 AllowGimbal = false;
455 }
456 }
457 });
458}
459
460void ACamera::EndPlay(const EEndPlayReason::Type EndPlayReason)
461{
462 Super::EndPlay(EndPlayReason);
463
464 FWorldDelegates::OnWorldPreActorTick.Remove(OnPreTickDelegate);
465
467 {
469 }
470 else
471 {
472 FWorldDelegates::OnWorldPostActorTick.Remove(OnPostTickDelegate);
473 }
474
475 if (UnrealWindow.IsValid())
476 {
477 OnWindowClosedDelegate.Unbind();
478 UnrealWindow->DestroyWindow();
479 UnrealWindow.Reset();
480 }
481
483
485 {
486 CaptureRenderTarget->ConditionalBeginDestroy();
487 CaptureRenderTarget = nullptr;
488 }
489
491 {
492 CaptureComponent2D->UnregisterComponent();
493 CaptureComponent2D->ConditionalBeginDestroy();
494 CaptureComponent2D = nullptr;
495 }
496
497 ImageMsg.Reset();
498 IceMaterialInstance.Reset();
499
500 if (BGR8Buffer)
501 {
502 delete[] BGR8Buffer;
503 BGR8Buffer = nullptr;
504 }
506}
507
508void ACamera::PreActorTick(UWorld* World, ELevelTick TickType, float DeltaSeconds)
509{
510 TRACE_CPUPROFILER_EVENT_SCOPE(ACamera::PreActorTick);
511
513
514 if (ShouldSimulate(DeltaSeconds) && CaptureComponent2D)
515 {
516 // Capture scene now.
517 // We could use CaptureSceneDeferred but it would not work if World (Spectator) Rendering is disabled.
518 CaptureComponent2D->CaptureScene();
519
520 // Inform FrameGrabber to capture this frame
521 if (FrameGrabber.IsValid() && !UseReadPixels)
522 {
523 FrameGrabber->CaptureThisFrame(FFramePayloadPtr());
524 }
525 }
526}
527
529{
530 // If this Camera sensor is attached to Drone,
531 // keep the camera steady kind of like a Gimbal.
533 {
534 if (SensorSetRotation == FRotator::ZeroRotator)
535 {
536 // Save garage sensor values
537 SensorSetRotation = GetTransform().Rotator();
538 }
539 FRotator GimbalRot = FRotator(SensorSetRotation.Pitch, GetActorRotation().Yaw, SensorSetRotation.Roll);
540 SetActorRotation(GimbalRot);
541 }
542}
543
544void ACamera::EndOfFrame(UWorld* World, ELevelTick TickType, float DeltaSeconds)
545{
547}
548
549void ACamera::PixelReadEndOfFrame(UWorld* World, ELevelTick TickType, float DeltaSeconds)
550{
552}
553
554void ACamera::EndOfFrameParellel(float DeltaTime)
555{
557}
558
559bool ACamera::ShouldSimulate(const float DeltaSeconds)
560{
561 if (!CanSimulateSensor())
562 {
563 ShouldSimulateCamera = false;
565 }
566
568 {
571 }
572
573 FrameRateTimer += DeltaSeconds;
575 {
576 FrameRateTimer = 0.0f;
579 }
580
581 ShouldSimulateCamera = false;
582
584}
585
587{
588 TRACE_CPUPROFILER_EVENT_SCOPE(ACamera::FrameGrabberCapture);
589
590 if (!ShouldSimulateCamera || !FrameGrabber.IsValid())
591 {
592 return;
593 }
594
596 {
597 return;
598 }
599
600 TArray<FCapturedFrameData> Frames = FrameGrabber->GetCapturedFrames();
601 if (Frames.Num())
602 {
603 FCapturedFrameData& LastFrame = Frames.Last();
604 TArray<FColor>& ImageBuffer = LastFrame.ColorBuffer;
605
606 AddProcessingToFrameBuffer(ImageBuffer);
607
609 {
611 {
614 }
615
616 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, ImageBuffer]()
617 {
619 });
620 }
621
623 }
624}
625
627{
628 TRACE_CPUPROFILER_EVENT_SCOPE(ACamera::ReadPixelsCapture);
629
631 {
632 return;
633 }
634
636 {
637 return;
638 }
639
640 TArray<FColor> ImageBuffer;
641 ImageBuffer.Reserve(ImageWidth * ImageHeight);
642 FReadSurfaceDataFlags Flags(RCM_UNorm);
643 Flags.SetLinearToGamma(false); // IMPORTANT to get instance segmentation ID's correctly
644 auto* Resource = CaptureRenderTarget->GameThread_GetRenderTargetResource();
645 const bool succeeded = Resource->ReadPixels(ImageBuffer, Flags);
646
648 {
650 {
653 }
654
655 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, ImageBuffer]()
656 {
658 });
659 }
660
662}
663
664void ACamera::AddProcessingToFrameBuffer(TArray<FColor>& buffer)
665{
666 return;
667}
668
669void ACamera::ApplyGammaCorrectionLUT(TArray<FColor>& Buffer)
670{
671 static uint8 GammaLUT[256];
672 static bool LUTInitialized = false;
673 if (!LUTInitialized)
674 {
675 LUTInitialized = true;
676 for (int32 i = 0; i < 256; ++i)
677 {
678 float Linear = FMath::Pow(i / 255.0f, 2.2f);
679 GammaLUT[i] = FMath::RoundToInt(Linear * 255.0f);
680 }
681 }
682
683 FColor* RESTRICT BufferPtr = Buffer.GetData();
684 const int32 Num = Buffer.Num();
685
686 const int32 ChunkSize = 512;
687 const int32 NumChunks = (Num + ChunkSize - 1) / ChunkSize;
688
689 ParallelFor(NumChunks, [BufferPtr, Num](int32 ChunkIndex)
690 {
691 const int32 Start = ChunkIndex * ChunkSize;
692 const int32 End = FMath::Min(Start + ChunkSize, Num);
693
694 for (int32 i = Start; i < End; ++i)
695 {
696 FColor& C = BufferPtr[i];
697 C.R = GammaLUT[C.R];
698 C.G = GammaLUT[C.G];
699 C.B = GammaLUT[C.B];
700 }
701 }, false);
702}
703
704void ACamera::HidePrimitiveComponent(UPrimitiveComponent* PrimitiveComponent)
705{
706 if (PrimitiveComponent && CaptureComponent2D)
707 {
708 CaptureComponent2D->HideComponent(PrimitiveComponent);
709 }
710}
711
713{
714 if (!UnrealWindow.IsValid())
715 {
716#if WITH_EDITOR
717 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: UnrealWindow is nullptr!"));
718#endif
719 return;
720 }
721
722 // Destroy old FrameGrabber
724
725 TSharedPtr<FSceneViewport> SceneViewport = UnrealWindow->GetSceneViewport();
726 if (SceneViewport.IsValid() && CaptureRenderTarget)
727 {
728 EPixelFormat PixelFormat = GetPixelFormatFromRenderTargetFormat(TextureFormat);
729 FIntPoint Size = SceneViewport->GetSize();
730
732 {
733 // Currently only DVS camera needs this texture to render DVS output into separate FUnrealWindow.
734 CaptureFrameTexture = UTexture2D::CreateTransient(Size.X, Size.Y, PixelFormat);
735 CaptureFrameTexture->UpdateResource();
736 }
737
738 FrameGrabber = MakeShareable(new FFrameGrabber(SceneViewport.ToSharedRef(), Size, PixelFormat));
739 FrameGrabber->StartCapturingFrames();
740 }
741}
742
744{
745 if (FrameGrabber.IsValid())
746 {
747 FrameGrabber->StopCapturingFrames();
748 FrameGrabber->Shutdown();
749 FrameGrabber.Reset();
750 }
751
753 {
754 CaptureFrameTexture->RemoveFromRoot();
755 CaptureFrameTexture->ConditionalBeginDestroy();
756 CaptureFrameTexture = nullptr;
757 }
758}
759
760void ACamera::SendImageDataToROS(const TArray<FColor>& FrameBuffer, int32 Width, int32 Height)
761{
762 TRACE_CPUPROFILER_EVENT_SCOPE(ACamera::SendImageDataToROS);
763
764 UTopic* Topic = GetROSTopic();
765 if (!SendDataToROS
766 || !Topic
767 || !IsROSConnected()
768 || FrameBuffer.IsEmpty()
769 || !ImageMsg.IsValid())
770 {
771 return;
772 }
773
774 // Cast TArray<FColor> into uint8[] data with all channels (RBGA)
775 // If you want to send RGBA data, uncomment the following lines
776 // and comment the BGR8Buffer conversion below
777 //ImageMsg->encoding = "bgra8";
778 //ImageMsg->step = Width * 4;
779 //ImageMsg->data = const_cast<uint8*>(reinterpret_cast<const uint8*>(FrameBuffer.GetData()));
780
781 // Convert TArray<FColor> to uint8[] data without alpha channel
782 // By removing alpha channel, we reduce ~25% of ROS bandwidth,
783 // but costs a bit more in processing time here
784 const int32 BufferSize = Width * Height * 3;
785 if (!BGR8Buffer || CurrentBufferSize != BufferSize)
786 {
787 // Resize buffer if needed
788 delete[] BGR8Buffer;
789 BGR8Buffer = new uint8[BufferSize];
790 CurrentBufferSize = BufferSize;
791 }
792
793 const FColor* RESTRICT Source = FrameBuffer.GetData();
794 uint8* RESTRICT Dest = BGR8Buffer;
795 const int32 PixelCount = FrameBuffer.Num();
796 const int32 ChunkSize = 512;
797 const int32 NumChunks = (PixelCount + ChunkSize - 1) / ChunkSize;
798
799 ParallelFor(NumChunks, [Source, Dest, ChunkSize, PixelCount](int32 ChunkIndex)
800 {
801 const int32 StartIndex = ChunkIndex * ChunkSize;
802 const int32 EndIndex = FMath::Min(StartIndex + ChunkSize, PixelCount);
803
804 for (int32 i = StartIndex; i < EndIndex; ++i)
805 {
806 const FColor& Pixel = Source[i];
807 const int32 j = i * 3;
808 Dest[j + 0] = Pixel.B;
809 Dest[j + 1] = Pixel.G;
810 Dest[j + 2] = Pixel.R;
811 }
812 }, false);
813
814 ImageMsg->data = BGR8Buffer;
815 ImageMsg->step = Width * 3;
816
817 ImageMsg->height = Height;
818 ImageMsg->width = Width;
819 ImageMsg->header.time = FROSTime::Now();
820
821 Topic->Publish(ImageMsg);
822}
823
824void ACamera::SaveImageToDisk(const TArray<FColor> FrameBuffer, int32 Width, int32 Height)
825{
826 TRACE_CPUPROFILER_EVENT_SCOPE(ACamera::SaveImageToDisk);
827
828 if (FrameBuffer.IsEmpty() || Width == 0 || Height == 0)
829 {
830 return;
831 }
832
833 int32 Size = Width * Height;
834 if (FrameBuffer.Num() != Size)
835 {
836 const FString Msg = "Camera sensor: Unable to save Image to disk. FrameBuffer size and resolution don't match!";
838 return;
839 }
840
841 const FString ImageName = FString::Printf(TEXT("image_%d.png"), ImageNumber);
842 const FString FullFileName = FString::Printf(TEXT("%s%s"), *FileSavePath, *ImageName);
843 ++ImageNumber;
844
845 SaveCameraMetaDataToDisk(ImageName);
846
847 FIntPoint DestSize(Width, Height);
848 TImagePixelData<FColor> PixelData(DestSize);
849 PixelData.Pixels = FrameBuffer;
850
851 // Create ImageTask
852 TUniquePtr<FImageWriteTask> ImageTask = MakeUnique<FImageWriteTask>();
853 ImageTask->PixelData = MakeUnique<TImagePixelData<FColor>>(PixelData);
854 ImageTask->Filename = FullFileName;
855 ImageTask->Format = EImageFormat::PNG;
856 ImageTask->CompressionQuality = (int32)EImageCompressionQuality::Default;
857 ImageTask->bOverwriteFile = true;
858 ImageTask->PixelPreProcessors.Add(TAsyncAlphaWrite<FColor>(255));
859
860 // Get Screenshot config and enqueue image save task
861 FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig();
862 TFuture<bool> CompletionFuture = HighResScreenshotConfig.ImageWriteQueue->Enqueue(MoveTemp(ImageTask));
863}
864
865void ACamera::SaveCameraMetaDataToDisk(const FString& ImageName)
866{
867 if (!IsValid(LogFile))
868 {
869 // If the log file located in base class is not valid, return here.
870 return;
871 }
872
873 const FVector ActorPosition = GetActorLocation();
874 const FRotator ActorRotation = GetActorRotation();
875
876 const FString TimeStamp = CreateTimeStampString();
877 FString MetaData;
878
880 {
881 FGeographicCoordinates GeoCoordinates = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, ActorPosition);
882 MetaData = FString::Printf(TEXT("%s, %s, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.8f, %.8f, %.8f"),
883 *TimeStamp, *ImageName,
884 ActorPosition.X, ActorPosition.Y, ActorPosition.Z,
885 ActorRotation.Pitch, ActorRotation.Yaw, ActorRotation.Roll,
886 GeoCoordinates.Latitude, GeoCoordinates.Longitude, GeoCoordinates.Altitude);
887 }
888 else
889 {
890 MetaData = FString::Printf(TEXT("%s, %s, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f"),
891 *TimeStamp, *ImageName,
892 ActorPosition.X, ActorPosition.Y, ActorPosition.Z,
893 ActorRotation.Pitch, ActorRotation.Yaw, ActorRotation.Roll);
894 }
895
896 // Write to the log file (this is written after sensor is destroyed)
897 WriteToLogFile(MetaData);
898}
899
900void ACamera::OnWindowClosed(const TSharedRef<SWindow>& Window)
901{
902#if WITH_EDITOR
903 UE_LOG(LogTemp, Warning, TEXT("Camera.cpp: Camera window closed. Destroying Camera sensor.."));
904#endif
905
906 // Broadcast event that this Camera sensor is about be destroyed when user has clicked Window Close button.
907 OnCameraWindowClosed.Broadcast(this);
908
909 // Destroy this camera sensor
910 this->Destroy();
911}
912
913void ACamera::AddWidgetToWindow(UWidget* WidgetToAdd)
914{
915 if (UnrealWindow.IsValid() && WidgetToAdd)
916 {
917 UnrealWindow->AddUWidgetToWindow(WidgetToAdd);
918 }
919}
920
921void ACamera::RemoveWidgetFromWindow(UWidget* WidgetToRemove)
922{
923 if (UnrealWindow.IsValid() && WidgetToRemove)
924 {
925 UnrealWindow->RemoveUWidgetFromWindow(WidgetToRemove);
926 }
927}
928
929void ACamera::SetShadowRendering(bool RenderShadows)
930{
932 {
933 auto& CameraShowFlags = CaptureComponent2D->ShowFlags;
934 CameraShowFlags.SetDynamicShadows(RenderShadows);
935 CameraShowFlags.SetContactShadows(RenderShadows);
936 CameraShowFlags.SetCapsuleShadows(RenderShadows);
937 }
938}
939
940void ACamera::SetTemporalAA(bool SetTemporal)
941{
943 {
944 auto& CameraShowFlags = CaptureComponent2D->ShowFlags;
945 CameraShowFlags.SetTemporalAA(SetTemporal);
946 }
947}
948
949void ACamera::ResizeCamera(int32 Width, int32 Height)
950{
951 if (Width == 0 || Height == 0)
952 {
953 return;
954 }
955
956 CameraParameters.Width = Width;
957 CameraParameters.Height = Height;
959}
960
961void ACamera::AddHiddenActor(AActor* Actor)
962{
963 if (Actor && CaptureComponent2D)
964 {
965 CaptureComponent2D->HiddenActors.AddUnique(Actor);
966 }
967}
968
970{
972 {
973 return;
974 }
975
976 auto& ShowFlags = CaptureComponent2D->ShowFlags;
977
978 ShowFlags.SetDeferredLighting(false);
979 ShowFlags.SetSkyLighting(false);
980 ShowFlags.SetAmbientOcclusion(false);
981 ShowFlags.SetAntiAliasing(false);
982 ShowFlags.SetVolumetricFog(false);;
983 ShowFlags.SetBloom(false);
984 ShowFlags.SetCameraImperfections(false);
985 ShowFlags.SetCameraInterpolation(false);
986 ShowFlags.SetColorGrading(false);
987 ShowFlags.SetDepthOfField(false);
988 ShowFlags.SetDiffuse(false);
989 ShowFlags.SetDirectionalLights(false);
990 ShowFlags.SetDirectLighting(false);
991 ShowFlags.SetDynamicShadows(false);
992 ShowFlags.SetEyeAdaptation(false);
993 ShowFlags.SetFog(false);
994 ShowFlags.SetGlobalIllumination(false);
995 ShowFlags.SetGrain(false);
996 ShowFlags.SetHLODColoration(false);
997 ShowFlags.SetHMDDistortion(false);
998 ShowFlags.SetLensFlares(false);
999 ShowFlags.SetLightComplexity(false);
1000 ShowFlags.SetLightFunctions(false);
1001 ShowFlags.SetLightInfluences(false);
1002 ShowFlags.SetLighting(false);
1003 ShowFlags.SetLightMapDensity(false);
1004 ShowFlags.SetLightRadius(false);
1005 ShowFlags.SetLightShafts(false);
1006 ShowFlags.SetLODColoration(false);
1007 ShowFlags.SetMotionBlur(false);
1008 ShowFlags.SetOnScreenDebug(false);
1009 ShowFlags.SetParticles(false);
1010 ShowFlags.SetPointLights(false);
1011 ShowFlags.SetRefraction(false);
1012 ShowFlags.SetSceneColorFringe(false);
1013 ShowFlags.SetScreenSpaceAO(false);
1014 ShowFlags.SetScreenSpaceReflections(false);
1015 ShowFlags.SetSkyLighting(false);
1016 ShowFlags.SetSpotLights(false);
1017 ShowFlags.SetStationaryLightOverlap(false);
1018 ShowFlags.SetSubsurfaceScattering(false);
1019 ShowFlags.SetTonemapper(false);
1020 ShowFlags.SetVisualizeBuffer(false);
1021 ShowFlags.SetVisualizeDistanceFieldAO(false);
1022 ShowFlags.SetVisualizeDOF(false);
1023 ShowFlags.SetVisualizeHDR(false);
1024 ShowFlags.SetVisualizeLightCulling(false);
1025 ShowFlags.SetVisualizeMeshDistanceFields(false);
1026 ShowFlags.SetVisualizeMotionBlur(false);
1027 ShowFlags.SetVisualizeOutOfBoundsPixels(false);
1028 ShowFlags.SetVisualizeSenses(false);
1029 ShowFlags.SetVisualizeShadingModels(false);
1030 ShowFlags.SetVisualizeSSR(false);
1031 ShowFlags.SetVisualizeSSS(false);
1032}
ESensorTypes
Definition: SensorTypes.h:15
@ InstanceSegmentationCamera
static auto BindTick(ObjectType *Object, FunctionType Function)
Definition: TickManager.h:162
void UpdateCameraStabilization()
Definition: Camera.cpp:528
bool UseParallelLateTick
Definition: Camera.h:235
void AddPostProcessingMaterial(const FString &Path, float Weight=1.0f)
Definition: Camera.cpp:61
int32 ImageNumber
Definition: Camera.h:390
FTickEntry TickEntry
Definition: Camera.h:303
TSharedPtr< FFrameGrabber > FrameGrabber
Definition: Camera.h:349
void ChangeCameraParameters(FCameraBaseParameters newParameters)
Definition: Camera.cpp:56
virtual ESensorTypes GetSensorType() const override
Definition: Camera.h:74
bool UseReadPixels
Definition: Camera.h:311
FCameraBaseParameters CameraParameters
Definition: Camera.h:365
void ApplyGammaCorrectionLUT(TArray< FColor > &Buffer)
Definition: Camera.cpp:669
FOnWindowClosed OnWindowClosedDelegate
Definition: Camera.h:378
void AddHiddenActor(AActor *Actor)
Definition: Camera.cpp:961
virtual void Init(FCameraBaseParameters parameters, bool SimulateSensor=true)
Definition: Camera.cpp:85
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
Definition: Camera.cpp:460
APIDDrone * DronePtr
Definition: Camera.h:314
USceneCaptureComponent2D * CaptureComponent2D
Definition: Camera.h:292
void RemovePostProcessingMaterial(UMaterial *Material)
Definition: Camera.cpp:76
void SaveCameraMetaDataToDisk(const FString &ImageName)
Definition: Camera.cpp:865
AGeoReferencingSystem * GeoReferencingSystem
Definition: Camera.h:374
float FrameRateTimer
Definition: Camera.h:388
void SaveImageToDisk(const TArray< FColor > FrameBuffer, int32 Width, int32 Height)
Definition: Camera.cpp:824
void ReadPixelsCapture()
Definition: Camera.cpp:626
FDelegateHandle OnPostTickDelegate
Definition: Camera.h:242
bool ShouldSimulateCamera
Definition: Camera.h:385
void PixelReadEndOfFrame(UWorld *World, ELevelTick TickType, float DeltaSeconds)
Definition: Camera.cpp:549
void SetShadowRendering(bool RenderShadows)
Definition: Camera.cpp:929
void OnWindowClosed(const TSharedRef< SWindow > &Window)
Definition: Camera.cpp:900
void StartFrameGrabber()
Definition: Camera.cpp:712
void FrameGrabberCapture()
Definition: Camera.cpp:586
void SendImageDataToROS(const TArray< FColor > &FrameBuffer, int32 Width, int32 Height)
Definition: Camera.cpp:760
FDelegateHandle OnPreTickDelegate
Definition: Camera.h:237
UTextureRenderTarget2D * CaptureRenderTarget
Definition: Camera.h:289
void AddWidgetToWindow(UWidget *WidgetToAdd)
Definition: Camera.cpp:913
TSharedPtr< ROSMessages::sensor_msgs::Image > ImageMsg
Definition: Camera.h:309
int32 CurrentBufferSize
Definition: Camera.h:394
FRotator SensorSetRotation
Definition: Camera.h:315
bool SaveCurrentFrameToDiskRequested
Definition: Camera.h:383
void SetTemporalAA(bool SetTemporal)
Definition: Camera.cpp:940
void CreateLogFile() override
Definition: Camera.cpp:92
UTexture2D * CaptureFrameTexture
Definition: Camera.h:299
void SetupCamera(FCameraBaseParameters parameters)
Definition: Camera.cpp:128
TWeakObjectPtr< UMaterial > PhysicLensDistortion
Definition: Camera.h:307
void RemoveWidgetFromWindow(UWidget *WidgetToRemove)
Definition: Camera.cpp:921
FCameraDelegate_OnWindowResized OnCameraWindowResized
Definition: Camera.h:148
void HidePrimitiveComponent(UPrimitiveComponent *PrimitiveComponent)
Definition: Camera.cpp:704
TEnumAsByte< ETextureRenderTargetFormat > TextureFormat
Definition: Camera.h:134
void ReleaseFrameGrabber()
Definition: Camera.cpp:743
void EndOfFrameParellel(float DeltaTime)
Definition: Camera.cpp:554
virtual void AddProcessingToFrameBuffer(TArray< FColor > &buffer)
Definition: Camera.cpp:664
void ResizeCamera(int32 Width=1280, int32 Height=720)
Definition: Camera.cpp:949
uint8 * BGR8Buffer
Definition: Camera.h:392
TSharedPtr< FUnrealWindow > UnrealWindow
Definition: Camera.h:301
void EndOfFrame(UWorld *World, ELevelTick TickType, float DeltaSeconds)
Definition: Camera.cpp:544
float CameraFrameRate
Definition: Camera.h:387
ACamera(const FObjectInitializer &ObjectInitializer)
Definition: Camera.cpp:33
int32 ImageHeight
Definition: Camera.h:381
void PreActorTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
Definition: Camera.cpp:508
FCameraDelegate_OnWindowClosed OnCameraWindowClosed
Definition: Camera.h:141
void DisableShowFlags()
Definition: Camera.cpp:969
virtual void BeginPlay() override
Definition: Camera.cpp:404
TWeakObjectPtr< UMaterialInstanceDynamic > IceMaterialInstance
Definition: Camera.h:305
int32 ImageWidth
Definition: Camera.h:380
bool ShouldSimulate(const float DeltaSeconds)
Definition: Camera.cpp:559
bool AllowGimbal
Definition: Camera.h:316
bool CanSimulateSensor() const
Definition: Sensor.h:170
static FPrimitiveAdded OnPrimitiveAdded
Definition: Sensor.h:377
FString GetSensorIdentifier() const
Definition: Sensor.h:75
virtual void CreateDataSavePath()
Definition: Sensor.cpp:254
FString GetSensorName() const
Definition: Sensor.h:96
void SetSimulateSensor(bool SimulateSensor)
Definition: Sensor.h:160
UTopic * GetROSTopic() const
Definition: Sensor.h:150
ULogFile * LogFile
Definition: Sensor.h:367
bool SendDataToROS
Definition: Sensor.h:364
static TArray< TWeakObjectPtr< UPrimitiveComponent > > GetComponentsToHide()
Definition: Sensor.h:270
FString CreateTimeStampString() const
Definition: Sensor.cpp:330
FString FileSavePath
Definition: Sensor.h:372
void WriteToLogFile(const FString &Message)
Definition: Sensor.cpp:302
FORCEINLINE bool IsROSConnected() const
Definition: Sensor.h:201
virtual void CreateROSTopic()
Definition: Sensor.cpp:197
static void RemoveTick(FTickEntry TickEntry)
Definition: TickManager.cpp:80
static FTickEntry AddTick(UObject *Object, std::function< void(float)> Function, ETickType Type)
Definition: TickManager.cpp:51
bool IsVehicleInGarage() const
Definition: Vehicle.h:133
static void Log(const FString &Message, bool LogToTextFile=true, bool LogToROS=true)
static bool ShouldStartWindowMinimized()
virtual void Setup(ACamera *CameraRef, bool ShowOnStartup=true)
static FGeographicCoordinates UnrealToGeographicCoordinates(AGeoReferencingSystem *GeoReferencingSystem, const FVector &Position)
void Create(const FString &FileNameWithoutExtension, FLogFileSettings Settings)
Definition: LogFile.cpp:40
bool KeepFileOpen
Definition: LogFile.h:42
bool Timestamp
Definition: LogFile.h:39
FString FilePath
Definition: LogFile.h:54
FFileWriteOptions FileWriteOptions
Definition: LogFile.h:45
int32 QueueLength
Definition: LogFile.h:48
bool OverrideFilePath
Definition: LogFile.h:51
FFileCreationOptions FileCreationOptions
Definition: LogFile.h:36