/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #include "itkOpenCVVideoIO.h" namespace itk { OpenCVVideoIO::OpenCVVideoIO() { this->ResetMembers(); } OpenCVVideoIO::~OpenCVVideoIO() { this->FinishReadingOrWriting(); } void OpenCVVideoIO::FinishReadingOrWriting() { if (this->m_Writer != nullptr) { cvReleaseVideoWriter(&(this->m_Writer)); } if (this->m_Capture != nullptr) { cvReleaseCapture(&(this->m_Capture)); } if (this->m_CVImage != nullptr) { cvReleaseImage(&(this->m_CVImage)); } this->ResetMembers(); } OpenCVVideoIO::TemporalOffsetType OpenCVVideoIO::GetPositionInMSec() const { return this->m_PositionInMSec; } OpenCVVideoIO::TemporalRatioType OpenCVVideoIO::GetRatio() const { return this->m_Ratio; } OpenCVVideoIO::FrameOffsetType OpenCVVideoIO::GetFrameTotal() const { return this->m_FrameTotal; } OpenCVVideoIO::TemporalRatioType OpenCVVideoIO::GetFramesPerSecond() const { return this->m_FramesPerSecond; } OpenCVVideoIO::FrameOffsetType OpenCVVideoIO::GetCurrentFrame() const { return this->m_CurrentFrame; } OpenCVVideoIO::FrameOffsetType OpenCVVideoIO::GetIFrameInterval() const { return this->m_IFrameInterval; } OpenCVVideoIO::FrameOffsetType OpenCVVideoIO::GetLastIFrame() const { return this->m_LastIFrame; } void OpenCVVideoIO::SetCameraIndex(CameraIDType idx) { this->m_CameraIndex = idx; } OpenCVVideoIO::CameraIDType OpenCVVideoIO::GetCameraIndex() const { return this->m_CameraIndex; } void OpenCVVideoIO::SetReadFromFile() { if (!this->m_ReaderOpen && !this->m_WriterOpen) { this->m_ReadFrom = ReadFromEnum::ReadFromFile; } else { itkExceptionMacro("Cannot change read type while reader is open"); } } void OpenCVVideoIO::SetReadFromCamera() { if (!this->m_ReaderOpen && !this->m_WriterOpen) { this->m_ReadFrom = ReadFromEnum::ReadFromCamera; } else { itkExceptionMacro("Cannot change read type while reader is open"); } } bool OpenCVVideoIO::CanReadFile(const char * filename) { // Make sure filename is specified std::string fname = filename; if (fname == "") { itkDebugMacro("NoFilename specified"); return false; } // Check File Extension (container type) // // Note: For now we only allow avi format, but this isn't right. We need to // support all formats that OpenCV does (which I believe is some subset // of all the formats that ffmpeg supports) bool extensionFound = false; std::string::size_type extPos = fname.rfind(".avi"); if (extPos != std::string::npos && extPos == fname.length() - 4) { extensionFound = true; } extPos = fname.rfind(".AVI"); if (extPos != std::string::npos && extPos == fname.length() - 4) { extensionFound = true; } if (!extensionFound) { itkDebugMacro("Unrecognized file extension"); return false; } // Try opening to read CvCapture * localCapture = cvCaptureFromFile(filename); if (!localCapture) { return false; } // Close the file and return true if successful cvReleaseCapture(&localCapture); return true; } bool OpenCVVideoIO::CanReadCamera(CameraIDType cameraID) const { // Try capture from current camera index CvCapture * localCapture = cvCaptureFromCAM(cameraID); if (!localCapture) { return false; } // Close the file and return true if successful cvReleaseCapture(&localCapture); return true; } void OpenCVVideoIO::ReadImageInformation() { // Set up a local capture and image CvCapture * localCapture; IplImage * tempImage; // Open capture from a file if (this->m_ReadFrom == ReadFromEnum::ReadFromFile) { // Make sure file can be read std::string filename = this->GetFileName(); if (!this->CanReadFile(filename.c_str())) { itkExceptionMacro("Cannot read file: " << filename); } // Open the video file localCapture = cvCaptureFromFile(filename.c_str()); // Query the frame and get frame total (since this is only valid for reading from files) tempImage = cvQueryFrame(localCapture); this->m_FrameTotal = static_cast(cvGetCaptureProperty(localCapture, CV_CAP_PROP_FRAME_COUNT)); // Try to figure out if there are I-Frame issues we need to worry about // and compensate accrodingly if (this->m_FrameTotal > 0) { // Try setting frame to 1 and see what actually gets set cvSetCaptureProperty(localCapture, CV_CAP_PROP_POS_FRAMES, 1); tempImage = cvQueryFrame(localCapture); this->m_IFrameInterval = cvGetCaptureProperty(localCapture, CV_CAP_PROP_POS_FRAMES); if (this->m_IFrameInterval == 0) { itkExceptionMacro(" I-Frame spacing for this video is zero! Please check input data."); } this->m_LastIFrame = (OpenCVVideoIO::FrameOffsetType)(static_cast(this->m_FrameTotal) / static_cast(this->m_IFrameInterval)) * this->m_IFrameInterval - 1; // Frame index should be 0-based index // If the I-Frame spacing is not 1, warn the user if (this->m_IFrameInterval != 1) { itkWarningMacro("OpenCV can only seek to I-Frames. I-Frame spacing for this video is " << this->m_IFrameInterval << ". Last I-Frame is " << this->m_LastIFrame); } } } // Open capture from a camera else if (this->m_ReadFrom == ReadFromEnum::ReadFromCamera) { // Open the camera capture localCapture = cvCaptureFromCAM(this->m_CameraIndex); // Make sure it opened right if (!localCapture) { itkExceptionMacro("Cannot read from camera " << this->m_CameraIndex); } // Query the frame tempImage = cvQueryFrame(localCapture); // Make sure the image is not empty if (!tempImage) { cvReleaseCapture(&localCapture); itkExceptionMacro("Empty image got from camera " << this->m_CameraIndex); } // Set the frame total to 1 this->m_FrameTotal = 1; } // Should never get here else { itkExceptionMacro("Invalid Read Type... How did we get here?"); } // Populate member variables this->m_FramesPerSecond = static_cast(cvGetCaptureProperty(localCapture, CV_CAP_PROP_FPS)); // Set width, height this->m_Dimensions.clear(); this->m_Dimensions.push_back(cvGetCaptureProperty(localCapture, CV_CAP_PROP_FRAME_WIDTH)); this->m_Dimensions.push_back(cvGetCaptureProperty(localCapture, CV_CAP_PROP_FRAME_HEIGHT)); this->m_NumberOfComponents = tempImage->nChannels; // Set the pixel type if (this->m_NumberOfComponents == 1) { this->m_PixelType = IOPixelEnum::SCALAR; } else if (this->m_NumberOfComponents == 3) { this->m_PixelType = IOPixelEnum::RGB; } else if (this->m_NumberOfComponents == 4) { this->m_PixelType = IOPixelEnum::RGBA; } else { itkExceptionMacro("OpenCV IO only supports Mono, RGB, and RGBA input"); } // Release the local capture and image cvReleaseCapture(&localCapture); } void OpenCVVideoIO::Read(void * buffer) { // Make sure we've already called ReadImageInformation (dimensions are non-zero) if (this->m_Dimensions[0] == 0 || this->m_Dimensions[1] == 0) { itkExceptionMacro("Cannot read frame with zero dimension. May need to call ReadImageInformation"); } // If video is not already open, open it and keep it open if (!this->m_ReaderOpen) { this->OpenReader(); } // Read the desired frame // // Note: This will advance to the next frame in the stream, so SetNextFrameToRead // doesn't need to be called before Read is called again unless you want to // skip to a different location. Be warned, though. SetNextFrameToRead can // only skip to I-Frames, so there can be unexpected behavior IplImage * tempIm = cvQueryFrame(this->m_Capture); if (tempIm == nullptr) { itkExceptionMacro("Error reading frame " << this->m_CurrentFrame << ". May be out of bounds"); } // Convert to RGB rather than BGR if (this->m_CVImage == nullptr) { this->m_CVImage = cvCreateImage(cvSize(this->m_Dimensions[0], this->m_Dimensions[1]), IPL_DEPTH_8U, this->m_NumberOfComponents); } cvCvtColor(tempIm, this->m_CVImage, CV_BGR2RGB); // Update the frame-dependent properties this->UpdateReaderProperties(); // Put the frame's buffer into the supplied output buffer void * tempBuffer = reinterpret_cast(this->m_CVImage->imageData); size_t bufferSize = this->m_CVImage->imageSize; memcpy(buffer, tempBuffer, bufferSize); } bool OpenCVVideoIO::SetNextFrameToRead(OpenCVVideoIO::FrameOffsetType frameNumber) { // If the capture isn't open, open it if (!this->m_ReaderOpen) { this->OpenReader(); } // Make sure we're not setting past the end if (frameNumber > this->m_LastIFrame) { itkDebugMacro("Warning: Trying to seek past end of video (past last I-Frame)"); return false; } if (this->m_Capture != nullptr) { cvSetCaptureProperty(this->m_Capture, CV_CAP_PROP_POS_FRAMES, frameNumber); this->UpdateReaderProperties(); this->Modified(); return true; } return false; } bool OpenCVVideoIO::CanWriteFile(const char * filename) { // Make sure reader is closed if (this->m_ReaderOpen) { itkWarningMacro("Can't write anything if reader is open"); return false; } // Make sure filename is specified std::string fname = filename; if (fname == "") { itkWarningMacro("No Filename specified"); return false; } // Check File Extension (container type) // // Note: For now we only allow avi format, but this isn't right. We need to // support all formats that OpenCV does (which I believe is some subset // of all the formats that ffmpeg supports) bool extensionFound = false; std::string::size_type extPos = fname.rfind(".avi"); if (extPos != std::string::npos && extPos == fname.length() - 4) { extensionFound = true; } extPos = fname.rfind(".AVI"); if (extPos != std::string::npos && extPos == fname.length() - 4) { extensionFound = true; } if (!extensionFound) { itkWarningMacro("Unrecognized file extension " << fname); return false; } return true; } void OpenCVVideoIO::WriteImageInformation() { // Don't do anything } void OpenCVVideoIO::SetWriterParameters(TemporalRatioType fps, const std::vector & dim, const char * fourCC, unsigned int nChannels, IOComponentEnum componentType) { if (this->m_ReaderOpen || this->m_WriterOpen) { itkExceptionMacro("Can not set the writer's parameters when either reader or writer is already open"); } // Make sure componentType is acceptable (right now we only support char) if (componentType != IOComponentEnum::UCHAR) { itkExceptionMacro("OpenCV IO only supports writing video with pixels of UCHAR"); } if (dim.size() != 2) { itkExceptionMacro("OpenCV IO only supports 2D video"); } this->m_Dimensions.clear(); this->m_Dimensions.push_back(dim[0]); this->m_Dimensions.push_back(dim[1]); this->m_FramesPerSecond = fps; this->m_FourCC = CV_FOURCC(fourCC[0], fourCC[1], fourCC[2], fourCC[3]); this->m_NumberOfComponents = nChannels; if (nChannels == 1) { this->m_PixelType = IOPixelEnum::SCALAR; } else if (nChannels == 3) { this->m_PixelType = IOPixelEnum::RGB; } else if (nChannels == 4) { this->m_PixelType = IOPixelEnum::RGBA; } else { itkExceptionMacro("OpenCV IO only supports Mono, RGB, and RGBA output"); } } void OpenCVVideoIO::Write(const void * buffer) { // Make sure parameters are specified if (this->m_FramesPerSecond == 0 || this->m_Dimensions.size() == 0 || this->m_FourCC == 0) { itkExceptionMacro("Can not write with empty parameters. You probably need to call SetWriterParameters"); } // Make sure the number of channels is 1 or 3 if (this->m_NumberOfComponents != 1 && this->m_NumberOfComponents != 3) { itkExceptionMacro( "OpenCV only supports 1 and 3 component images. NumberOfComponents = " << this->m_NumberOfComponents); } // If the writer isn't open yet, open it if (!this->m_WriterOpen) { this->OpenWriter(); } // Place the contents of the buffer into an OpenCV image if (this->m_CVImage == nullptr) { // The output image always has to be 3 components for the OpenCV writer this->m_CVImage = cvCreateImage(cvSize(this->m_Dimensions[0], this->m_Dimensions[1]), IPL_DEPTH_8U, 3); } if (this->m_TempImage == nullptr) { this->m_TempImage = cvCreateImage(cvSize(this->m_Dimensions[0], this->m_Dimensions[1]), IPL_DEPTH_8U, this->m_NumberOfComponents); } // Insert the buffer into TempImage cvSetData(this->m_TempImage, reinterpret_cast(const_cast(buffer)), this->m_TempImage->widthStep); // Handle grayscale if (this->m_NumberOfComponents == 1) { cvCvtColor(this->m_TempImage, this->m_CVImage, CV_GRAY2BGR); } // Guaranteed to be 3 channels else { cvCvtColor(this->m_TempImage, this->m_CVImage, CV_RGB2BGR); } // Write the frame cvWriteFrame(this->m_Writer, this->m_CVImage); // Update the current frame this->m_CurrentFrame++; } void OpenCVVideoIO::UpdateReaderProperties() { // 0-based index of the frame tobe decoded/captured next this->m_CurrentFrame = cvGetCaptureProperty(this->m_Capture, CV_CAP_PROP_POS_FRAMES); this->m_PositionInMSec = cvGetCaptureProperty(this->m_Capture, CV_CAP_PROP_POS_MSEC); this->m_FramesPerSecond = static_cast(cvGetCaptureProperty(this->m_Capture, CV_CAP_PROP_FPS)); this->m_Ratio = cvGetCaptureProperty(this->m_Capture, CV_CAP_PROP_POS_AVI_RATIO); this->m_FourCC = cvGetCaptureProperty(this->m_Capture, CV_CAP_PROP_FOURCC); } void OpenCVVideoIO::OpenReader() { if (this->m_ReaderOpen) { itkExceptionMacro("Can not open reader while video is already open for reading"); } if (this->m_WriterOpen) { itkExceptionMacro("Can not open reader while video is already open for writing"); } // If neither reader nor writer is currently open, open the reader if (this->m_ReadFrom == ReadFromEnum::ReadFromFile) { this->m_Capture = cvCaptureFromFile(this->GetFileName()); if (this->m_Capture != nullptr) { this->m_ReaderOpen = true; } else { itkExceptionMacro("Video failed to open"); } } else if (this->m_ReadFrom == ReadFromEnum::ReadFromCamera) { this->m_Capture = cvCaptureFromCAM(this->m_CameraIndex); if (this->m_Capture != nullptr) { this->m_ReaderOpen = true; } else { itkExceptionMacro("Video failed to open"); } } } void OpenCVVideoIO::OpenWriter() { if (this->m_WriterOpen) { itkExceptionMacro("Can not open writer while video is already open for writing"); } if (this->m_ReaderOpen) { itkExceptionMacro("Can not open writer while video is already open for reading"); } // If neither reader nor writer is currently open, open the writer this->m_Writer = cvCreateVideoWriter( this->GetFileName(), this->m_FourCC, this->m_FramesPerSecond, cvSize(this->m_Dimensions[0], this->m_Dimensions[1])); this->m_WriterOpen = true; } void OpenCVVideoIO::ResetMembers() { this->m_CVImage = 0; this->m_TempImage = 0; this->m_Capture = 0; this->m_Writer = 0; this->m_WriterOpen = false; this->m_ReaderOpen = false; this->m_FramesPerSecond = 0; this->m_Dimensions.clear(); this->m_FourCC = 0; this->m_FrameTotal = 0; this->m_CurrentFrame = 0; this->m_NumberOfComponents = 0; this->m_IFrameInterval = 0; this->m_LastIFrame = 0; // Default to reading from a file this->m_ReadFrom = ReadFromEnum::ReadFromFile; this->m_CameraIndex = 0; // Members from ImageIOBase this->m_PixelType = IOPixelEnum::SCALAR; this->m_ComponentType = IOComponentEnum::UCHAR; this->SetNumberOfDimensions(2); this->m_Spacing[0] = 1.0; this->m_Spacing[1] = 1.0; this->m_Origin[0] = 0.0; this->m_Origin[1] = 0.0; } void OpenCVVideoIO::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); if (this->m_CVImage != nullptr) { os << indent << "Image dimensions : [" << this->m_CVImage->width << ',' << this->m_CVImage->height << ']' << std::endl; os << indent << "Origin : " << this->m_CVImage->origin << std::endl; os << indent << "Image spacing (in bits) : " << this->m_CVImage->depth << std::endl; os << indent << "Image Size : " << this->m_CVImage->imageSize << std::endl; os << indent << "Color model : " << this->m_CVImage->colorModel << " (" << this->m_NumberOfComponents << " channels)" << std::endl; } } } // end namespace itk