OpenCV儀表資料識別
from https://www.itread01.com/content/1549402408.html
OpenCV儀表資料識別(一):整體思想
一、目標:利用OpenCV完成對儀表上八段數碼管資料的識別。
二、整體思想:
1. 影象預處理
2. 數字識別
3. 將數字按照正確的精度儲存並顯示到螢幕
1. 影象預處理
2. 數字識別
3. 將數字按照正確的精度儲存並顯示到螢幕
程式流程圖如下:
接下來將分開介紹各部分的實現方法。
參考文獻:
尹傳歷,基於視訊影象的數字儀表讀數自動識別
王淑憬,基於OPENCV的數字萬用表數字識別方法
from https://blog.csdn.net/ZhtSunday/article/details/52093165
from https://www.itread01.com/content/1547840015.html
ads
尹傳歷,基於視訊影象的數字儀表讀數自動識別
王淑憬,基於OPENCV的數字萬用表數字識別方法
from https://blog.csdn.net/ZhtSunday/article/details/52093165
OpenCV仪表数据识别(三):数据按行分割
1.实现方法
采用投影法,将每一行的数字横向投影。
就像这样
就像这样
但是在实际的操作中我发现其实并没有必要将投影图建立出来,只要有了每一行的白色像素数就够了。
2.代码
本段代码的输入图像是用上一篇文章的方法生成的二值图BinaryImage。这里要注意的是,如果将二值图储存再读取之后,建议重新二值化,否则很多像素将不再是标准的0或者255。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
3.解析
- 首先建立rowwidth数组用来存储每行白色的像素数。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 用DivLoc数组储存分行的位置,如果前一行白色像素数>=10而后一行>10,则判定为分行位置。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 将图像按行分割。每次循环确定一个仅包含该行的感兴趣区域,将该区域拷贝到新的图像,并且以Row1.jpg Row2.jpg为名称存储在指定位置。用LinePath给每次分割出的行图像分配存储路径。注意在每次循环中均建立一个新的RowImage、LinePath也要在每个循环中释放RowImage和LinePath。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 最后记得释放内存!返回行数待用。
- 1
- 2
- 3
from https://www.itread01.com/content/1547840015.html
基於OpenCV的數碼管數字識別
利用OpenCV可實現工業儀表裝置的讀數識別。儀表一般可分為兩:數字式儀表和指標式儀表,本博文主要介紹一下數字式儀表識別的關鍵技術。下圖是用軟體模擬的數碼管圖片,本文識別的也就是圖中的數字。
一、影象定位
在實際的應用場景中,拍攝到的儀表區域很有可能會包含多餘的背景部分,一個比較簡單的解決方法是在拍攝時先行設定一個邊界區域,提醒拍攝者將待識別的內容限制在區域中。後期識別時直接提取邊界區域內的資訊進行識別。
在實際的應用場景中,拍攝到的儀表區域很有可能會包含多餘的背景部分,一個比較簡單的解決方法是在拍攝時先行設定一個邊界區域,提醒拍攝者將待識別的內容限制在區域中。後期識別時直接提取邊界區域內的資訊進行識別。
二、影象預處理
影象預處理的內容包括灰度化、二值化、腐蝕(或膨脹)、輪廓提取以及數字分割等。
影象預處理的內容包括灰度化、二值化、腐蝕(或膨脹)、輪廓提取以及數字分割等。
1.灰度化
灰度化的目的是將圖片從RGB的格式轉為單通道,畫素值為~255範圍內的灰度圖。
灰度化的目的是將圖片從RGB的格式轉為單通道,畫素值為~255範圍內的灰度圖。
#define picture "test4.png" // filepath
...
Mat image_org = imread(picture, IMREAD_COLOR);
imshow("image_org", image_org); // read RGB image
Mat image_gry = imread(picture, IMREAD_GRAYSCALE);
if (image_gry.empty()) // read RGB image
return -1;
imshow("image_gry", image_gry);
如下圖所示:
2.二值化
二值化操作將灰度圖變為畫素值為0或者255的二值化影象,閾值可以根據圖片的實際需求設定,要求是能將背景和數字分開。
二值化操作將灰度圖變為畫素值為0或者255的二值化影象,閾值可以根據圖片的實際需求設定,要求是能將背景和數字分開。
Mat image_bin;
threshold(image_gry, image_bin, 50, 255, THRESH_BINARY); // convert to binary image
imshow("image_bin", image_bin);
二值化效果如下,threshold()函式中第二個形參選取的是THRESH_BINARY,因此影象變成黑底白字的效果。注意:此時圖片背景文字的顏色直接影響後期的處理。
此形參的取值詳見: cv::ThresholdTypes
此形參的取值詳見: cv::ThresholdTypes
3.腐蝕/膨脹
數字式儀表大部分採用八段式數碼管,因此數字是不連續的。因此,在數字分割提取之前需要採取一定的操作使得數字的筆畫連線起來,以防止數字被割裂而無法識別。腐蝕膨脹操作就可以解決這個問題。需要注意的是,腐蝕膨脹是對於白色部分而言的,膨脹就是影象中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有比原圖更小的高亮區域。
現在的字型是白色的,如果想要讓字型連續,就需要進行膨脹。 但也不能過度膨脹,否則會使得相鄰數字連線起來,無法分割。
數字式儀表大部分採用八段式數碼管,因此數字是不連續的。因此,在數字分割提取之前需要採取一定的操作使得數字的筆畫連線起來,以防止數字被割裂而無法識別。腐蝕膨脹操作就可以解決這個問題。需要注意的是,腐蝕膨脹是對於白色部分而言的,膨脹就是影象中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有比原圖更小的高亮區域。
現在的字型是白色的,如果想要讓字型連續,就需要進行膨脹。 但也不能過度膨脹,否則會使得相鄰數字連線起來,無法分割。
Mat image_dil;
Mat element = getStructuringElement(MORPH_RECT, Size(20, 20)); // 膨脹
dilate(image_bin, image_dil, element);
imshow("image_dil", image_dil);
膨脹的效果如下:
4.輪廓提取
每個數字連通後,即可進行輪廓提取,找個每個數字的輪廓位置資訊。輪廓資訊都儲存在contours_out中。然後根據輪廓擬合成矩形輪廓。但是注意位置資訊儲存的順序不是按照實際的座標位置儲存的,需要重新排序。本文是根據輪廓所在列資訊(x)進行重排。
每個數字連通後,即可進行輪廓提取,找個每個數字的輪廓位置資訊。輪廓資訊都儲存在contours_out中。然後根據輪廓擬合成矩形輪廓。但是注意位置資訊儲存的順序不是按照實際的座標位置儲存的,需要重新排序。本文是根據輪廓所在列資訊(x)進行重排。
vector<vector<Point> > contours_out;
vector<Vec4i> hierarchy;
findContours(image_dil, contours_out, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
// re-arrange location according to the real position in the original image
const size_t size = contours_out.size();
vector<Rect> num_location;
for (int i = 0; i < contours_out.size(); i++)
{
num_location.push_back(boundingRect(Mat(contours_out[i])) );// 轉換為矩形輪廓
}
sort(num_location.begin(), num_location.end(), cmp); // 重排輪廓資訊
bool cmp(const Rect& a, const Rect& b)
{
if (a.x < b.x)
return true;
else
return false;
}
查詢到的輪廓如下圖所示:
在實際的應用場景中,影象不可避免地存在一些噪聲部分,此時噪聲部分也可能被提取出來,因此在得到所有輪廓後還需進行濾波處理,除去噪聲輪廓。上圖中包括噪聲影象為數字3、4以及7、8之間的白色部分,需要濾除。
在實際的應用場景中,影象不可避免地存在一些噪聲部分,此時噪聲部分也可能被提取出來,因此在得到所有輪廓後還需進行濾波處理,除去噪聲輪廓。上圖中包括噪聲影象為數字3、4以及7、8之間的白色部分,需要濾除。
5.數字分割
根據提取的矩形輪廓資訊,可分割出單獨的數字進行識別。
根據提取的矩形輪廓資訊,可分割出單獨的數字進行識別。
for (int i = 0; i < contours_out.size(); i++)
{
if (!IsAllWhite(image_dil(num_location.at(i)))) // 是否為數字
{
tube.push_back(image_dil(num_location.at(i)));
imshow(string(_itoa(tube_num, rectnum, 10)), tube.at(tube_num));
tube_num++;
}
}
分割出的數字效果如下:
三、數字識別
1.穿線法
數字式儀表的數字都是八段數碼管式數字,都是橫平豎直的筆畫,沒有弧度,可以考慮用割線進行識別,原理圖如下。將數字區域(數字1除外)分割成六個部分,掃描個部分的畫素點,判斷該區域內是否存在筆畫(a,b,c,d,e,f,g),最後根據二進位制的規則可推斷出數字的值。除數字1外,剩餘數字分割後圖像長寬比都接近,唯獨數字1影象的長寬比相對要大一些,可設定合理的閾值來確定數字1的影象。
數字式儀表的數字都是八段數碼管式數字,都是橫平豎直的筆畫,沒有弧度,可以考慮用割線進行識別,原理圖如下。將數字區域(數字1除外)分割成六個部分,掃描個部分的畫素點,判斷該區域內是否存在筆畫(a,b,c,d,e,f,g),最後根據二進位制的規則可推斷出數字的值。除數字1外,剩餘數字分割後圖像長寬比都接近,唯獨數字1影象的長寬比相對要大一些,可設定合理的閾值來確定數字1的影象。
int TubeIdentification(Mat inputmat) // 穿線法判斷數碼管a、b、c、d、e、f、g、
{
int tube = 0;
int tubo_roi[7][4] =
{
{ inputmat.rows * 0 / 3, inputmat.rows * 1 / 3, inputmat.cols * 1 / 2, inputmat.cols * 1 / 2 }, // a
{ inputmat.rows * 1 / 3, inputmat.rows * 1 / 3, inputmat.cols * 2 / 3, inputmat.cols - 1 }, // b
{ inputmat.rows * 2 / 3, inputmat.rows * 2 / 3, inputmat.cols * 2 / 3, inputmat.cols - 1 }, // c
{ inputmat.rows * 2 / 3, inputmat.rows - 1 , inputmat.cols * 1 / 2, inputmat.cols * 1 / 2 }, // d
{ inputmat.rows * 2 / 3, inputmat.rows * 2 / 3, inputmat.cols * 0 / 3, inputmat.cols * 1 / 3 }, // e
{ inputmat.rows * 1 / 3, inputmat.rows * 1 / 3, inputmat.cols * 0 / 3, inputmat.cols * 1 / 3 }, // f
{ inputmat.rows * 1 / 3, inputmat.rows * 2 / 3, inputmat.cols * 1 / 2, inputmat.cols * 1 / 2 }, // g
};
if (inputmat.rows / inputmat.cols > 2) // 1 is special, which is much narrower than others
{
tube = 6;
}
else
{
for (int i = 0; i < 7; i++)
{
if (Iswhite(inputmat, tubo_roi[i][0] , tubo_roi[i][1], tubo_roi[i][2], tubo_roi[i][3]))
tube = tube + (int)pow(2, i);
}
}
switch (tube)
{
case 63: return 0; break;
case 6: return 1; break;
case 91: return 2; break;
case 79: return 3; break;
case 102: return 4; break;
case 109: return 5; break;
case 125: return 6; break;
case 7: return 7; break;
case 127: return 8; break;
case 111: return 9; break;
default: return -1;
}
}
2.KNN演算法
使用K近鄰法對數字影象進行分類,若採用此方法,首先需要收集數碼管數字的資料集。需要注意的時,建立KNN模型時,對於資料集進行了一些操作,因此需要對待分類的影象進行相同的操作,否則識別的準確率不高。特別要注意資料集和待識別影象中背景和字型的顏色是否一致。
使用K近鄰法對數字影象進行分類,若採用此方法,首先需要收集數碼管數字的資料集。需要注意的時,建立KNN模型時,對於資料集進行了一些操作,因此需要對待分類的影象進行相同的操作,否則識別的準確率不高。特別要注意資料集和待識別影象中背景和字型的顏色是否一致。
char trainfile[100];
Mat traindata, trainlabel, tmp;
for (int i = 0; i < TRAINDATANUM; i++)
{
sprintf(trainfile, "%s\\%d.jpg", TRAINPATH, i); // TRAINPATH可能需要根據實際修改
tmp = imread(trainfile, IMREAD_GRAYSCALE); // 讀取資料集影象資訊
threshold(tmp, tmp, 50, 255, THRESH_BINARY);
resize(tmp, tmp, Size(NORMWIDTH, NORMHEIGHT));
traindata.push_back(tmp.reshape(0, 1));
trainlabel.push_back(i); // 附件標籤資訊
}
traindata.convertTo(traindata, CV_32F);
int K = 1;
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, trainlabel);
Ptr<KNearest> knn = KNearest::create();
knn->setDefaultK(K);
knn->setIsClassifier(true);
knn->train(tData);
for (int i = 0; i < tube_num; i++)
{
resize(tube.at(i), tube.at(i), Size(NORMWIDTH, NORMHEIGHT));
tube.at(i) = tube.at(i).reshape(0, 1);
tube.at(i).convertTo(tube.at(i), CV_32F);
int r = knn->predict(tube.at(i)); //對所有行進行預測
cout << r << endl;
}
from https://www.itread01.com/content/1546437455.html
下載和配置Opencv在網上和書上有很多的講解,這裡不再贅述。
此處附上Opencv的下載連結。
此處附上Opencv的下載連結。
想要對圖片中的數字資訊進行識別首先要對圖片進行預處理,排除干擾的因素,只留下有價值的資訊。
這裡需要兩張圖,一張為有資料的圖片,一張為儀表關閉時沒有資料的圖片。
這裡需要兩張圖,一張為有資料的圖片,一張為儀表關閉時沒有資料的圖片。
1.原理
儀表數字和背景的區別就是資料會在短時間內會發生變化,這樣在差分二值圖中未變化的背景區域就會被濾除。
2.函式說明
1.cvAbsDiff(const CvArr* scr1, const CvArr* scr2, CvArr *dst)
功能:求兩個圖片(兩幀)的差值
引數1:源圖片1,單通道灰度圖
引數2:源圖片2,單通道灰度圖
引數3:目標影象,得到的差值圖存在*dst中2.cvLoadImage( const char* filename, int flags=CV_LOAD_IMAGE_COLOR )
功能:載入圖片
引數1:源圖片地址
引數2:載入圖片的屬性
常用:
CV_LOAD_IMAGE_UNCHANGED 影象保持原有屬性
CV_LOAD_IMAGE_COLOR 載入影象為三通道3.cvCreateImage(CvSize size, int depth, int channels)
功能:建立圖片
引數1:圖片寬、高
引數2:圖片深度
IPL_DEPTH_8U - 無符號8位整型
IPL_DEPTH_8S - 有符號8位整型
IPL_DEPTH_16U - 無符號16位整型
IPL_DEPTH_16S - 有符號16位整型
IPL_DEPTH_32S - 有符號32位整型
IPL_DEPTH_32F - 單精度浮點數
IPL_DEPTH_64F - 雙精度浮點數
引數3:一個元素的通道數(1、2、3)4.cvCvtColor( const CvArr* src, CvArr* dst, int code )
功能:顏色轉換
引數1:源圖片
引數2:目標圖片
引數3:轉換模式
CV_BGR2GRAY 轉換為灰度圖 (des是單通道圖片)5.cvThreshold( const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type )
功能:圖片二值化(只有黑色和白色)
引數1:src 源圖片,單通道灰度圖
引數2:dst 目標圖片,單通道灰度圖
引數3:threshold 閾值
引數4:max_value 使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。
引數5:threshold_type 閾值型別
CV_THRESH_BINARY =0 大於閾值的畫素設為最大值,小於閾值的設為最小值
CV_THRESH_BINARY_INV =1 大於閾值的畫素設為最小值,小於閾值的畫素設為最大值
CV_THRESH_TRUNC =2 大於閾值的畫素設為閾值,小於閾值的畫素保持原色
CV_THRESH_TOZERO =3 大於閾值的畫素保持原值,小於閾值的畫素設為0
CV_THRESH_TOZERO_INV=4 大於閾值的畫素設為0,小於閾值的畫素保持原值
3.程式碼
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//建立指標
//源影象:
IplImage *SrcImage1=NULL, *SrcImage2=NULL;
//二值圖:
IplImage *BinaryImage=NULL;
//灰度圖:
IplImage *GrayImage1=NULL, *GrayImage2=NULL, *GrayImage3=NULL;
//源圖片載入地址
const char *SrcImageName1="C:\\picture\\biao1.jpg";
const char *SrcImageName2="C:\\picture\\biao2.jpg";
//圖片儲存地址
const char *SaveBinaryPath="C:\\picture\\binary1.jpg";
//影象視窗名稱
const char *BinaryWindowName="二值圖";
int main()
{
//讀取原圖
SrcImage1=cvLoadImage(SrcImageName1,CV_LOAD_IMAGE_UNCHANGED);
SrcImage2=cvLoadImage(SrcImageName2,CV_LOAD_IMAGE_UNCHANGED);
//建立灰度圖
GrayImage1=cvCreateImage(cvGetSize(SrcImage1),IPL_DEPTH_8U,1);
GrayImage2=cvCreateImage(cvGetSize(SrcImage2),IPL_DEPTH_8U,1);
GrayImage3=cvCreateImage(cvGetSize(SrcImage1),IPL_DEPTH_8U,1);
//變成灰度圖
cvCvtColor(SrcImage1,GrayImage1,CV_BGR2GRAY);
cvCvtColor(SrcImage2,GrayImage2,CV_BGR2GRAY);
//檢測差值
cvAbsDiff(GrayImage1,GrayImage2,GrayImage3);
//建立二值圖
BinaryImage=cvCreateImage(cvGetSize(GrayImage3),IPL_DEPTH_8U,1);
//影象二值化
cvThreshold(GrayImage3,BinaryImage,35,255,CV_THRESH_BINARY);
//建立視窗
cvNamedWindow(BinaryWindowName,CV_WINDOW_AUTOSIZE);
//顯示
cvShowImage(BinaryWindowName,BinaryImage);
//儲存
cvSaveImage(SaveBinaryPath,BinaryImage);
//等待按鍵事件
cvWaitKey();
//釋放
cvDestroyWindow(BinaryWindowName);
cvReleaseImage(&SrcImage1);
cvReleaseImage(&SrcImage2);
cvReleaseImage(&BinaryImage);
cvReleaseImage(&GrayImage1);
cvReleaseImage(&GrayImage2);
cvReleaseImage(&GrayImage3);
return 0;
}
留言
張貼留言