顯示具有 OpenCV 標籤的文章。 顯示所有文章
顯示具有 OpenCV 標籤的文章。 顯示所有文章

2021年2月20日 星期六

OpenCV Trackbar

一般寫影像處理時,不同的環境條件所需設定的參數都不一樣,為了加快自己調整參數的速度,決定使用opencv 的tracker bar做即時調整,而opencv的 tracker bar是callback函式,所以當拉把被移動位置時,會主動跳到callback函式做動作,同時也將當前bar的數值給函式使用,如此一來,就可以即時顯示不同結果,實際使用非常方便,不然從程式端去調整很麻煩,如果大家有更好的做法,也歡迎分享一下~~

Trackbar主要有五個參數要設定,分別為Trackbar的標籤名稱、在哪個視窗顯示Trackbar、Trackbar最大值(最小值預設固定為0)、Trackbar當前數值、callback函式(引數內容固定)

若想要使用兩個Trackbar,只要在第一個參數(標籤名稱)提供另一個名稱就可以,如下所示
	createTrackbar("Param1", "Window", &Param1, SliderMax, on_trackbar);
	createTrackbar("Param2", "Window", &Param2, SliderMax, on_trackbar);


程式碼如下:
#include <cstdio>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

const int SliderMax = 100;
int Param1 = 50;
int Param2 = 0;

Mat GrayImg, OutputImg;

static void on_trackbar(int, void*)
{
	double Alpha = (float)Param1 / 50;
	double Beta = Param2;
	GrayImg.convertTo(OutputImg, -1, Alpha, Beta);
	imshow("Window", OutputImg);
}

int main(){

	Mat SrcImg = imread("dog.jpg", CV_LOAD_IMAGE_COLOR);
	namedWindow("Window", WINDOW_AUTOSIZE);
	
	cvtColor(SrcImg, GrayImg, CV_BGR2GRAY);

	createTrackbar("Param1", "Window", &Param1, SliderMax, on_trackbar);
	createTrackbar("Param2", "Window", &Param2, SliderMax, on_trackbar);

	waitKey(0);
	return 0;
}
結果圖:


或是



備註(參考資料):
1.https://docs.opencv.org/3.4/da/d6a/tutorial_trackbar.html





2020年12月31日 星期四

OpenCV findhomography(單應性矩陣)與warpperspective(透視變換)

隨著科技進步與市場競爭,許多公司會試著將機械手臂導入到產線上,同時搭配工業相機做位置辨識,如手眼機器人(eye to hand和eye in hand),其原因就是要配合現在少量多變化的產能需求,達到彈性製造。

為了做到手眼機器人的功能,因有兩個不同座標系(影像座標跟手臂座標),如下圖所示,因無法直接將影像座標給機器手臂使用,故需使用OpenCV findhomography(單應性矩陣),找出兩者之間的關係,再利用warpperspective(透視變換)做座標轉換。



又或物體因離相機遠近不同,雖然明明是一樣的距離,但從影像上看卻不一樣長,如下圖的伯朗大道一樣,近的道路很寬,遠的又小到不行。



而Homography其實就是一個3 x 3矩陣,如下圖所示,其中h_9設為常數1,同時可以發現Homography  矩陣尚有八個未知數,而一對影像與機械座標只能求出其中兩個解,表示在實際校正個過程,至少要有四對非共線影像與機械座標才能求出Homography 矩陣。


下列程式碼主要展示從一張有歪斜的影像中,經由座標轉換之後,擷取到一張沒歪斜影像,如下圖所示,左邊為歪斜影像,右邊則是方正影像,不過校正用的四組座標,我並非用影像處理計算而是簡單人員標記,所以可能看起來沒有到完全很正。


程式碼如下:
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){

	Mat SrcImg1 = imread("SrcImg.jpg", CV_LOAD_IMAGE_COLOR);
	Mat DstImg = Mat::zeros(Size(3500, 1500), CV_8SC1);

	vector PtData1, PtData2;

	PtData1.push_back(Point2f(513,627));
	PtData1.push_back(Point2f(233, 2000));
	PtData1.push_back(Point2f(3877, 2047));
	PtData1.push_back(Point2f(3855, 835));

	PtData2.push_back(Point2f(0, 0));
	PtData2.push_back(Point2f(0, 1500));
	PtData2.push_back(Point2f(3500, 1500));
	PtData2.push_back(Point2f(3500, 0));

	Mat HomogMat = findHomography(PtData1, PtData2, CV_RANSAC);
	warpPerspective(SrcImg1, DstImg, HomogMat, DstImg.size());
	imwrite("DstImg.bmp", DstImg);
return 0; }

SrcImg1

DstImg







2020年11月28日 星期六

Python with Anaconda and Visual Studio Code

因應這幾年來深度學習熱潮,來介紹一下如何使用Anaconda建立虛擬環境,並搭配使用Visual Studio Code做程式編輯,步驟如下:

步驟1:下載Anaconda,連結為https://www.anaconda.com/products/individual,下載64位元版本


然後下載VS Code,連結為https://code.visualstudio.com/,我自己習慣是下載免安裝版本比較好使用

步驟2:安裝Anaconda,這步驟其實很單純,基本上就是一直下一頁就是了









步驟3:啟動VS Conde後,先到擴充工具安裝Python,這樣後續才能挑選自己想要的直譯器(環境),如果想要中文介面也可一併下載中文包,如下圖所示。


步驟4:在Anaconda中建立虛擬環境,可根據不同專案需求加入不同package,以避免會互相干擾,例如我想建立一個專門開發OpenCV的虛擬環境,該如何做呢?

步驟4-1:開啟Anaconda Prompt.exe,一般可從windows 開始中找尋Anaconda 資料夾,如下圖所示。


步驟4-2(建立環境):輸入conda create -n Opencv python=3.6,代表建立一個Opencv虛擬環境,並安裝Python 3.6版本 




完成後,系統還會跟你說接下來可以做啥

步驟4-3(啟用環境):輸入activate Opencv,可發現原本的base變成Opencv,代表我們已經切換環境了


步驟4-4(安裝套件):輸入conda install --y -c menpo opencv,然後等待安裝完成,此外,還需再安裝opencv-python才能正確使用OpenCV lib,所以再輸入pip install opencv-python,然後慢慢等



步驟4-5(測試):進入Python 直譯器,並直接將下列程式碼複製貼上測看看(記得貼上時每一行前面不能有空白,不然會出錯),會直接跑出一個視窗,裡面為一張500 x 500黑色影像,等跑到cv.destroyAllWindows()時再按下Enter就結束了,這樣就能確認OpenCV lib是否有安裝成功。

import numpy as np
import cv2 as cv
img = np.zeros((500, 500, 3), np.uint8)
cv.imshow('Image test', img)
cv.waitKey(1000)
cv.destroyAllWindows()

步驟4-6(停用環境):輸入conda deactivate 或deactivate,但有些版本下deactivate會出現警告


步驟4-7(刪除環境):假設有個test環境,輸入conda env remove --name test,就會刪除其環境,如下圖所示


步驟1~4主要介紹如何安裝Anaconda與Visual Studio Code,並建立虛擬環境,而下個步驟則要說明如何用Visual Studio Code做程式碼的修改與測試。

步驟5:開啟Visual Studio Code,選擇一個練習用的資料夾,如TestFolder

新增一個檔案,test.py,然後將剛剛步驟4-5的程式碼貼到此py檔中


再來就是將
直譯器設為剛剛建立的虛擬環境OpenCV,如下圖所示。



步驟6:最後,右鍵點選test.py並選擇在終端機中執行Pytone檔案,如果有正常顯示結果,就代表這次環境安裝成功可使用摟,並且未來可直接在Visual Studio Code修改程式碼。


結果:

#opencv #Python #Anaconda #Visual Studio Code 


2020年11月22日 星期日

OpenCV Image Watch

最近,在閱讀OpenCV文件時,突然發現一個很酷的東西,就是Image watch,這個工具是Microsoft提供給開發者,在除錯的時候能快速的知道每個影像(矩陣)實際長怎樣,換作是我平常的習慣,我都會將想看的影像利用imread存下來後,再利用小畫家或是GIMP去看影像閥值怎樣定最好,但有時候要開來開去也是很麻煩,而Image watch,就能夠在除錯中直接提供影像基本資訊,像是影像長寬、深度、畫面亮暗等。

如果要使用Image watch,可以直接從網路上搜尋image watch vs2013(選擇你對應的版本),如連結(https://marketplace.visualstudio.com/items?itemName=VisualCPPTeam.ImageWatch)並直接下載,會得到一個ImageWatch.vsix檔案,直接安裝即可。

如果想要確認是否有安裝成功,可以開啟你的Visual studio後,點選工具->擴充功能和更新選項後,如果有看到Image watch已被安裝,就代表完成瞜。







再來就是說明如何使用,先調整為debug模式,並在想查找位置下設中斷點,當執行到中斷點的時候,點選檢視->其他視窗->Image watch,就會調出一個很酷的視窗,如下所示。


能夠藉由這個工具讓你知道目前記憶體已經載入的資訊,此外,滾動滑鼠滾輪能放大影像,並直接看到每個像素亮度大小,如下圖所示。


不過我目前最常只用來看亮度分布而已,其他功能都不太吸引我,哈哈,如果有新的使用心得再分享出來。


備註(參考資料):
1.http://docs.opencv.org/2.4/doc/tutorials/introduction/windows_visual_studio_image_watch/windows_visual_studio_image_watch.html#windows-visual-studio-image-watch





2020年9月27日 星期日

OpenCV Drawing Functions

 這次來介紹OpenCV常用的六個畫圖函式,分別有畫線(line)、畫圓(circle)、畫矩形(rectangle)、畫橢圓(ellipse)、畫多邊形(polylines)、文字顯示(putText)等,說明如下:

--------------------------------------------------------------------------------------------------------------

1.line (InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)

img:輸入影像

pt1:起點座標

pt1:終點座標

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0

--------------------------------------------------------------------------------------------------------------

2.circle (InputOutputArray img, Point center, int radius, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)

img:輸入影像

center:圓心座標

radius:半徑

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0

--------------------------------------------------------------------------------------------------------------

3.rectangle (Mat &img, Rect rec, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
rectangle (InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)

img:輸入影像

rect:矩形範圍(矩形左上角x座標,矩形左上角y座標,矩形寬度,矩形高度)

pt1:矩形左上角座標

pt2:矩形右下角座標

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0

除此之外,如果想要繪製旋轉的矩形,則是定義RotateRect,並搭配line一條一條繪製,可參考下列程式碼

--------------------------------------------------------------------------------------------------------------

4.ellipse (InputOutputArray img, const RotatedRect &box, const Scalar &color, int thickness=1, int lineType=LINE_8)
ellipse (InputOutputArray img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)

img:輸入影像

rotatedRect:旋轉矩形範圍(Point2f圓心座標, Size2f 矩形寬高,旋轉角度)

center:圓心座標

axes:橢圓長短軸長度的一半

angle:橢圓旋轉角度(請參考下圖,3.2.0版本圖片似乎是錯誤的,所以選3.3.0的圖片說明,註1)

startAngle:繪製起始角度(請參考下圖,註1)

endAngle:繪製結束角度(請參考下圖,註1)

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0



--------------------------------------------------------------------------------------------------------------

5.polylines (InputOutputArray img, InputArrayOfArrays pts, bool isClosed, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)

img:輸入影像

pts:點座標

isClosed:是否為封閉輪廓

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0

--------------------------------------------------------------------------------------------------------------

6.putText (InputOutputArray img, const String &text, Point org, int fontFace, double fontScale, Scalar color, int thickness=1, int lineType=LINE_8, bool bottomLeftOrigin=false)

img:輸入影像

text:欲輸入文字內容

org:文字左下角座標

fontFace:文字類型(請參考下圖,註2)

fontScale:文字大小

color:顏色

thickness:預設厚度為1

lineType:預設8連結,選項有LINE_4 (4連結)、LINE_8(8連結) 、LINE_AA (反鋸齒)

shift:預設座標小數點數為0


--------------------------------------------------------------------------------------------------------------

程式碼如下:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(){

	Mat SrcImg(500, 500, CV_8UC3, Scalar(0, 0, 0));
	Point StartPt = Point(25, 25), EndPt = Point(475, 475);
	Point TextPt = Point(250, 65), CenterPt = Point(250, 250);
	Size2f RotateSize = Size2f(200, 60);
	int Thickness = 3;

	//line function
	line(SrcImg, StartPt, EndPt, Scalar(0, 255, 0), Thickness, 8, 0);

	//circle function
	circle(SrcImg, CenterPt, 30, Scalar(0, 255, 255), -1, 8, 0);

	//rectangle function
	Rect NormalRect = Rect(25, 25, 100, 100);
	rectangle(SrcImg, NormalRect, Scalar(255, 0, 0), Thickness, 8, 0);
	rectangle(SrcImg, Point(375, 375), EndPt, Scalar(255, 0, 0), Thickness, 8, 0);

	//rotatedRect function
	RotatedRect RotateRect = RotatedRect(CenterPt, RotateSize, 45);
	Point2f Vertices[4];
	RotateRect.points(Vertices);
	for (int i = 0; i < 4; i++)
		line(SrcImg, Vertices[i], Vertices[(i + 1) % 4], Scalar(255, 255, 0), Thickness, 8, 0);

	//ellipse function
	ellipse(SrcImg, CenterPt, Size(RotateSize.width / 2, RotateSize.height / 2), -45, 0, 360, Scalar(255, 0, 255), Thickness, 8, 0);
	ellipse(SrcImg, RotateRect, Scalar(255, 0, 255), Thickness, 8);

	//polylines function
	vector polygons;
	polygons.push_back(Point(25, 125));
	polygons.push_back(Point(125, 125));
	polygons.push_back(Point(125, 375));
	polygons.push_back(Point(375, 375));
	polygons.push_back(Point(375, 475));
	polygons.push_back(Point(25, 475));
	polylines(SrcImg, polygons, true, Scalar(255, 150, 150), Thickness, 8, 0);

	//putText function
	putText(SrcImg, "OpenCV", TextPt, FONT_HERSHEY_COMPLEX, 1.5, Scalar(0, 0, 255), Thickness, 8, false);
	circle(SrcImg, TextPt, 1, Scalar(0, 0, 255), Thickness, 8, 0);

	imshow("SrcImg", SrcImg);

	waitKey(0);
	return 0;
}
結果圖:





備註(參考資料):
1.https://docs.opencv.org/3.3.0/d6/d6e/group__imgproc__draw.html
2.https://docs.opencv.org/3.3.0/d0/de1/group__core.html#ga0f9314ea6e35f99bb23f29567fc16e11

2020年8月31日 星期一

OpenCV erode、dilate(形態學處理)

當使用完二值化或邊緣偵測後,畫面總會殘留一些白點雜訊,或特徵會有破洞等狀況,為了不影響後續的辨識,這時可用型態學的方式,如侵蝕或膨脹將不想要的特徵給去除。

侵蝕

void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue())

InputArray src:輸入影像
OutputArray dst:輸出影像
InputArray kernel:輸入核心
Point anchor:錨點位置
int iterations:疊代次數
nt borderType:邊界模式

膨脹

void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue())

InputArray src:輸入影像
OutputArray dst:輸出影像
InputArray kernel:輸入核心
Point anchor:錨點位置
int iterations:疊代次數
nt borderType:邊界模式

但不論是侵蝕或膨脹,其預設的kernel都是3x3,如果想定義自己的kernel,可利用下列函示

Mat getStructuringElement(int shape, Size esize, Point anchor = Point(-1, -1));
int shape:元素形狀
Size esize:建構元素大小(kernal size)
Point anchor:錨點位置


程式碼如下:
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){

	Mat SrcImg = imread("dog.jpg", CV_LOAD_IMAGE_COLOR);
	Mat GrayImg, BinaryImg;
	Mat ErodeImg, DilateImg, UserErodeImg, UserDilateImg;

	cvtColor(SrcImg, GrayImg, CV_BGR2GRAY);

	Mat Element = getStructuringElement(MORPH_ELLIPSE, Size(7, 7));
	erode(GrayImg, ErodeImg, Mat(), Point(-1, -1), 1);
	erode(GrayImg, UserErodeImg, Element, Point(-1, -1), 1);
	dilate(GrayImg, DilateImg, Mat(), Point(-1, -1), 1);
	dilate(GrayImg, UserDilateImg, Element, Point(-1, -1), 1);

	imshow("GrayImg", GrayImg);
	imshow("ErodeImg", ErodeImg);
	imshow("UserErodeImg", UserErodeImg);
	imshow("DilateImg", DilateImg);
	imshow("UserDilateImg", UserDilateImg);

	waitKey(0);
	return 0;
}

GrayImg

ErodeImg

UserErodeImg

DilateImg

UserDilateImg

2020年8月1日 星期六

OpenCV Threshold(二值化)

為了從影像找出我們想要的特徵,一般會使用二值化(Threshold)先將前景(foreground)與背景(background)初步分離出來,以減少不必要的訊息量(雜訊),底下就來介紹一下OpenCV常用的threshold函式,我自己初分成三種類型。

第一種:基本款的二值化(依據閥值進行分類的二值化)

double cv::threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
InputArray src:輸入的影像來源
OutputArray dst:輸出的影像來源
double thresh:判定閥值
double maxval:最大值(當像素值大於或小於判定閥值時,用此參數取代)
int type:設定二值化模式,常見有THRESH_BINARY、THRESH_BINARY_INV、THRESH_TRUNC、THRESH_TOZERO、THRESH_TOZERO_INV五種。
THRESH_BINARY:將大於閥值的灰度值設為最大值,反之設為0
THRESH_BINARY_INV:將大於閥值的灰度值設為0,反之設為最大值
THRESH_TRUNC:將大於閥值的灰度值設為閥值,反之則不變
THRESH_TOZERO:將大於閥值的灰度值不變,反之則設為0
THRESH_TOZERO_INV:將大於閥值的灰度值設為0,反之則不變

第二種:進階版的二值化(依據亮度分佈,自動定義閥值進行分類的二值化)

跟第一種幾乎一樣,差別在於設定的二值化模式不同,可選擇為THRESH_OTSU跟THRESH_TRIANGLE兩種,THRESH_OTSU比較適用在有兩個分峰值情況下使用,而THRESH_TRIANGLE則用在單一峰值比較合適。


第三種:強大版的二值化(依據區域不同,適應不同閥值進行分類的二值化)

void adaptiveThreshold(InputArray src, OutputArray dst,double maxValue,int adaptiveMethod, int thresholdType, int blockSize, double C)
InputArray src:輸入的影像來源
OutputArray dst:輸出的影像來源
double maxval:最大值(當像素值大於或小於判定閥值時,用此參數取代)
int adaptiveMethod:設定自適應模式,有ADAPTIVE_THRESH_MEAN_C(平均法)、ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)兩種。
nt thresholdType:設定二值化模式,即前面提到的二值化五種模式
int blockSize:區塊大小
double C:常數

總結:

上述三種方式,我覺得第一種使用單純但無法用於環境光源變化大的地方、第二種雖然可以自動定義閥值,但使用時若影像亮度分佈沒有明顯的兩個或一個峰值,則效果可能有限,第三種則適應能力較強,處理後的效果還有點像邊緣偵測,也許可用在其他地方上。


現在就來實作一下吧,為了比較好理解基本款二值化操作,我設計了一張圖片,當中分成了五個區域,由左至右亮度值分別為(50、100、150、200、250),讓我們更容易看出不同的差異性。

程式碼如下:
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){

	Mat SrcImg = imread("GradientImg.bmp", CV_LOAD_IMAGE_COLOR);
	Mat GrayImg, BinaryImg, BinaryInvImg, BinaryTruncImg, BinaryToZeroImg, BinaryToZeroInvImg;
	Mat OtsuImg, TriangleImg, AdaptiveThImg;

	cvtColor(SrcImg, GrayImg, CV_BGR2GRAY);
	threshold(GrayImg, BinaryImg, 150, 255, THRESH_BINARY);//將大於閥值的灰度值設為最大值,反之設為0
	threshold(GrayImg, BinaryInvImg, 150, 255, THRESH_BINARY_INV);//將大於閥值的灰度值設為0,反之設為最大值
	threshold(GrayImg, BinaryTruncImg, 190, 255, THRESH_TRUNC);//將大於閥值的灰度值設為閥值,反之則不變
	threshold(GrayImg, BinaryToZeroImg, 150, 255, THRESH_TOZERO);//將大於閥值的灰度值不變,反之則設為0
	threshold(GrayImg, BinaryToZeroInvImg, 150, 255, THRESH_TOZERO_INV);//將大於閥值的灰度值設為0,反之則不變
	threshold(GrayImg, OtsuImg, 0, 255, THRESH_OTSU);//自動設定最佳閥值,適用於有兩個峰值的情況
	threshold(GrayImg, TriangleImg, 250, 255, THRESH_TRIANGLE);//自動設定最佳閥值,適用只有一個峰值的情況
	adaptiveThreshold(GrayImg, AdaptiveThImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 5, 1);

	imshow("GradientImg", SrcImg);
	imshow("BinaryImg", BinaryImg);
	imshow("BinaryInvImg", BinaryInvImg);
	imshow("BinaryTruncImg", BinaryTruncImg);
	imshow("BinaryToZeroImg", BinaryToZeroImg);
	imshow("BinaryToZeroInvImg", BinaryToZeroInvImg);
	imshow("OtsuImg", OtsuImg);
	imshow("TriangleImg", TriangleImg);
	imshow("AdaptiveThImg", AdaptiveThImg);

	waitKey(0);
	return 0;
}


GradientImg

BinaryImg

BinaryInvImg

BinaryTruncImg

BinaryToZeroImg

BinaryToZeroInvImg

OtsuImg


TriangleImg

AdaptiveImg