|
Basler ace GigE 카메라와 OpenCV 2.4.11을 이용하여 영상을 캡쳐하는 프로그램을 작성해 보자.
GigE 카메라 USB 3.0 카메라 CameraLink 카메라
다음 그림은 USB 3.0 케이블이다. USB 안쪽이 파란색으로 구분을 한다. USB 2.0은 검은색이다.
* Pylon 라이브러리 다운로드 및 설치
- Pylon Lib 다운로드 사이트: https://www2.baslerweb.com/en/downloads/software-downloads/
- 직접 다운로드 (2023.11.02): pylon 7.4.0 Camera Software Suite Windows
- 설치시 Developer와 USB를 선택한다. (개발자 모드로 설치해야 라이브러리를 사용 가능하다.)
위 라이브러리를 설치하여 PylonView로 카메라가 동작을 하는지 확인해야 한다.
바슬러카메라 영상이 한쪽으로 치우쳤을 경우 다음과 같이 Pylon 설정부분에서 OffsetX, OffsetY를 조절한다.
* Basler 카메라 (Thread 사용) 를 MFC (Visual Studio 2022)로 직접 작성해보자.
단계 | 코드 작성 내용 |
Visual Studio 2022 | 대화상자 (Dialog base)로 프로젝트 생성한다. 카메라 화면: Picture Control, IDC_CAM_VIEW, 변수추가: m_CamView 시작버튼: Cam Start, IDC_CAM_START 정지버튼: Cam Stop, IDC_CAM_STOP |
OpenCV 설치 | 참고: https://cafe.daum.net/smhan/cczU/61 |
Pylon 7 Lib 설치 프로젝트 속성 설정 | Pylon7 Lib 다운로드: pylon 7.4.0 Camera Software Suite Windows Opencv 480 다운로드: opencv-4.8.0-windows.exe 구성속성->VC++ 디렉터리->포함 디렉터리 $(PYLON_DEV_DIR)\include C:\opencv480\build\include 구성속성->VC++ 디렉터리->라이브러리 디렉터리 $(PYLON_DEV_DIR)\lib\x64 C:\opencv480\build\x64\vc16\lib 구성속성->디버깅->환경 PATH=%PATH%;C:\opencv480\build\x64\vc16\bin; 구성속성->링커->입력->추가 종속성 opencv_world480d.lib |
baslerCam2023Dlg.h 함수 및 변수 선언 | #include <opencv2/opencv.hpp> #include <pylon/PylonIncludes.h> using namespace Pylon; using namespace GenApi; using namespace cv; UINT ThreadImageCaptureFunc(LPVOID param); // 쓰레드 함수 void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin); void DisplayImage(CDC* pDC, CRect rect, Mat& srcimg); class CbaslerCam2023Dlg : public CDialogEx { public: BOOL m_bThreadFlag; // 쓰레드 루프 돌기 CInstantCamera *m_pCamera; // Basler Camera CGrabResultPtr m_ptrGrabResult; // grab result data 받기 Mat m_Image; // 추가하기 ... } |
Pylon 초기화 | using namespace std; BOOL CbaslerCam2023Dlg::OnInitDialog() { .... // TODO: 여기에 추가 초기화 작업을 추가합니다. PylonInitialize(); // Get the transport layer factory. CTlFactory& tlFactory = CTlFactory::GetInstance(); // Get all attached devices and exit application if no device is found. DeviceInfoList_t devices; if (tlFactory.EnumerateDevices(devices) == 0) { MessageBox(L"Basler Camera가 연결되지 않았습니다."); } else { // create device //IPylonDevice *pDevice = TlFactory.CreateDevice(lstDevices[0]); // Create an instant camera object with the camera device found first. m_pCamera = new CInstantCamera(CTlFactory::GetInstance().CreateFirstDevice()); try { // 카메라 파리미터 설정하기 m_pCamera->Open(); INodeMap& nodemap = m_pCamera->GetNodeMap(); // Get the integer nodes describing the AOI. CIntegerPtr offsetX(nodemap.GetNode("OffsetX")); CIntegerPtr offsetY(nodemap.GetNode("OffsetY")); CIntegerPtr width(nodemap.GetNode("Width")); CIntegerPtr height(nodemap.GetNode("Height")); // GenApi has some convenience predicates to check this easily. int new_width = 1024; int new_height = 624; // 768시 error 발생 if (IsWritable(width)) width->SetValue(new_width); if (IsWritable(height)) height->SetValue(new_height); if (IsWritable(offsetX)) offsetX->SetValue(new_width / 2); if (IsWritable(offsetY)) offsetY->SetValue(new_height / 2); // 카메라 정보 출력 CString info; info.Format(_T("Found camera!\n<%s>\nSize X: %d\nSize Y: %d"), CString(m_pCamera->GetDeviceInfo().GetModelName()), width->GetValue(), height->GetValue()); AfxMessageBox(info); m_pCamera->Close(); } catch (GenICam::GenericException& e) { // Error handling CString strTrace; strTrace.Format(_T("Open_Camera-Generic Exception : %s\n"), (CString)e.GetDescription()); AfxMessageBox(strTrace); return FALSE; } } return TRUE; // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다. } |
baslerCam2023Dlg.cpp 맨 밑에 쓰레드함수 추가 | UINT ThreadImageCaptureFunc(LPVOID param) { CbaslerCam2023Dlg* pDlg = (CbaslerCam2023Dlg*)param; if (pDlg->m_pCamera == NULL) return 0; while (pDlg->m_bThreadFlag) { try { if (pDlg->m_pCamera->IsGrabbing()) { // Wait for an image and then retrieve it. A timeout of 5000 ms is used. pDlg->m_pCamera->RetrieveResult(5000, pDlg->m_ptrGrabResult, TimeoutHandling_ThrowException); if (pDlg->m_ptrGrabResult->GrabSucceeded()) { // Color로 포맷을 변경할 경우 //CImageFormatConverter fc; //fc.OutputPixelFormat = PixelType_RGB8packed; //CPylonImage pylonImage; //fc.Convert(pylonImage, pDlg->m_ptrGrabResult); //Mat camImage = Mat(pDlg->m_ptrGrabResult->GetHeight(), // pDlg->m_ptrGrabResult->GetWidth(), CV_8UC3, // (uint8_t*)pylonImage.GetBuffer()); // 카메라 이미지를 화면에 출력 pDlg->m_Image = Mat(pDlg->m_ptrGrabResult->GetHeight(), pDlg->m_ptrGrabResult->GetWidth(), CV_8UC1, (uint8_t*) pDlg->m_ptrGrabResult->GetBuffer()); CRect rect; CDC* pDC = pDlg->m_CamView.GetDC(); // 출력한 부분의 DC 얻기 pDlg->m_CamView.GetClientRect(rect); // 출력할 영역 얻기 DisplayImage(pDC, rect, pDlg->m_Image ); // 화면에 그리기 pDlg->ReleaseDC(pDC); } else { CString str; str.Format(_T("Error: %d %d") , pDlg->m_ptrGrabResult->GetErrorCode(), pDlg->m_ptrGrabResult->GetErrorDescription()); AfxMessageBox(str); pDlg->m_bThreadFlag = false; } } } catch (GenICam::GenericException& e) { // Error handling. cerr << "An exception occurred." << e.GetDescription() << endl; } } return 0; } 다음은 화면 출력 함수로 카페에서 참고하여 복사한다. (https://cafe.daum.net/smhan/darS/2) void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin) { .... } void DisplayImage( CDC* pDC, CRect rect, Mat& srcimg) { .... } |
카메라 시작 버튼 | void CbaslerCam2023Dlg::OnBnClickedCamStart() { if (m_pCamera == NULL) { MessageBox(L"Basler Camera를 연결 후 다시 실행시켜주세요."); return; } m_pCamera->StartGrabbing(); m_bThreadFlag = TRUE; CWinThread *pThread = ::AfxBeginThread(ThreadImageCaptureFunc, this); } |
카메라 정지 버튼 | void CbaslerCam2023Dlg::OnBnClickedCamStop() { if (m_pCamera == NULL) { MessageBox(L"Basler Camera를 연결 후 다시 실행시켜주세요."); return; } m_pCamera->StopGrabbing(); m_bThreadFlag = FALSE; // 쓰레드 정지 시킴 } |
basler 종료시 메모리 삭제 WM_DESTROY | void CbaslerCam2023Dlg::OnDestroy() { CDialogEx::OnDestroy(); PylonTerminate(); // Releases all pylon resources. m_pCamera->StopGrabbing(); m_bThreadFlag = FALSE; if (m_pCamera != NULL) delete m_pCamera; } |
영상 불러오기 | // 불러올때는 Cam 을 멈춘상태에서 불러와야 지워지지 않는다. void CbaslerCam2023Dlg::OnBnClickedBmpLoad() { wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(TRUE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { CRect rect; CStringA filename(dlg.GetPathName()); m_Image = cvLoadImage(filename); // Invalidate(FALSE); CRect rect; CDC* pDC = m_CamView.GetDC(); // 출력한 부분의 DC 얻기 m_CamView.GetClientRect(rect); // 출력할 영역 얻기 DisplayImage(pDC, rect, m_Image); // 화면에 그리기 ReleaseDC(pDC); } } |
영상 저장하기 | // 현재 캡쳐한 영상을 파일로 저장한다. // Mat m_Image를 *.h 클래스에 선언한다. void CbaslerCam2023Dlg::OnBnClickedBmpSave() { wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(FALSE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { cvFlip(m_Image, m_Image, 0); // 영상을 위/아래로 뒤집는다. CStringA filename(dlg.GetPathName()); cvSaveImage(filename, m_Image); } } |
* GigE 카메라와 OpenCV 2.4.11로 작성한 프로그램 소스파일들
샘플 소스: (Timer 사용)
샘플 소스: (Thread 사용, OpenCV 2.4.13)
(떨림 현상을 제거하려면, thread()함수에서 Invalidate()함수를 제거하고 직접 영상을 출력합니다.)
Basler USB3.0 카메라와 Opencv 2.4.11을 이용해서 테스트를 해보았다.
샘플 Mono 소스:
샘플 Color 소스:
|