Friday, December 29, 2017

opencv - set up extra module 設置opencv擴充函式庫

這邊想要紀錄設置超級麻煩的opencv擴充函式庫


---

step 01


首先先去下載

opencv-3.3.0 -  https://github.com/opencv/opencv
opencv_contrib-3.3.0 - https://github.com/opencv/opencv_contrib


(可以自己選擇想要的版本, 兩個版本要一致, 後面的3.3.0代表3.3.0版本)

記得點選 [branch] -> [Tag] 選版本

然後按右邊的綠色按鈕 [Clone or download] -> [Download ZIP] 下載



---

step 02


將兩個檔案都解壓縮之後

打開CMake設定上面的Where is the source code為剛剛opencv-3.3.0資料夾

 Where to build the binaries 設為自己想要裝的資料夾

然後按下 [Configure]


 ---

step 03



設定你的Visual Studio版本以及電腦位元數後按下 [Finish]



---

step 04



在OPENCV_EXTRA_MODULES_PATH 後面

選取 opencv_contrib-3.3.0下的modules資料夾 (要點選 [...]選取資料夾)

再按一次[Configure]



 ---

step 05


接下來它應該會冒出來包含aruco的一系列

再按一次 [Configure]



---

step06


確認沒有東西是紅色的之後按下 [Generate]



---

step 07



接下來會看到有 [Generating done] 的字樣

可以按下 [Open Project] 打開該 Project

或是去



---

step 08



接下來在 ALL_BUILD按下右鍵 [Build]




---

step 09


如果你跑出來的結果跟我一樣有大量error的話

很不幸的你要跟我一起進行 step 10

如果沒有的話可以直接跳到 step 12










---

step 10

請回到CMake中 把跟CUDA有關的東西都取消掉之後按下 [Generate]







---

step 11


接下來把原本的Visual Studio關掉

重新在CMake按下 [Open Project]

再將ALL_BUILD右鍵按下[Rebuild]




應該會如圖沒有Error了

這時應該將上方的Debug切成Release再做一次本步驟



(這樣 Debug 和 Release的函示庫才會都跑出來)




不放心的話可以去安裝的位置下 lib>Debug 及 lib>Release 資料夾確認




---

step 12

接下來在INSTALL右鍵按下[Rebuild]

Debug模式和Release模式都要做一遍





---

step 12


最後可以回到資料夾開始找檔案了~

dll檔在 install > x64 > vc14 > bin

lib檔在 install > x64 > vc14 > lib

hpp檔在原本下載好的 opencv_contrib-3.3.0\modules 裡

Tuesday, December 26, 2017

realsense - faild to detect black color on depth image 深度圖的黑色不見了


最近在做 Intel® RealSense™ Camera SR300 的實驗

原本預定要將取得的IR圖做矯正後與深度圖拿來做彩色3D投射

此為原圖


彩色圖


IR圖


深度圖(雖然是黑漆漆的, 但是確實是有東西的唷)

---

結果想說來看一下深度圖的情況

將深度圖轉成3D點之後就發現悲劇了

我的棋盤格黑色的部分居然都沒有數據 (讀成0)  =_=




以前在使用Kinect的時候沒有遇過這個問題

問了學長之後

응수學長說這是因為realsense採深度圖的方式跟Kinect略有不同

某些情況下, 黑色會因為反射不了IR光而讓它的IR感應器讀不到

好像也跟材質有關係


---

換了另一塊板子就沒事了



結果:

這次的板子就有整塊跑出來

















realsense - get started with realsense stream 開始使用RealSense SR300感應器

這裡是使用 Intel® RealSense™ Camera SR300 作實驗的初心者實驗

包含了讀取Realsens感應器的串流(彩色影像、深度影像、IR圖)

以及將擷取到的影像使用OpenCV的library顯示及儲存

--

#include <iostream>
#include <direct.h>
#include <string>
#include <time.h>
#include "opencv2\opencv.hpp"
#include "librealsense\pxcsensemanager.h"

cv::Mat PXCImage2CVMat(PXCImage *pxcImage, PXCImage::PixelFormat format)
{
 PXCImage::ImageData data;
 pxcImage->AcquireAccess(PXCImage::ACCESS_READ, format, &data);

 int width = pxcImage->QueryInfo().width;
 int height = pxcImage->QueryInfo().height;

 if (!format)
  format = pxcImage->QueryInfo().format;

 int type = 0;
 if (format == PXCImage::PIXEL_FORMAT_Y8)  //讀IR
  type = CV_8UC1;
 else if (format == PXCImage::PIXEL_FORMAT_RGB24) //讀彩色
  type = CV_8UC3;
 else if (format == PXCImage::PIXEL_FORMAT_DEPTH_F32) //比較容易看懂的深度圖(黑白黑白區隔)
  type = CV_32FC1;
 else if (format == PXCImage::PIXEL_FORMAT_DEPTH) //一般的深度圖
  type = CV_16UC1;
 else if (format == PXCImage::PIXEL_FORMAT_DEPTH_RAW) //千層麵深度圖
  type = CV_16UC1;

 cv::Mat ocvImage = cv::Mat(cv::Size(width, height), type, data.planes[0]);

 pxcImage->ReleaseAccess(&data);
 return ocvImage;
}

int main()
{
 PXCSession *session = PXCSession::CreateInstance();
 PXCSenseManager *sm = PXCSenseManager::CreateInstance();

 cv::Size frameSize = cv::Size(640, 480);
 float frameRate = 60;
 cv::Mat frameColor = cv::Mat::zeros(frameSize, CV_8UC3);
 cv::Mat frameDepth = cv::Mat::zeros(frameSize, CV_8UC1);
 cv::Mat frameIR = cv::Mat::zeros(frameSize, CV_8UC1);
 
 //啟動串流
 sm->EnableStream(PXCCapture::STREAM_TYPE_COLOR, frameSize.width, frameSize.height, frameRate);
 sm->EnableStream(PXCCapture::STREAM_TYPE_DEPTH, frameSize.width, frameSize.height, frameRate);
 sm->EnableStream(PXCCapture::STREAM_TYPE_IR, frameSize.width, frameSize.height, frameRate);
 sm->Init();

 //設定資料夾名稱
 std::string folderName = ".\\data";
 std::string colorFileName = folderName + "\\color";
 std::string depthFileName = folderName + "\\depth";
 std::string irFileName = folderName + "\\IR";

 //新增資料夾
 _mkdir(folderName.c_str());
 _mkdir((colorFileName).c_str());
 _mkdir((depthFileName).c_str());
 _mkdir((irFileName).c_str());

 clock_t startTime;
 startTime = clock();

 bool storeImg = false;

 for (;;)
 {
  if (sm->AcquireFrame(true) < PXC_STATUS_NO_ERROR)
  {
   std::cout << "No input Frame" << std::endl;
   break;
  }

  PXCCapture::Sample *sample;
  sample = sm->QuerySample();


  if (sample->color)
  {
   frameColor = PXCImage2CVMat(sample->color, PXCImage::PIXEL_FORMAT_RGB24);
  }

  if (sample->depth)
  {
   //frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH_F32);
   //frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH_RAW);
   frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH);
  }

  if (sample->ir)
  {
   frameIR = PXCImage2CVMat(sample->ir, PXCImage::PIXEL_FORMAT_Y8);
  }

  cv::imshow("color", frameColor);
  cv::imshow("Depth", frameDepth);
  cv::imshow("ir", frameIR);

  
  if (cv::waitKey(27) == 13) //按下Enter開始不斷儲存照片直到下次按下Enter為止
  {
   storeImg = !storeImg;
   if (storeImg)
   {
    std::cout << "START CAPTURE" << std::endl;
   }
   else
   {
    std::cout << "FINISHED CAPTURE" << std::endl;
   }
  }

  if (storeImg) //儲存圖片
  {
   clock_t nowsTime = clock() - startTime;
   std::cout << "Time : " << ((float)nowsTime) / CLOCKS_PER_SEC << "(sec)" << std::endl;
   float nowsTime_str = ((float)nowsTime) / CLOCKS_PER_SEC;

   cv::imwrite(colorFileName + "\\" + std::to_string(nowsTime_str) + ".png", frameColor);
   cv::imwrite(depthFileName + "\\" + std::to_string(nowsTime_str) + ".png", frameDepth);
   cv::imwrite(irFileName + "\\" + std::to_string(nowsTime_str) + ".png", frameIR);
  }
  
  sm->ReleaseFrame();
 }

 system("pause");
 return 0;
}

--
實驗結果

這張圖是run下去後會應該會跑出的畫面

從左至右分別是 彩色影像、IR圖、深度圖

深度圖因為本身就很難用肉眼辨識的關係會覺得一團黑

實際上圖是有些微變化的 ( 讀取圖像數值的話就知道了!)


--

如果旨在看得見或是有其他用途的話

可以把我中間程式碼註解掉的這段程式碼拿來用看看 (三選一的意思)



  frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH_F32);
  frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH_RAW);
  frameDepth = PXCImage2CVMat(sample->depth, PXCImage::PIXEL_FORMAT_DEPTH);

其中
 PIXEL_FORMAT_DEPTH_F32
 PIXEL_FORMAT_DEPTH_RAW
 PIXEL_FORMAT_DEPTH

是讀取深度圖的不同方法

我平時在做3D投射是需要使用最下面這個 PIXEL_FORMAT_DEPTH



下圖是三種方式的比較

右下角為IR圖 (因為IR圖和深度圖是可以相對應的, 所以放在一起比較)




Wednesday, December 13, 2017

sdl - error when include "sdl.h" 引入標頭檔時的錯誤訊息

在載好SDL2並想要引入"SDL2\SDL.h"時

跑出了如圖的錯誤訊息


---

要修正這個問題需要將下面這行程式碼一起引入

#include "SDL2\SDL.h"
#undef main



就可以解決這個問題了
---

Sunday, December 3, 2017

opencv - camera calibration 相機校正

---

這邊以相機校正的實作為主

就不談細節了

在做相機校正之前要先準備最好15張以上拍好的棋盤格圖片

如果不想自己照的朋友

可以先用我照好的,取其中15張左右就可以了



全部都使用也可以,只是運行時間當然地會花比較久

下載連結

而使用的照片越多,結果也會越精準

---

其中這個部分

#define n_corner_cols 8 //棋盤點行數
#define n_corner_rows 6 //棋盤點列數
#define corner_size 25  //棋盤格大小 (mm)


棋盤點的行數和列數是要看交界點的數量

而不是看棋盤的格子數

所以下面這張範例圖的棋盤行數和列數是8和6



---

程式碼:


#include "opencv2\opencv.hpp"

#include <windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <fstream>
#include <iostream>
#include <direct.h>

using namespace std;
using namespace cv;

#define n_corner_cols 8 //棋盤點行數
#define n_corner_rows 6 //棋盤點列數
#define corner_size 25  //棋盤格大小 (mm)


void fprintMatrix(Mat matrix, string name) //輸出矩陣
{
 FILE * fp;
 fp = fopen(name.c_str(), "w");
 int i, j;
 printf("%s size %d %d\n", name.c_str(), matrix.cols, matrix.rows);
 for (i = 0; i< matrix.rows; ++i)
 {
  for (j = 0; j< matrix.cols; ++j)
  {
   fprintf(fp, "%lf ", matrix.at<  double >(i, j));
  }
  fprintf(fp, "\n");
 }

 fclose(fp);
}

vector <string> getAllFilesName(string inputFolder) //取得資料夾內所有的檔案
{
 vector <string> allFileName;

 int n_files = 0;
 wstring inputFolder_wchar = wstring(inputFolder.begin(), inputFolder.end());
 const wchar_t* lookForFile = inputFolder_wchar.c_str();
 WIN32_FIND_DATA findFileFullName;

 HANDLE hFind;
 hFind = FindFirstFile(lookForFile, &findFileFullName);

 if (hFind != INVALID_HANDLE_VALUE) 
 {

  do
  {
   wstring ws(findFileFullName.cFileName); //利用wstring 將wchar型態的檔案名稱轉成string型態
   string findFileFullName_str(ws.begin(), ws.end());

   allFileName.push_back(findFileFullName_str);

   n_files++;
  } while (FindNextFile(hFind, &findFileFullName) == TRUE);
 }

 cout << endl << "Number of files: " << n_files << endl;

 return allFileName;
}

void calibration(string folder_colorImg, string folder_calibration)
{
 vector <string> fileNames;
 fileNames = getAllFilesName(folder_colorImg + "*.png"); //設定要找的資料為.png檔

 vector<vector<Point2f>> corner_all_2D;
 vector<vector<Point3f>> corner_all_3D;

 Size image_size = imread(folder_colorImg + fileNames[0], CV_LOAD_IMAGE_COLOR).size();

 for (int index_file = 0; index_file < fileNames.size(); index_file++)
 {
  cout << fileNames[index_file] << endl;

  //read files
  Mat img_gray = imread(folder_colorImg + fileNames[index_file], CV_LOAD_IMAGE_GRAYSCALE);
  Mat img_color = imread(folder_colorImg + fileNames[index_file], CV_LOAD_IMAGE_COLOR);
  imshow("img gray", img_gray);

  //find corners 
  vector <Point2f> corner_per_2D;
  vector <Point3f> corner_per_3D;

  bool isFindCorner = findChessboardCorners(img_gray, Size(n_corner_cols, n_corner_rows), corner_per_2D);
  if (isFindCorner && (corner_per_2D.size() == n_corner_rows*n_corner_cols))
  {

   //2D corners
   cv::TermCriteria param(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 30, 0.1);
   cornerSubPix(img_gray, corner_per_2D, cv::Size(5, 5), cv::Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

   drawChessboardCorners(img_color, Size(n_corner_cols, n_corner_rows), corner_per_2D, true);

   imshow("img color show", img_color);
   corner_all_2D.push_back(corner_per_2D);


   //3D corners
   for (int r = 0; r < n_corner_rows; r++)
   {
    for (int c = 0; c < n_corner_cols; c++)
    {
     corner_per_3D.push_back(Point3f(c*corner_size, r*corner_size, 0));
    }
   }
   corner_all_3D.push_back(corner_per_3D);

  }
  else
  {
   putText(img_color, "NO FOUND", Point(0, img_color.rows / 2), FONT_HERSHEY_DUPLEX, 3, Scalar(0, 0, 255));
   imshow("img color show", img_color);
  }
  //cv::waitKey(0); //把這個註解取消掉可以看單張找到棋盤格的狀況
 }

 //camera calibration
 vector< Mat> rvecs, tvecs;
 Mat intrinsic_Matrix(3, 3, CV_64F);
 Mat distortion_coeffs(8, 1, CV_64F);

 calibrateCamera(corner_all_3D, corner_all_2D, image_size, intrinsic_Matrix, distortion_coeffs, rvecs, tvecs);
 cout << "Finished IMAGE Camera calibration" << endl;

 //save part
 fprintMatrix(intrinsic_Matrix, folder_calibration + "intrinsic.txt");
 fprintMatrix(distortion_coeffs, folder_calibration + "distortion.txt");

}


int main()
{             
 string folder_colorImg = ".//data//color//";    //圖片的資料夾

 string folder_calibration = ".//calibration//"; //要放置相機矯正資料的資料夾
 _mkdir((folder_calibration).c_str());           //建立相機校正資料的資料夾

 calibration(folder_colorImg, folder_calibration);

 std::system("pause");
 return 0;
}

---

運行之後應該就可以在資料夾 calibration 內找到校正的矩陣資料了



---

以下是使用狀態比較不好的圖片所做的實驗

如圖找不到應有的棋盤點數的話會在圖片上顯示NO FOUND字樣

也不會將該圖片的棋盤點拿去做相機校正






opencv - mouse event 滑鼠事件


利用OpenCV的滑鼠事件(Mouse Event) 實作簡單的小畫家

---

程式碼:

#include <opencv2\opencv.hpp>

using namespace std;
using namespace cv;

Mat palette; //調色盤
Mat canvas;  //畫布
Scalar brushColor; //儲存畫筆的顏色

void renew_brushColor(int r, int g, int b)
{
 brushColor = Scalar(b, g, r); 
 Mat img_brushColor(150, 150, CV_8UC3, brushColor); //顯示現在畫筆顏色的視窗

 imshow("brush color", img_brushColor);
}

void mouseEvent_getColor(int event, int x, int y, int flag, void* param)
{
 if (event == CV_EVENT_LBUTTONDOWN)
 {
  int r = palette.at<Vec3b>(y, x)[2]; //紅色在第三通道
  int g = palette.at<Vec3b>(y, x)[1]; //綠色在第二通道
  int b = palette.at<Vec3b>(y, x)[0]; //藍色在第一通道

  cout << "x, y = (" << x << ", " << y << ")      ";
  cout << "r, g, b = (" << r << ", "<< g << ", " << b << ")" << endl;

  renew_brushColor(r, g, b);
 }
}

void mouseEvent_drawColor(int event, int x, int y, int flag, void* param)
{
 static bool flag_down = false;

 if (event == CV_EVENT_LBUTTONDOWN)
 {
  flag_down = true;
  
 }

 if (event == CV_EVENT_MOUSEMOVE && flag_down == true)
 {
  circle(canvas, Point(x, y), 3, brushColor, 2, 8, 0);
  imshow("canvas", canvas);

 }

 if (event == CV_EVENT_LBUTTONUP)
 {
  flag_down = false;

 }
}

int main()
{

 palette = imread("colour_palette.png"); //將色票圖檔讀進調色盤
 imshow("colour palette",palette);

 canvas = Mat(300, 300, CV_8UC3, Scalar(255, 255, 255)); //創建畫布
 imshow("canvas", canvas);


 setMouseCallback("colour palette", mouseEvent_getColor);
 setMouseCallback("canvas", mouseEvent_drawColor);

 waitKey(0);
 system("pause");
 return 0;
}

---

我所使用的調色盤檔案: 檔案連結




運行結果:





Saturday, December 2, 2017

opencv - capture image 從影像中截圖

實作打開視訊鏡頭之後

按下[Enter]截圖

---
程式碼:

#include "opencv2\opencv.hpp"

using namespace cv;
using namespace std;

int main()
{
 VideoCapture webcam(0);

 if (!webcam.isOpened())
 {
  cout << "Cannot open webcam" << endl;
  return 0;
 }

 Mat frame; //影像
 int countNumber = 0; //計算儲存的張數

 while (true)
 {
  webcam >> frame;

  imshow("webcam", frame);
  if (waitKey(20) == 13) //按下[ENTER]鍵儲存影像
  {
   cout << "capture" << endl;
   imwrite(to_string(countNumber)+".png", frame);
   countNumber++;
  }
 }
}



---
運行結果:

run下去之後應該就可以看到視訊鏡頭的畫面了

如果發生錯誤或是偵測不到鏡頭

1. 請先確認使用的鏡頭是否已經裝好驅動程式了

2. 把程式碼中

VideoCapture webcam(0);

的 0 改成 1或是2試試看




Friday, December 1, 2017

opencv - load video 存取影片

---

在opencv裡

我們需要使用 VideoCapture 來讀取影像串流

再將影像串流丟給Mat型態的陣列就行了

等於是將每個畫面都丟到一張圖片裡

---

#include "opencv2\opencv.hpp"

using namespace cv;
using namespace std;

int main()
{

 string fileName = "pikachu.avi"; //檔案名稱
 VideoCapture video(fileName); //讀檔案, 若要改成讀取視訊鏡頭的影像串流改成0或是1

 if (!video.isOpened())
 {
  return 0;
 }

 Mat videoFrame;
 while (true)
 {
  video >> videoFrame; //將影像餵給videoFrame

  if (videoFrame.empty())
  {
   break;
  }

  imshow("Video demo", videoFrame); //顯示影像
  waitKey(22);
 }
 return 0;
}

---

使用影像:

可以去下載我使用的測試影片: 下載連結

opencv - read & write image 存取圖片

 ---

讀取影像 - imread 

Mat imread(const String& filename, int flags=IMREAD_COLOR ) 

filename: 檔案名稱
flags: 讀取的方法

CV_LOAD_IMAGE_ANYDEPTH
If set, return 16 - bit / 32 - bit image when the input has the corresponding depth, 
otherwise convert it to 8 - bit.
如果圖片有相應的深度值,回傳16 - bit / 32 - bit影像
若沒有則轉成8 - bit影像

CV_LOAD_IMAGE_COLOR
If set, always convert image to the color one
讀取彩色影像

CV_LOAD_IMAGE_GRAYSCALE
If set, always convert image to the grayscale one
讀取灰階影像

顯示影像-imshow 

void imshow(const String& winname, InputArray mat)

 winname:視窗名稱
 mat: 欲顯示的影像
 

儲存影像-imwrite 

bool imwrite(const String& filename, InputArray img, const vector<int>& params=vector<int>() )

 filename: 檔案名稱
 img: 欲儲存的影像



範例

使用圖片:



---

#include "opencv2\opencv.hpp"

int main()
{
 cv::Mat img;
 img = cv::imread("rabbit.jpg", CV_LOAD_IMAGE_COLOR); //讀檔(彩色)
 cv::imshow("show rabbit", img); //顯示圖片在叫做"show rabbit"的視窗

 cv::Mat img_gray;
 img_gray = cv::imread("rabbit.jpg", CV_LOAD_IMAGE_GRAYSCALE); //讀檔(灰階)
 cv::imshow("show gray rabbit", img_gray); //顯示圖片在叫做"show gray rabbit"的視窗

 cv::imwrite("gray_rabbit.jpg", img_gray); //把讀成灰階檔的圖片存起來
 cv::waitKey(0); //讓視窗暫停直到下次按下鍵盤任意鍵
}

---