2015-12-21

OpenCV quick start

Lâu ngày có dịp dùng OpenCV. Tính ra chỉ lúc học ĐH là xài nhiều sau đó dùng thêm 1 số dịp nữa là thôi. Kể từ lần cuối viết theo C style của version 1, hầu như chẳng đụng tới nó. Sẵn tiện coi thử có gì mới sau gần 10 năm.

Phiên bản giờ là OpenCV 3.1 ngày 2015-12-21, tuy nhiên do chưa đụng tới lâu nên xem thử trước version 2 là OpenCV 2.4.12 ngày 2015-07-30. Download bản dùng cho Windows, code C/C++ xài Visual Studio 2013/Windows nói chung khá là OK. Change logs thì có ở đây. So với những gì còn nhớ thì cách viết chuyển qua C++ hết, để NS, chuyển struct thành class :D. Nói chung ok, có vẻ dễ xài hơn.

Đầu tiên thì setup, sau khi download về và install vào đâu đó thường mình để chung 1 đám trong D:\dev, giả sử là D:\dev\opencv-2.4.12.



Setup

Tạo environment variable để dễ sử dụng cho configuration tên là OPENCV_DIR.
Chọn View Other Windows Property Manager Debug | x64.

Chọn Microsoft.Cpp.x64.user Properties, thêm user macro OPENCV_DIR, set macro là environment variable.

Thêm OpenCV vào project, tạo environment variable OPENCV_DIR dùng global property page
Với Visual Studio 2008 (vc10): Tools Options Projects and Solutions VC++ Directories.

Thêm đường dẫn bin (chứa các dll của OpenCV) vào PATH Control Panel System Advanced system settings Advanced Tab Environment variables... Tuy nhiên các này không tốt lắm, tốt nhất là thêm bin theo project khi debug.

Nếu theo project trong Project Properties Debugging Environment.
Ví dụ: OpenCV

PATH=$(OPENCV_DIR)\x64\vc12\bin%3b$(PATH)

Ví dụ: Qt + OpenCV

PATH=$(QTDIR)\bin%3b$(OPENCV_DIR)\x64\vc12\bin%3b$(PATH)

Thêm Project Properties Configuration Properties C/C++ General Additional Include Directories

OpenCV

.;$(OPENCV_DIR)\include;%(AdditionalIncludeDirectories)

Với Qt + OpenCV

.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;$(OPENCV_DIR)\include;%(AdditionalIncludeDirectories)

Thêm Project Properties Configuration Properties Linker General Additional Library Directories

$(OPENCV_DIR)\x64\vc12\lib;

Thêm Project Properties Configuration Properties Linker Input Additional Dependencies
opencv_[xxx]2412[d].lib với 2412 là version và d cho debug configuration.

opencv_calib3d2412d.lib
opencv_contrib2412d.lib
opencv_core2412d.lib
opencv_features2d2412d.lib
opencv_flann2412d.lib
opencv_gpu2412d.lib
opencv_highgui2412d.lib
opencv_imgproc2412d.lib
opencv_legacy2412d.lib
opencv_ml2412d.lib
opencv_nonfree2412d.lib
opencv_objdetect2412d.lib
opencv_ocl2412d.lib
opencv_photo2412d.lib
opencv_stitching2412d.lib
opencv_superres2412d.lib
opencv_ts2412d.lib
opencv_video2412d.lib
opencv_videostab2412d.lib

Showing Image

OK ví dụ đơn giản lấy ví dụ của OpenCV thử.

#include <opencv/cv.h> 
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv/highgui.h> 

using namespace std;

int main(int argcconst char** argv)
{
     cv::Mat image;
     cv::String fileName = "demo.jpg";

     if (argc == 2) {
        fileName = argv[1];     // Image path from args
     }

     if (fileName.empty()) {
        cout << " Usage: display_image /path/to/image" << endl;
        return -1;
     }
     else {
        image = cv::imread(fileName);
        if (image.empty()) {
           cout << "Could not open or find the image" << endl;
           return -1;
        }
        else {
           cv::namedWindow("Display window", cv::WINDOW_AUTOSIZE);  // Create a window for display.
           cv::imshow("Display window", image);               // Show our image inside it.

           cv::waitKey(0);                                 // Wait for a keystroke in the window
        }
     }
}

Các ví dụ có thể tìm tại OpenCV tutorials và module xử lý ảnh Image Processing

Dùng với Qt

Chuyển giữa OpenCV::Mat và Qt::QImage Convert between cv::mat and Qimage correctly
xem chi tiết tại Qt and OpenCV.

OpenCV dùng mặc định BGR thay cho RGB. Để hiển thị ảnh grayscale dùng QImage::Format_Grayscale8 hoặc dùng QImage::Format_RGB888 cho ảnh 8UC3.

//---------------------------------------------------------------------------
// Convert cv::Mat to QImage without copy(share the same buffer)
//---------------------------------------------------------------------------
// The trap at here is mat.step, the buffer of the cv::Mat may do some padding(factor of 4)
// on each row for the sake of speed. If you just wrote
// QImage(mat.data, mat.cols, mat.rows, format);

//---------------------------------------------------------------------------
// Convert the channels from RGB to BGR
//---------------------------------------------------------------------------
// The default channels of the openCV is BGR but the QImage is RGB,
// that means you need to convert the channels from RGB to BGR when
// convert between three channels image and vice versa.In QImage,
// you could call "rgbSwapped()";
// In cv::Mat, you could call cv::cvtColor to convert the channels.
// cvtColor(col, rgb,cv::COLOR_BGR2RGB);
auto image = QImage(mat.data, mat.cols, mat.rows,
     mat.step, QImage::Format_RGB888)
     .rgbSwapped();

//---------------------------------------------------------------------------
// Convert QImage to cv::Mat without copy(share the same buffer)
//---------------------------------------------------------------------------
//mat = cv::Mat(img.height(), img.width(),
//   format, img.bits(), img.bytesPerLine());

//---------------------------------------------------------------------------
// Convert cv::Mat to QImage by copy
//---------------------------------------------------------------------------
//img = QImage(mat.data, mat.cols, mat.rows,
//   mat.step, format).copy();

Đổi colorspace cho pixel

Để đổi colorspace cho single pixel, OpenCV ko expose method, copy struct conversion từ source code tại OpenCV Github nhưng nhanh nhất là chuyển sang Mat 1x1 pixel.
Lưu ý: OpenCV xài HSV/HSB với ảnh 8 bit range là (180-255-255) so với thông thường (360-100-100) để match với range 0-255.

Xem phần RGB / HSV (CV_BGR2HSV, CV_RGB2HSV, CV_HSV2BGR, CV_HSV2RGB)
8-bit images
$V  \leftarrow 255 V, S  \leftarrow 255 S, H  \leftarrow H/2  \text{(to fit to 0 to 255)}$

cv::Scalar colorBGR2HSV(const cv::Scalar& bgrClr) {
     // cv::Scalar.val[0-2] used
     cv::Mat bgr(1, 1, CV_8UC3);
     bgr.setTo(bgrClr);

     cv::Mat hsv;
     cv::cvtColor(bgr, hsv, cv::COLOR_BGR2HSV);

     auto clr = hsv.atVec3b>(cv::Point(0, 0));
     return cv::Scalar(clr.val[0], clr.val[1], clr.val[2]);
}

cv::Scalar colorBGR2Lab(const cv::Scalar& bgrClr) {
     // cv::Scalar.val[0-2] used
     cv::Mat bgr(1, 1, CV_8UC3);
     bgr.setTo(bgrClr);

     cv::Mat lab;
     cv::cvtColor(bgr, lab, cv::COLOR_BGR2Lab);

     auto clr = lab.atVec3b>(cv::Point(0, 0));
     return cv::Scalar(clr.val[0], clr.val[1], clr.val[2]);
}

cv::Scalar colorHSV2BGR(const cv::Scalar& hsvClr) {
     // cv::Scalar.val[0-2] used
     cv::Mat hsv(1, 1, CV_8UC3);
     hsv.setTo(hsvClr);

     cv::Mat bgr;
     cv::cvtColor(hsv, bgr, cv::COLOR_HSV2BGR);

     auto clr = bgr.atVec3b>(cv::Point(0, 0));
     return cv::Scalar(clr.val[0], clr.val[1], clr.val[2]);
}

cv::Scalar colorHSV2Lab(const cv::Scalar& hsvClr) {
     // cv::Scalar.val[0-2] used
     cv::Mat hsv(1, 1, CV_8UC3);
     hsv.setTo(hsvClr);

     cv::Mat bgr, lab;
     cv::cvtColor(hsv, bgr, cv::COLOR_HSV2BGR);
     cv::cvtColor(bgr, lab, cv::COLOR_BGR2Lab);

     auto clr = lab.atVec3b>(cv::Point(0, 0));
     return cv::Scalar(clr.val[0], clr.val[1], clr.val[2]);
}

Xác định màu của image, findContours

Để xác định object có màu xác định thông thường sẽ dùng threshold binrary với mask là lower/upper color.

Range để xác định một số màu cơ bản

void initializeRanges() {

 // Red lower 0°-28° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Red,
  cv::Scalar(0, 27, 35), cv::Scalar(14, 255, 255)));

 // Brown 28°-40° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Brown,
  cv::Scalar(14, 27, 35), cv::Scalar(20, 255, 255)));

 // Orange 40°-46° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Orange,
  cv::Scalar(20, 27, 35), cv::Scalar(23, 255, 255)));

 // Yellow 46°-64° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Yellow,
  cv::Scalar(23, 27, 35), cv::Scalar(34, 255, 255)));

 // Green 64°-146° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Green,
  cv::Scalar(34, 27, 35), cv::Scalar(73, 255, 255)));

 // Aqua 146°-204° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Aqua,
  cv::Scalar(73, 27, 35), cv::Scalar(102, 255, 255)));

 // Aqua-Blue, Blue 204°-254° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Blue,
  cv::Scalar(102, 27, 35), cv::Scalar(127, 255, 255)));

 // Magenta 254°-298° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Magenta,
  cv::Scalar(127, 27, 35), cv::Scalar(149, 255, 255)));

 // Pink 298°-350° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Pink,
  cv::Scalar(149, 27, 37), cv::Scalar(175, 255, 255)));

 // Red upper 350°-360° (0-360)
 colorRanges.push_back(ColorRange(ColorType::Red,
  cv::Scalar(175, 27, 35), cv::Scalar(180, 255, 255)));

 // Black
 colorRanges.push_back(ColorRange(ColorType::Black,
  cv::Scalar(0, 0, 0), cv::Scalar(180, 255, 35)));

 // White (not blue) hue 0°-170° (0-360) & 230°-360° (0-360)
 colorRanges.push_back(ColorRange(ColorType::White,
  cv::Scalar(0, 0, 190), cv::Scalar(85, 27, 255)));
 colorRanges.push_back(ColorRange(ColorType::White,
  cv::Scalar(115, 0, 190), cv::Scalar(180, 27, 255)));

 colorRanges.push_back(ColorRange(ColorType::Blue,
  cv::Scalar(85, 0, 190), cv::Scalar(115, 27, 255)));
};

Xác định vùng theo specific color

// Threshold the HSV image to get only specific colors
cv::Mat mask, blurred, thresholded;
cv::inRange(hsv, lower, upper, mask);

cv::GaussianBlur(mask, blurred, cv::Size(9, 9), 5, 5);

// Separate out regions of an image corresponding to objects which we want to analyze. 
// This separation is based on the variation of intensity 
// between the object pixels and the background pixels.
cv::threshold(blurred, thresholded, 120, 255, CV_THRESH_BINARY);

// MORPH_RECT vs MORPH_CROSS vs MORPH_ELLIPSE
cv::Mat strEl = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(10, 10));

// Morphological opening (remove small objects from the foreground)
// Open dilate(erode(src, element))
//cv::erode(thresholded, thresholded, strEl);
//cv::dilate(thresholded, thresholded, strEl);
cv::morphologyEx(thresholded, thresholded, cv::MORPH_OPEN, strEl);

// Morphological closing (fill small holes in the foreground)
// Close erode(dilate(src, element))
//cv::dilate(thresholded, thresholded, strEl);
//cv::erode(thresholded, thresholded, strEl);
cv::morphologyEx(thresholded, thresholded, cv::MORPH_CLOSE, strEl);

cv::vector<cv::vector<cv::Point> > contours;
cv::vector<cv::Vec4i> hierarchy;

// CV_RETR_EXTERNAL/CV_RETR_LIST instead of CV_RETR_TREE 
// @see http://stackoverflow.com/questions/8830619/difference-between-cv-retr-list-cv-retr-tree-cv-retr-external
// Documentation http://opencv.itseez.com/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?#findcontours
// CV_RETR_EXTERNAL gives "outer" contours, so if you have (say) one contour enclosing another 
// (like concentric circles), only the outermost is given.
cv::findContours(thresholded, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);

//cv::RNG rng;
//cv::Mat mat = thresholded.clone();
//for (int i = 0; i < contours.size(); i++) {
// auto clr = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
// cv::drawContours(mat, contours, i, clr, 1, 8, hierarchy, 0, cv::Point());
//}

Color difference

Chuyển qua L*a*b scale và dùng CIE Delta E 1976 algorithm hay CIE Delta E 2000.
Code của algorithm này như sau:

double deg2rad(double deg) {
 return (deg * (M_PI / 180.0));
}

double rad2deg(double rad) {
 return ((180.0 / M_PI) * rad);
}

// Computes the difference between two BGR colors by converting them to 
// the L*a*b scale BGR/CIE (CV_BGR2Lab) and comparing them using the 
// CIE76 algorithm http://en.wikipedia.org/wiki/Color_difference#CIE76
// The 1976 formula is the first color-difference formula that related 
// a measured to a known set of CIELAB coordinates. This formula has been 
// succeeded by the 1994 and 2000 formulas because the CIELAB space 
// turned out to be not as perceptually uniform as intended, especially in 
// the saturated regions. This means that this formula rates these 
// colors too highly as opposed to other colors.
// Lab, Delta E (standard Euclidean distance) sqrt(L² + a² + b²)
// CIE Delta E 1976
// JND: ~2.3
double deltaE1976(const cv::Scalar& lab1, const cv::Scalar& lab2)
{
 // RGB/CIE L*a*b*
 // 8-bit images: L ← L ∗ 255 / 100, a ← a + 128, b ← b + 128
 auto dL = lab1.val[0] - lab2.val[0];
 // Covert dL back CIE L*a*b*
 dL = dL * 100 / 255;

 auto da = lab1.val[1] - lab2.val[1];
 auto db = lab1.val[2] - lab2.val[2];

 return cv::sqrt(dL * dL + da * da + db * db);
}

// http://svn.int64.org/viewvc/int64/colors/colors.js
// http://www.ece.rochester.edu/~gsharma/ciede2000/
double deltaE2000(const cv::Scalar& lab1, const cv::Scalar& lab2) {

 // RGB/CIE L*a*b*
 // 8-bit images: L ← L ∗ 255 / 100, a ← a + 128, b ← b + 128
 auto x = lab1;
 auto y = lab2;

 /*
 * "For these and all other numerical/graphical delta E00 values
 * reported in this article, we set the parametric weighting factors
 * to unity(i.e., kL = kC = kH = 1.0)." (Page 27).
 */
 const double kL = 1.0, kC = 1.0, kH = 1.0;
 const double deg360InRad = deg2rad(360.0);
 const double deg180InRad = deg2rad(180.0);
 const double pow25To7 = 6103515625.0; /* pow(25, 7) */

 /*
 * Step 1
 */
 /* Equation 2 */
 double C1 = sqrt((x.val[1] * x.val[1]) + (x.val[2] * x.val[2]));
 double C2 = sqrt((y.val[1] * y.val[1]) + (y.val[2] * y.val[2]));

 /* Equation 3 */
 double barC = (C1 + C2) / 2.0;

 /* Equation 4 */
 double G = 0.5 * (1 - sqrt(pow(barC, 7) / (pow(barC, 7) + pow25To7)));

 /* Equation 5 */
 double a1Prime = (1.0 + G) * x.val[1];
 double a2Prime = (1.0 + G) * y.val[1];

 /* Equation 6 */
 double CPrime1 = sqrt((a1Prime * a1Prime) + (x.val[2] * x.val[2]));
 double CPrime2 = sqrt((a2Prime * a2Prime) + (y.val[2] * y.val[2]));

 /* Equation 7 */
 double hPrime1;
 if (x.val[2] == 0 && a1Prime == 0) {
  hPrime1 = 0.0;
 }
 else {
  hPrime1 = atan2(x.val[2], a1Prime);
  /*
  * This must be converted to a hue angle in degrees between 0
  * and 360 by addition of 2􏰏 to negative hue angles.
  */
  if (hPrime1 < 0){
   hPrime1 += deg360InRad;
  }
 }

 double hPrime2;
 if (y.val[2] == 0 && a2Prime == 0) {
  hPrime2 = 0.0;
 }
 else {
  hPrime2 = atan2(y.val[2], a2Prime);
  /*
  * This must be converted to a hue angle in degrees between 0
  * and 360 by addition of 2 to negative hue angles.
  */
  if (hPrime2 < 0){
   hPrime2 += deg360InRad;
  }
 }

 /*
 * Step 2
 */
 /* Equation 8 */
 double deltaLPrime = y.val[0] - x.val[0];

 /* Equation 9 */
 double deltaCPrime = CPrime2 - CPrime1;

 /* Equation 10 */
 double deltahPrime;
 double CPrimeProduct = CPrime1 * CPrime2;

 if (CPrimeProduct == 0) {
  deltahPrime = 0;
 }
 else {
  /* Avoid the fabs() call */
  deltahPrime = hPrime2 - hPrime1;
  if (deltahPrime < -deg180InRad) {
   deltahPrime += deg360InRad;
  }
  else if (deltahPrime > deg180InRad) {
   deltahPrime -= deg360InRad;
  }
 }
 /* Equation 11 */
 double deltaHPrime = 2.0 * sqrt(CPrimeProduct) *
  sin(deltahPrime / 2.0);

 /*
 * Step 3
 */
 /* Equation 12 */
 double barLPrime = (x.val[0] + y.val[0]) / 2.0;

 /* Equation 13 */
 double barCPrime = (CPrime1 + CPrime2) / 2.0;

 /* Equation 14 */
 double barhPrime, hPrimeSum = hPrime1 + hPrime2;

 if (CPrime1 * CPrime2 == 0) {
  barhPrime = hPrimeSum;
 }
 else {
  if (fabs(hPrime1 - hPrime2) <= deg180InRad) {
   barhPrime = hPrimeSum / 2.0;
  }
  else {
   if (hPrimeSum < deg360InRad) {
    barhPrime = (hPrimeSum + deg360InRad) / 2.0;
   }
   else {
    barhPrime = (hPrimeSum - deg360InRad) / 2.0;
   }
  }
 }

 /* Equation 15 */
 double T = 1.0 - (0.17 * cos(barhPrime - deg2rad(30.0))) +
  (0.24 * cos(2.0 * barhPrime)) +
  (0.32 * cos((3.0 * barhPrime) + deg2rad(6.0))) -
  (0.20 * cos((4.0 * barhPrime) - deg2rad(63.0)));

 /* Equation 16 */
 double deltaTheta = deg2rad(30.0) *
  exp(-pow((barhPrime - deg2rad(275.0)) / deg2rad(25.0), 2.0));

 /* Equation 17 */
 double RC = 2.0 * sqrt(pow(barCPrime, 7.0) /
  (pow(barCPrime, 7.0) + pow25To7));

 /* Equation 18 */
 double SL = 1 + ((0.015 * pow(barLPrime - 50.0, 2.0)) /
  sqrt(20 + pow(barLPrime - 50.0, 2.0)));

 /* Equation 19 */
 double SC = 1 + (0.045 * barCPrime);

 /* Equation 20 */
 double SH = 1 + (0.015 * barCPrime * T);

 /* Equation 21 */
 double RT = (-sin(2.0 * deltaTheta)) * RC;

 /* Equation 22 */
 double deltaE = sqrt(
  pow(deltaLPrime / (kL * SL), 2.0) +
  pow(deltaCPrime / (kC * SC), 2.0) +
  pow(deltaHPrime / (kH * SH), 2.0) +
  (RT * (deltaCPrime / (kC * SC)) * (deltaHPrime / (kH * SH))));

 return (deltaE);
}


2015-12-05

Disable CTRL-Space toggling Chinese IME (Windows 7)

Tắt Ctrl-Space chuyển mode Chinese IME.

Xem http://superuser.com/questions/327479/ctrl-space-always-toggles-chinese-ime-windows-7

Tóm tắt:

1. Bật Registry Edit: Start (Windows + R) > regedit
2. Tới HKEY_CURRENT_USER/Control Panel/Input Method/Hot Keys
3. Chọn 2 key name
  • 00000070 for the Chinese (Traditional) IME - Ime/NonIme Toggle hotkey
  • 00000010 for the Chinese (Simplified) IME - Ime/NonIme Toggle hotkey
4. Có 2 subkey cần chỉnh














  • Key Modifiers quy định Alt/Ctrl/Shift/etc giá trị đang là Ctrl (02c00000).
  • Virtual Key hiện tại Space (20000000).
5. Chỉnh Key Modifiers từ 02 thành 00
6. Chỉnh Virtual Key từ 20 thành FF
7. Log off hoặc restart.