图像规整化处理
条评论前段时间玩了一下tensorflow觉得用起来还挺方便的,所以准备做一个自己的机器学习的识别系统,正好又一个网络摄像头,也更加方便了,而搞机器学习的首要步骤大概就是样本的选择吧,似乎用Python做图像的处理不太好搞,正好自己又比较熟悉用C++开发,借着这个机会做了一个小工具用以选取训练样本。
操作系统的选择:
由于自己这大半年来一直再ubuntu下开发,也比较熟悉这个系统了,整个系统的配置都比较成熟,当然,如果在win下vs毕竟是最强大的IDE没有之一,但是既然入坑了就不能随便弃坑所以开发的系统选择的是ubuntu16.04。
开源库的选择:
我们的成功当然是建立在巨人的肩膀上,再说了开源库这么多,这么成熟,而且免费而且成熟,不用白不用。
开源库:GDAL;OpenCV开源库的版本暂时没有管,GDAL好象是2.1 OpenCV2.0以上都可以,到了3.0之后就不太了解了。
系统设计:
系统功能设计:系统读取影像并在屏幕上显示出来,鼠标左键点击并拖动能够在影像上绘制矩形,并将矩形单独显示出来,最后再主影像界面点击右键将读取的影像保存到预设的文件夹内。最后将文件夹内所有影像重采样为50×50大小的影像并将其保存为二进制文件方便读写。
1.影像显示:
影像显示作为主体功能要放在首位,在使用OpenCV的情况下,通过imshow函数可以轻松的将影像显示出来,但是在此过程中没有考虑到影像尺寸的问题,在显示过程中如果影像尺寸大于屏幕尺寸,则imshow函数无法将所有影像都显示出来,此时考虑两种解决方案:1.通过滚动条来进行显示;2.通过影像缩放进行显示。这两种方案都存在其优劣,通过滚动条进行显示,可以按比例显示,1:1的显示方便后续的截取工作;通过方案2进行显示显示比较容易,但是后续的截取的过程相对来说会复杂一些。在本工具中使用方案2作为显示方式,而其截取的过程描述为:首先认为缩放前后影像坐上角点的坐标为(0,0),而其他点的坐标则可以根据缩放比例进行计算,具体的计算方法为:当前点的坐标/缩放比例,得到转换后当前点在原始影像上的坐标。由此可以得到选取的未知在原始影像上的坐标。
2.鼠标框选的操作:
鼠标框选的操作通过OpenCV实现起来比较简单,其实现方式为: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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92void on_mouse(int event,int x,int y,int flags,void *ustc)//event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{
static Point pre_pt = Point(-1,-1);//初始坐标
static Point cur_pt = Point(-1,-1);//实时坐标
char temp[16];
if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处划圆
{
sample.copyTo(img);//将原始图片复制到img中
sprintf(temp,"(%d,%d)",x,y);
pre_pt = Point(x,y);
putText(img,temp,pre_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,0,255),1,8);//在窗口上显示坐标
circle(img,pre_pt,2,Scalar(255,0,0,0),CV_FILLED,CV_AA,0);//划圆
imshow("img",img);
}
else if (event == CV_EVENT_MOUSEMOVE && !(flags & CV_EVENT_FLAG_LBUTTON))//左键没有按下的情况下鼠标移动的处理函数
{
sample.copyTo(tmp);//将img复制到临时图像tmp上,用于显示实时坐标
sprintf(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(tmp,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,0,255));//只是实时显示鼠标移动的坐标
imshow("img",tmp);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))//左键按下时,鼠标移动,则在图像上划矩形
{
sample.copyTo(tmp);
sprintf(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(tmp,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,0,255));
rectangle(tmp,pre_pt,cur_pt,Scalar(0,255,0,0),1,8,0);//在临时图像上实时显示鼠标拖动时形成的矩形
imshow("img",tmp);
}
else if (event == CV_EVENT_LBUTTONUP)//左键松开,将在图像上划矩形
{
sample.copyTo(img);
sprintf(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(img,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,0,255));
circle(img,pre_pt,2,Scalar(255,0,0,0),CV_FILLED,CV_AA,0);
rectangle(img,pre_pt,cur_pt,Scalar(0,255,0,0),1,8,0);//根据初始点和结束点,将矩形画到img上
imshow("img",img);
sample.copyTo(tmp);
//截取矩形包围的图像,并保存到dst中
int width = abs(pre_pt.x - cur_pt.x)/xscale;
int height = abs(pre_pt.y - cur_pt.y)/yscale;
if (width == 0 || height == 0)
{
printf("width == 0 || height == 0");
return;
}
dst = org(Rect(min(cur_pt.x,pre_pt.x)/xscale,min(cur_pt.y,pre_pt.y)/yscale,width,height));
namedWindow("dst");
imshow("dst",dst);
}
if (event == CV_EVENT_RBUTTONDOWN)
{
if(dst.empty())
return ;
else
{ //按下回车键保存
char temp[20];
char tempdesc[20]="descriptor.desc";
char path[256];
char pathdesc[256];
strcpy(path,dir);
strcpy(pathdesc,dir);
count++;
sprintf(temp,"%d.jpg",count);
strcat(path,temp);
strcat(pathdesc,tempdesc);
if(!imwrite(path,dst)){
printf("影像写入文件 %s 失败!\n",path);
count--;
}
else
{
printf("影像写入文件 %s 成功!\n",path);
FILE *fptr=fopen(pathdesc,"a+");
fprintf(fptr,"%s\n",path);
fclose(fptr);
}
cv::destroyWindow("dst");
dst.release();
}
}
}
下面我们对上述代码进行详细的分析,首先需要两个点,一个存储鼠标左键点击下去的那个点,一个存储当前鼠标所在的位置,然后进行绘制,在选取的过程中img为当前显示的图像,由于需要实时绘制坐标,所以需要通过putText函数进行实时绘制,此时图像为tmp,最后得到图像为dst将其写入文件中。实际上每次绘制一个矩形都是一幅新图像,所以在处理过程中可能出现刷新不及时或者出现闪烁的现象,毕竟不是双缓存但是刷新频率很高所以也不太能够看出来,还是可以接受的。
- 3.图像的转换
此功能是通过GDAL库实现的,其实现代码为: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
64FILE* fcptr=fopen(fcatalog,"r+");
if(fcptr==NULL)
return false;
if(fbinnary==NULL)
return false;
FILE* fbptr=fopen(fbinnary,"wb+");
GDALAllRegister();
do{
char img[256];
fgets(img,256,fcptr);
if(img=="")
continue;
printf("转换影像:%s",img);
int len = strlen(img);
char* p = new char[len];
for(int i=0;i<len-1;++i)
p[i]=img[i];
p[len-1]='\0';
GDALDatasetH m_dataset = GDALOpen(p,GA_ReadOnly);
int xsize = GDALGetRasterXSize(m_dataset);
int ysize = GDALGetRasterYSize(m_dataset);
int bands = GDALGetRasterCount(m_dataset);
int cxsize = 50;
int cyszie = 50;
int *img_data = new int[cxsize*cyszie];
int bandIdx = 1;
//三个波段
/*
if(bands>=3)
{
for (int i = 0; i < 3; ++i) {
GDALRasterIO(GDALGetRasterBand(m_dataset,bandIdx),GF_Read,0,0,xsize,ysize,img_data,cxsize,cyszie,GDT_Int32,0,0);
fwrite(img_data,sizeof(int),cxsize*cyszie,fbptr);
bandIdx++;
}
}
else if(bands==2){
for (int i = 0; i < 3; ++i) {
GDALRasterIO(GDALGetRasterBand(m_dataset,bandIdx),GF_Read,0,0,xsize,ysize,img_data,cxsize,cyszie,GDT_Int32,0,0);
fwrite(img_data,sizeof(int),cxsize*cyszie,fbptr);
bandIdx++;
if(bandIdx>bands)
bandIdx=bandIdx%bands;
}
} else{
for (int i = 0; i < 3; ++i) {
GDALRasterIO(GDALGetRasterBand(m_dataset,bandIdx),GF_Read,0,0,xsize,ysize,img_data,cxsize,cyszie,GDT_Int32,0,0);
fwrite(img_data,sizeof(int),cxsize*cyszie,fbptr);
}
}
*/
//一个波段
GDALRasterIO(GDALGetRasterBand(m_dataset,bandIdx),GF_Read,0,0,xsize,ysize,img_data,cxsize,cyszie,GDT_Int32,0,0);
fwrite(img_data,sizeof(int),cxsize*cyszie,fbptr);
delete []img_data;img_data=NULL;
delete []p;p=NULL;
}while(!feof(fcptr));
return true;
实现过程很简单,通过GDAL读取影像,通过IO进行重采样,最后写入文件,其中又一个小问题,在linux下fgets读取一行读取了最后的换行符,所以一开始总是提示路径有错误,最后重新新建数组完成了数据读取的问题。全部代码为:
1 |
|
程序已经上传到了Git中Git地址为:https://github.com/RemoteSensingFrank/ImageROICut.git
v1.5.2