Computer Vision — Part 1

Ran
Ran ( AI Deep Learning )
6 min readAug 6, 2019

這篇文章,實現 Computer Vision — Part 1。(本文章內容經筆者實現驗證過)

  • 讀取影像 (color) / 寫入影像 (gray)
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
int main( int argc, const char** argv )
{
// 讀取影像
Mat color= imread("lena.jpg");
Mat gray= imread("lena.jpg", 0);

// 寫入影像
imwrite("lenaGray.jpg", gray);

int myRow=color.cols-1;
int myCol=color.rows-1;
Vec3b pixel= color.at<Vec3b>(myRow, myCol);
cout << "Pixel value (B,G,R): (" << (int)pixel[0] << "," << (int)pixel[1] << "," << (int)pixel[2] << ")" << endl;

// 顯示影像
imshow("Lena BGR", color);
imshow("Lena Gray", gray);
}
  • AOI (自動光學檢測):Object segmentation (物體分割)、Object detection (物體偵測)

(1) 前置處理 (Pre-process):移除雜訊 (Noise Removal)

// 移除雜訊
Mat img_noise, img_box_smooth;
medianBlur(img, img_noise, 3);
blur(img, img_box_smooth, Size(3,3));
(左) 包含雜訊的原始輸入。 (右) 移除雜訊後。

(2) 前置處理 (Pre-process):移除 Lighting (Lighting Removal)

Mat removeLight(Mat img, Mat pattern, int method)
{
Mat aux;
// 如果需要標準化 normalization
if(method==1)
{
// 除法運算需要將影像轉換為 32 float
Mat img32, pattern32;
img.convertTo(img32, CV_32F);
pattern.convertTo(pattern32, CV_32F);
// 將影像除以 pattern
aux= 1-(img32/pattern32);
// 縮放數值以轉換為 8 位元格式
aux=aux*255;
// 轉換為 8 位元格式
aux.convertTo(aux, CV_8U);
}else{
aux= pattern-img;
}
return aux;
}
(左) 移除雜訊後。 (中) Light Pattern。 (右) 移除 Lighting 後。

(3) 前置處理 (Pre-process):二值化 (Binarization)

// 將影像二值化以利 segment
Mat img_thr;
if(method_light!=2){
threshold(img_no_light, img_thr, 30, 255, THRESH_BINARY);
}else{
threshold(img_no_light, img_thr, 140, 255, THRESH_BINARY_INV);
}
二值化後 (Binarization)

(4) 分割 (Segmentation):連接元件 (Connected Component)

void ConnectedComponents(Mat img)
{
// 使用 connected components 分割出影像可能的部分
Mat labels;
int num_objects= connectedComponents(img, labels);
// 檢查偵測到的 objects 數量
if(num_objects < 2 ){
cout << "No objects detected" << endl;
return;
}else{
cout << "Number of objects detected: " << num_objects - 1 << endl;
}
// 建立對 objects 著色的輸出影像
Mat output= Mat::zeros(img.rows,img.cols, CV_8UC3);
RNG rng( 0xFFFFFFFF );
for(int i=1; i<num_objects; i++){
Mat mask= labels==i;
output.setTo(randomColor(rng), mask);
}
imshow("Result", output);
miw->addImage("Result", output);
}
void ConnectedComponentsStats(Mat img)
{
// 使用 connected components with stats
Mat labels, stats, centroids;
int num_objects= connectedComponentsWithStats(img, labels, stats, centroids);
// 檢查偵測到的 objects 數量
if(num_objects < 2 ){
cout << "No objects detected" << endl;
return;
}else{
cout << "Number of objects detected: " << num_objects - 1 << endl;
}
// 建立對 objects 著色的輸出影像,並顯示面積
Mat output= Mat::zeros(img.rows,img.cols, CV_8UC3);
RNG rng( 0xFFFFFFFF );
for(int i=1; i<num_objects; i++){
cout << "Object "<< i << " with pos: " << centroids.at<Point2d>(i) << " with area " << stats.at<int>(i, CC_STAT_AREA) << endl;
Mat mask= labels==i;
output.setTo(randomColor(rng), mask);
// 顯示面積的文字
stringstream ss;
ss << "area: " << stats.at<int>(i, CC_STAT_AREA);
putText(output,
ss.str(),
centroids.at<Point2d>(i),
FONT_HERSHEY_SIMPLEX,
0.4,
Scalar(255,255,255));
}
imshow("Result", output);
miw->addImage("Result", output);
}

(5) 分割 (Segmentation):尋找輪廓 (Find Contours)

void FindContoursBasic(Mat img)
{
vector<vector<Point> > contours;
findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Mat output= Mat::zeros(img.rows,img.cols, CV_8UC3);
// 檢查偵測到的 objects 數量
if(contours.size() == 0 ){
cout << "No objects detected" << endl;
return;
}else{
cout << "Number of objects detected: " << contours.size() << endl;
}
RNG rng( 0xFFFFFFFF );
for(int i=0; i<contours.size(); i++)
drawContours(output, contours, i, randomColor(rng));
imshow("Result", output);
miw->addImage("Result", output);
}
  • AOI (自動光學檢測):Feature Extraction (特徵萃取)、 Machine Learning Classification (機器學習分類)

(1) Feature Extraction (特徵萃取)

vector< vector<float> > ExtractFeatures(Mat img, vector<int>* left=NULL, vector<int>* top=NULL)
{
vector< vector<float> > output;
vector<vector<Point> > contours;
Mat input= img.clone();

vector<Vec4i> hierarchy;
findContours(input, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 檢查偵測到的 objects 數量
if(contours.size() == 0 ){
return output;
}
RNG rng( 0xFFFFFFFF );
for(int i=0; i<contours.size(); i++){

Mat mask= Mat::zeros(img.rows, img.cols, CV_8UC1);
drawContours(mask, contours, i, Scalar(1), FILLED, LINE_8, hierarchy, 1);
Scalar area_s= sum(mask);
float area= area_s[0];

if(area>500){ // 如果 area 大於 min.
RotatedRect r= minAreaRect(contours[i]);
float width= r.size.width;
float height= r.size.height;
float ar=(width<height)?height/width:width/height;
vector<float> row;
row.push_back(area);
row.push_back(ar);
output.push_back(row);
if(left!=NULL){
left->push_back((int)r.center.x);
}
if(top!=NULL){
top->push_back((int)r.center.y);
}

miw->addImage("Extract Features", mask*255);
miw->render();
waitKey(10);
}
}
return output;
}
vector< vector<float> > ExtractFeatures(Mat img, vector<int>* left=NULL, vector<int>* top=NULL)
{
vector< vector<float> > output;
vector<vector<Point> > contours;
Mat input= img.clone();

vector<Vec4i> hierarchy;
findContours(input, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 檢查偵測到的 objects 數量
if(contours.size() == 0 ){
return output;
}
RNG rng( 0xFFFFFFFF );
for(int i=0; i<contours.size(); i++){

Mat mask= Mat::zeros(img.rows, img.cols, CV_8UC1);
drawContours(mask, contours, i, Scalar(1), FILLED, LINE_8, hierarchy, 1);
Scalar area_s= sum(mask);
float area= area_s[0];

if(area>500){ // 如果 area 大於 min.
RotatedRect r= minAreaRect(contours[i]);
float width= r.size.width;
float height= r.size.height;
float ar=(width<height)?height/width:width/height;
vector<float> row;
row.push_back(area);
row.push_back(ar);
output.push_back(row);
if(left!=NULL){
left->push_back((int)r.center.x);
}
if(top!=NULL){
top->push_back((int)r.center.y);
}

miw->addImage("Extract Features", mask*255);
miw->render();
waitKey(10);
}
}
return output;
}

(2) Machine Learning Classification (機器學習分類):訓練 SVM 模型

void trainAndTest()
{
vector< float > trainingData;
vector< int > responsesData;
vector< float > testData;
vector< float > testResponsesData;
int num_for_test= 20;// 取得 nut 目錄下的影像
readFolderAndExtractFeatures("/data/nut/tuerca_%04d.pgm", 0, num_for_test, trainingData, responsesData, testData, testResponsesData);
// 取得與處理 ring 目錄下的影像
readFolderAndExtractFeatures("/data/ring/arandela_%04d.pgm", 1, num_for_test, trainingData, responsesData, testData, testResponsesData);
// 取得與處理 screw 目錄下的影像
readFolderAndExtractFeatures("/data/screw/tornillo_%04d.pgm", 2, num_for_test, trainingData, responsesData, testData, testResponsesData);

cout << "Num of train samples: " << responsesData.size() << endl;
cout << "Num of test samples: " << testResponsesData.size() << endl;

// 合併所有 data
Mat trainingDataMat(trainingData.size()/2, 2, CV_32FC1, &trainingData[0]);
Mat responses(responsesData.size(), 1, CV_32SC1, &responsesData[0]);
Mat testDataMat(testData.size()/2, 2, CV_32FC1, &testData[0]);
Mat testResponses(testResponsesData.size(), 1, CV_32FC1, &testResponsesData[0]);

SVM::Params params;
params.svmType = SVM::C_SVC;
params.kernelType = SVM::CHI2;
params.termCrit = TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6);
svm = StatModel::train<SVM>(trainingDataMat, ROW_SAMPLE, responses, params);if(testResponsesData.size()>0){
cout << "Evaluation" << endl;
cout << "==========" << endl;
// 測試 ML Model
Mat testPredict;
svm->predict(testDataMat, testPredict);
cout << "Prediction Done" << endl;
// 計算 Error
Mat errorMat= testPredict!=testResponses;
float error= 100.0f * countNonZero(errorMat) / testResponsesData.size();
cout << "Error: " << error << "\%" << endl;
// 畫出 training data 並標記 error label
plotTrainData(trainingDataMat, responses, &error);
}else{
plotTrainData(trainingDataMat, responses);
}
}

(3) Machine Learning Classification (機器學習分類):輸入影像並預測物體

int main( int argc, const char** argv )
{
CommandLineParser parser(argc, argv, keys);
parser.about("Test");

if (parser.has("help"))
{
parser.printMessage();
return 0;
}
String img_file= parser.get<String>(0);
String light_pattern_file= "/data/pattern.pgm";

if (!parser.check())
{
parser.printErrors();
return 0;
}

miw= new MultipleImageWindow("Main window", 2, 2, WINDOW_AUTOSIZE);
Mat img= imread(img_file, 0);
if(img.data==NULL){
cout << "Error loading image "<< img_file << endl;
return 0;
}
Mat img_output= img.clone();
cvtColor(img_output, img_output, COLOR_GRAY2BGR);
light_pattern= imread(light_pattern_file, 0);
if(light_pattern.data==NULL){

cout << "ERROR: Not light patter loaded" << endl;
return 0;
}
medianBlur(light_pattern, light_pattern, 3);
trainAndTest();


Mat pre= preprocessImage(img);
// 特徵萃取
vector<int> pos_top, pos_left;
vector< vector<float> > features= ExtractFeatures(pre, &pos_left, &pos_top);
cout << "Num objects extracted features " << features.size() << endl; for(int i=0; i< features.size(); i++){

cout << "Data Area AR: " << features[i][0] << " " << features[i][1] << endl;

Mat trainingDataMat(1, 2, CV_32FC1, &features[i][0]);
cout << "Features to predict: " << trainingDataMat << endl;
float result= svm->predict(trainingDataMat);
cout << result << endl;


stringstream ss;
Scalar color;
if(result==0){
color= green; // 螺帽
ss << "NUT";
}
else if(result==1){
color= blue; // 墊圈
ss << "RING" ;
}
else if(result==2){
color= red; // 螺絲
ss << "SCREW";
}

putText(img_output,
ss.str(),
Point2d(pos_left[i], pos_top[i]),
FONT_HERSHEY_SIMPLEX,
0.4,
color);

}
miw->addImage("Binary image", pre);
miw->addImage("Result", img_output);
miw->render();
waitKey(0);
return 0;}

https://medium.com/ran-ai-deep-learning,ran1988mail@gmail.com

網誌所有文章總目錄個人簡歷 (Personal Resume)

--

--

Ran
Ran ( AI Deep Learning )

Senior Electronic R&D Manager。(DL Algorithm、software and hardware),ran1988mail@gmail.com,https://medium.com/ran-ai-deep-learning