Случайное падение FPS каждый раз, когда камера завершает инициализацию в OpenCVSharpC#

Место общения программистов C#
Ответить
Anonymous
 Случайное падение FPS каждый раз, когда камера завершает инициализацию в OpenCVSharp

Сообщение Anonymous »

Мое приложение — настольное приложение Winforms, использующее OpenCV для записи и хранения видеофайлов. В настоящее время я использую
VideoCapture videoCapture = VideoCapture.FromCamera(index: cameraIndex, apiPreference: VideoCaptureAPIs.DSHOW);

И по большей части все работало нормально. Но примерно несколько дней назад мой клиент сообщил, что иногда после загрузки приложения и завершения процесса инициализации камеры FPS падал до 1–5 кадров в секунду и выглядел очень нестабильно, если не перезапустить приложение. Я могу воспроизвести с помощью многократно переинициализированной VideoCapture, и это произошло случайно. Так что я потратил два полных дня, методом проб и ошибок, пытаясь их выследить. Но не повезло.
Хотя я обнаружил, что если я изменю свои apiPreferences на VideoCaptureAPIs.MSMF, проблема в основном будет решена. Плюс прирост производительности очень заметен. Но это приводит к другой проблеме, похожей на эту проблему. Так что теперь у меня действительно закончились варианты. Я также не совсем уверен, что проблема связана с API, и, возможно, с моей стороны это небрежный код. Но здесь я, по сути, захожу в настоящий тупик.
Попробую воспроизвести на примере. А пока, если можете, взгляните на мой фрагмент. Возможно, вы знаете что-то, чего я не знаю. Спасибо!
Создайте VideoCapture в OpenCVHelper. Я использую DsDevice для получения индекса камеры
public VideoCapture CreateVideoCaptureFromCamera(int cameraIndex, int width, int height, double inputFPS, int IsAutoFocus)
{
try
{
VideoCapture videoCapture = VideoCapture.FromCamera(index: cameraIndex, apiPreference: VideoCaptureAPIs.DSHOW);
videoCapture.FrameHeight = height;
videoCapture.FrameWidth = width;
videoCapture.Fps = inputFPS;

if (IsAutoFocus == (int)AutoFocusStatus.On)
{
videoCapture.AutoFocus = true;
}
else
{
videoCapture.AutoFocus = false;
videoCapture.Focus = 100;
}
videoCapture.Zoom = 0;
videoCapture.FourCC = "MJPG";
return videoCapture;
}
catch
{
throw;
}
}

Теперь на уровне Presenter. Создайте захват видео и используйте многопоточность.
videoCapturePresenter = recorderMainAppManager.CreateVideoCapture(
index: camera.Index,
resolution: resolution, //resolutions[resolutions.Count - 1],
fps: ProfilePresenter.CameraFps,
focus: ProfilePresenter.IsAutoFocus ?? (int)AutoFocusStatus.Off
);
captureTokenSource = new();
_captureThread = new Thread(() => CaptureFrame(videoCapturePresenter, captureTokenSource))
{
Name = "Capture Thread",
Priority = ThreadPriority.AboveNormal, ///This is not the cause because i added after i can reproduced the issues.
};
_captureThread.Start();

Это CaptureFrame. Я использовал Cv2.Resize, чтобы изменить размер кадра до размера PictureBox, прежде чем перенести его на уровень пользовательского интерфейса. Без этого PictureBox попытается изменить размер каждого кадра самостоятельно и занять половину доступной оперативной памяти.
private void CaptureFrame(VideoCapture capture, CancellationTokenSource captureToken)
{

string newOverlayText = string.Empty;
try
{
ShipmentOverlay shipmentOverlay = recorderMainAppManager.GetShipmentOverlay
(
resolution: $"{capture.FrameWidth}x{capture.FrameHeight}"
);

string username = $"Employee Name : {UserPresenter.Username}";
while (!captureToken.IsCancellationRequested)
{
string datetime = DateTimeOffset.Now.ToString("dd/MM/yyyy HH:mm:ss");
string overlayText = createNewOverlayString();
using (Mat rawFrame = new())
{
newOverlayText = $"Date : {datetime} | {username} | {overlayText}";
if (!capture.Read(rawFrame))
{
Console.WriteLine("Error: Failed to read frame.");
break;
}

if (rawFrame.Empty())
{
Console.WriteLine("Error: Empty frame.");
break;
}

using (Mat frameWithOverlay = recorderMainAppManager.DrawOverlay(frame: rawFrame, shipmentOverlay: shipmentOverlay, overlayText: newOverlayText))
{
Mat clonedFrame = frameWithOverlay.Clone();
{
lock (clonedFrame)
{
var currentPictureBox = recorderMainAppView.GetPicRecordResolution();
var targetSize = new OpenCvSharp.Size() { Width = currentPictureBox.Width, Height = currentPictureBox.Height };
if (targetSize.Width > 0 && targetSize.Height > 0)
{
Mat captureMat = new();
Cv2.Resize(clonedFrame, captureMat, targetSize, interpolation: InterpolationFlags.Area);
using (Bitmap _bitmap = BitmapConverter.ToBitmap(captureMat))
{
Bitmap clonedResizeFrame = (Bitmap)_bitmap.Clone();
recorderMainAppView.UpdateCameraView(clonedResizeFrame);
}

if (_isRecording == RecordStatus.RECORD)
{
_frameQueue.Enqueue(clonedFrame);
}
else
{
captureMat.Dispose();
}
}
}
}
}
}
_threadStopEvent.Wait(1);
if (captureToken.IsCancellationRequested) break;
}
}
catch
{
throw;
}
}

Это уровень пользовательского интерфейса. Когда моя камера начнет создавать кадр, он будет отправлен в PictureBox в качестве предварительного просмотра видео для моего пользователя.
public void UpdateCameraView(Bitmap img)
{
try
{
if (picRecord.InvokeRequired &&
WindowState != FormWindowState.Minimized)
{
using (picRecord.Image)
{
Invoke(() => picRecord.Image = img);
}
}
}
catch (ObjectDisposedException)
{
// This function throw everytime in debug mode, apparently because some video frame aren't being disposed yet
// But frmMain is already disposed. Happened only when i exit the app.
// For now, do nothing. But if you know how to solve this. Feel free.
return;
}
}

Изменить: здесь находится _frameQueue, как и было запрошено. Я не думал, что это действительно связано с вопросом, поскольку он находится в отдельном потоке и не запускается до тех пор, пока пользователь не введет его. Но проблема возникла еще до начала этого процесса.
Сначала создается другой поток. Это предназначено для преобразования каждого кадра в видеофайлы при вводе пользователем записи.
videoWriterPresenter = recorderMainAppManager.CreateVideoWriter(
capture: videoCapturePresenter,
savedDir: outputDirectory,
BaseLabel: data.FirstOrDefault(t => !string.IsNullOrEmpty(t)));
_threadStopEvent.Reset();
writerTokenSource = new();
_writerThread = new Thread(() => WriteFrame(writerTokenSource))
{
Name = "Writer Thread",
IsBackground = true
};
_writerThread.Start();

private void WriteFrame(CancellationTokenSource writerToken)
{
Stopwatch stopwatch = Stopwatch.StartNew();
double _delay = 1_000 / videoCapturePresenter!.Fps;
try
{
while (!_threadStopEvent.Wait(0) && !writerToken.IsCancellationRequested)
{
if (stopwatch.Elapsed.TotalMilliseconds

Подробнее здесь: https://stackoverflow.com/questions/791 ... pencvsharp
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»