前段时间玩了一下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
    92
    void 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
    64
    FILE* 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
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#include <iostream>

#include "gdal_priv.h"
#include <opencv2/opencv.hpp>
#include<gtest/gtest.h>

using namespace cv;

cv::Mat org,sample,dst,img,tmp;

int count = 0;
float xscale = 1.0f;
float yscale = 1.0f;
char dir[128] = "/home/wuwei/Picture/Cut/";

bool CVLibraryTest()
{
Mat img = imread("/home/wuwei/Picture/bridge_ly.jpg");
if(img.empty())
return false;
else
return true;
}
bool GDALLibraryTest()
{
GDALAllRegister();
GDALDatasetH m_dataset = GDALOpen("/home/wuwei/Picture/bridge_ly.jpg",GA_ReadOnly);
if(m_dataset!=NULL)
return true;
else
return false;
}

TEST(TestLibrary, ALLLibTest)
{
EXPECT_EQ(true,CVLibraryTest());
EXPECT_EQ(true,GDALLibraryTest());
}

/**
* 鼠标点击世间
* @param event
* @param x
* @param y
* @param flags
* @param ustc
*/
void 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();
}
}

}

/*
* 文件夹中文件编目情况获取
*/
bool file_catalog(char* file)
{
FILE *fptr=fopen(file,"r+");
if(fptr==NULL)
return false;
char line[256];
int num=0;
do{
fgets(line,256,fptr);
if(line!="")
num++;
}while(!feof(fptr));
count = num;
return true;
}

/*
* 将影像文件转换为二进制码
*/
bool tranImageToBinnary(char* fcatalog, char* fbinnary)
{
FILE* 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;
}

int main()
{
//首先读取文件夹中记录的文件信息,确定编号
char* fcatalog = "/home/wuwei/Picture/Cut/descriptor.desc";
char* imgpath = "/home/wuwei/Picture/bridge_ly.jpg";
char* bin = "/home/wuwei/Picture/data.bin";

/*file_catalog(fcatalog);
org = imread(imgpath);
if(org.empty())
{
return -1;
}

//判断是否需要重采样
int xsize = 1080;
int ysize = 768;
if(org.cols>1080)
xscale = float(xsize)/float(org.cols);
if(org.rows>768)
yscale = float(ysize)/float(org.rows);

//重采样
resize(org,sample,Size(0,0),xscale,yscale);
sample.copyTo(img);
sample.copyTo(tmp);
namedWindow("img");//定义一个img窗口
setMouseCallback("img",on_mouse,0);//调用回调函数
imshow("img",sample);
cv::waitKey(0);*/

//转换
printf("影像重采样并转换为二进制文件...\n");
tranImageToBinnary(fcatalog,bin);
printf("转换完成!\n");
return 0;
}

程序已经上传到了Git中Git地址为:https://github.com/RemoteSensingFrank/ImageROICut.git