找出图片上每条河流的长度(涉及C++读取及写入图片,dfs)
部分代码来源于百度文库(处理图片的部分),点我跳转
一次地图学作业让我突然想到能不能用C/C++来处理图片呢,答案当然时肯定的!于是就打开了一扇新的大门——C++处理图片
原图是这个样子的:
嗯,你没有看错,这是一张水系图。
老师给的解决方案是用圆规一点一点地量出每一小段的距离然后加起来求出每条河流的长度……
这个程序的任务就是也只是找出每条河流的长度,并输出在屏幕上。因为时间有限,我选择了以‘#’来代表像素点输出在控制台界面。思路是这样的:先把图片处理一下,做成单像素矢量图的样子:
工作量不小。。但也没办法,谁让咱不会栅格数据转换成矢量数据的算法呢╮(╯▽╰)╭
因为每条河流的源头都在图片边界上,所以把边界上除了源头把其他所有的像素点都删除,这样只需要遍历一下图片边界就可以找出所有的源头,在递归深搜联通路并记录路程就算出了所有路径所占的像素点数量,依照这些像素点数量就可以大致估算出线条的长度了!耶!( •̀ ω •́ )y
不过。。。
咦?这误差也太大了吧!这明显不符合呀!!怎么回事,算法天衣无缝,怎么会与实际差别那么大呢???
仔细分析后发现:当线条为直线时,确实可以用面积来代替,因为线条宽度为1个像素。但当线条为斜向45度时,面积会远大于长度,因为实际两个像素间的距离是根号二,也就是1.414,而算面积得出的是1+1 = 2
emmmmm,这问题不小
有了!既然这种会会多算,那找一种会少算的算法和它叠加不就可以模拟出正常的长度了吗,比如把图片做成这样:
把斜向的1.414作为1来读取,再把两次得出的数据加起来除以二,最后再转换成A4纸的长和宽就算出了曲线在A4纸上的长度
,精度会高很多。
成果:
线条末尾的数字即为曲线在A4纸上的长度
因为控制台上每个单元的长和宽不相等,所以地图被拉长了好多。压扁后是这样:
代码:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <queue>
#include <cstdio>
#include <cstring>
#define maxn 300
#define inf 0x3f3f3f3f
using namespace std;
/****************************************
*******************************
* 函数名称:
* readBmp()
*
*函数参数:
* char *bmpName -文件名字及路径
*
*返回值:
* 0为失败,1为成功
*
*说明:给定一个图像文件名及其路径,读图像 的位图数据、宽、高、颜色表及每像素
* 位数等数据进内存,存放在相应的全局变量中 ****************************************
*******************************/
unsigned char *pBmpBuf;//读入图像数据的指针
int bmpWidth;//图像的宽
int bmpHeight;//图像的高
RGBQUAD *pColorTable;//颜色表指针
int biBitCount;//图像类型,每像素位数
bool readBmp(char *bmpName) {//二进制读方式打开指定的图像文件
FILE *fp = fopen(bmpName, "rb");
if (fp == 0) return 0;
//跳过位图文件头结构BITMAPFILEHEADER
fseek(fp, sizeof(BITMAPFILEHEADER), 0);
//定义位图信息头结构变量,读取位图信息头进内存, 存放在变量head中
BITMAPINFOHEADER head;
fread(&head, sizeof(BITMAPINFOHEADER), 1, fp);
//获取图像宽、高、每像素所占位数等信息
bmpWidth = head.biWidth;
bmpHeight = head.biHeight;
biBitCount = head.biBitCount;
//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)
int lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4;
//灰度图像有颜色表,且颜色表表项为256
if (biBitCount == 8) { //申请颜色表所需要的空间,读颜色表进内存
pColorTable = new RGBQUAD[256];
fread(pColorTable, sizeof(RGBQUAD), 256, fp);
}
//申请位图数据所需要的空间,读位图数据进内存
pBmpBuf = new unsigned char[lineByte * bmpHeight];
fread(pBmpBuf, 1, lineByte * bmpHeight, fp);
//关闭文件
fclose(fp);
return 1;
}
/*****************************************
* 函数名称:
* saveBmp()
*
*函数参数:
* char *bmpName-文件名字及路径
* unsigned char *imgBuf-待存盘的位图数据
* int width-以像素为单位待存盘位图的宽
* int height-以像素为单位待存盘位图高
* int biBitCount-每像素所占位数
* RGBQUAD *pColorTable-颜色表指针
*返回值:
* 0为失败,1为成功
*
*说明:给定一个图像位图数据、宽、高、颜色表 指针及每像素所占的位数等信息,
* 将其写到指定文件中
******************************************
*****************************/
bool saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height,
int biBitCount, RGBQUAD *pColorTable) {//如果位图数据指针为0,则没有数据传入,函数返回
if (!imgBuf) return 0;
//颜色表大小,以字节为单位,灰度图像颜色表 为1024字节,彩色图像颜色表大小为0
int colorTablesize = 0;
if (biBitCount == 8) colorTablesize = 1024;
//待存储图像数据每行字节数为4的倍数
int lineByte = (width * biBitCount / 8 + 3) / 4 * 4;
//以二进制写的方式打开文件
FILE *fp = fopen(bmpName, "wb");
if (fp == 0) return 0;
//申请位图文件头结构变量,填写文件头信息
BITMAPFILEHEADER fileHead;
fileHead.bfType = 0x4D42;//bmp类型
//bfSize是图像文件4个组成部分之和
fileHead.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
+ colorTablesize + lineByte * height;
fileHead.bfReserved1 = 0;
fileHead.bfReserved2 = 0;
//bfOffBits是图像文件前3个部分所需空间之和
fileHead.bfOffBits = 54 + colorTablesize;
//写文件头进文件
fwrite(&fileHead, sizeof(BITMAPFILEHEADER), 1, fp);
//申请位图信息头结构变量,填写信息头信息
BITMAPINFOHEADER head;
head.biBitCount = biBitCount;
head.biClrImportant = 0;
head.biClrUsed = 0;
head.biCompression = 0;
head.biHeight = height;
head.biPlanes = 1;
head.biSize = 40;
head.biSizeImage = lineByte * height;
head.biWidth = width;
head.biXPelsPerMeter = 0;
head.biYPelsPerMeter = 0;
//写位图信息头进内存
fwrite(&head, sizeof(BITMAPINFOHEADER), 1, fp);
//如果灰度图像,有颜色表,写入文件
if (biBitCount == 8)
fwrite(pColorTable, sizeof(RGBQUAD), 256, fp);
//写位图数据进文件
fwrite(imgBuf, height*lineByte, 1, fp);
//关闭文件
fclose(fp);
return 1;
}
//检测是否是黑点
bool che(int i, int j) {
return *(pBmpBuf + i * ((bmpWidth*biBitCount / 8 + 3) / 4 * 4) + j * 3 + 0) < 200;
}
class poi {
public:
int x, y;//坐标值
double e;//面积即长度
poi() {}
poi(int x, int y) :x(x), y(y) { e = 0; }
};//起点类
int _max_x0 = 0, _max_y0 = 0, _max_x1 = 0, _max_y1 = 0,
_max_num10, _max_num20, _max_num11, _max_num21;//最长的路径的起点坐标和终点坐标
int _max = -inf;//最长路径的长度
vector<vector<poi>> a;//保存起点的队列
int dir[8][2] = {
1, 1,
-1, -1,
1, -1,
-1, 1,
0, 1,
1, 0,
0, -1,
-1, 0
};//方向常量,用于搜寻的方向
bool vis[1000][1000] = { 0 };//标记是否已经来过
////把所有起点入列
//void dfs(int i, int j, int l) {
// if (i >= bmpHeight || j >= bmpWidth || i < 0 || j < 0) return;
// vis[i][j] = 1;
// int f = 0;
// for (int k = 0; k < 4; k++) {
// int x = i + dir[k][0],
// y = j + dir[k][1];
// if (che(x, y) && vis[x][y] == 0) {
// f++;
// dfs(x, y, l);
// }
// }
// if (f == 0) {
// a[l].push_back(poi(i, j));
// }
//}
////从包含所有起点的队列里查找目标点是否是起点
//poi _find(poi tem) {
// for (int x = 0; x < a.size(); x++) {
// for (int y = 0; y < a[x].size(); y++) {
// if (a[x][y].x == tem.x && a[x][y].y == tem.y) {
// return poi(x, y);
// }
// }
// }
// return poi(-1, -1);
//}
int num1, num2;//起点
double dis[maxn][maxn];
//找出每个起点的最长路径长度
void dfs2(int i, int j, int e) {
bool f = 1;
vis[i][j] = 1;
for (int k = 0; k < 8; k++) {
int x = i + dir[k][0],
y = j + dir[k][1];
if (x >= 0 && x < bmpHeight && y >= 0 && y < bmpWidth && che(x, y) && vis[x][y] == 0) {
f = 0;
if (k > 3) {
e++;
dfs2(x, y, e);
e--;
}
else {
e += 1.414;
dfs2(x, y, e);
e -= 1.414;
}
}
}
vis[i][j] = 0;
if (f) {//如果当前位置为终点
dis[i][j] = max(dis[i][j], e);
if (e > _max) {
_max = e;
_max_x0 = num1;
_max_y0 = num2;
_max_x1 = i;
_max_y1 = j;
//_max_num10 = num1;
//_max_num20 = num2;
//poi tem = _find(poi(i, j));
//if (tem.x != -1) {
// _max_num11 = tem.x;
// _max_num21 = tem.y;
//}
}
}
}
void set0(int i, int j, int x) {
*(pBmpBuf + i * ((bmpWidth*biBitCount / 8 + 3) / 4 * 4) + j * 3 + 0) = x;
*(pBmpBuf + i * ((bmpWidth*biBitCount / 8 + 3) / 4 * 4) + j * 3 + 1) = x;
*(pBmpBuf + i * ((bmpWidth*biBitCount / 8 + 3) / 4 * 4) + j * 3 + 2) = x;
}
int main() {
//读入指定BMP文件进内存
char readPath[] = "PIC.BMP";
readBmp(readPath);
//输出图像的信息
printf("width=%d,height=%d,biBitCount=%d\n\n\n", bmpWidth, bmpHeight, biBitCount);
//循环变量,图像的坐标
int i, j;
//每行字节数
int lineByte = (bmpWidth*biBitCount / 8 + 3) / 4 * 4;
////改变线条灰度
//for (i = 0; i < bmpHeight - 1; i++) {
// for (j = 0; j < bmpWidth - 1; j++) {
// if (che(i, j)) {
// *(pBmpBuf + i * lineByte + j * 3 + 0) = 0;//RGB色
// *(pBmpBuf + i * lineByte + j * 3 + 1) = 0;
// *(pBmpBuf + i * lineByte + j * 3 + 2) = 0;
// }
// else {
// *(pBmpBuf + i * lineByte + j * 3 + 0) = 255;
// *(pBmpBuf + i * lineByte + j * 3 + 1) = 255;
// *(pBmpBuf + i * lineByte + j * 3 + 2) = 255;
// }
// }
//}
////遍历图像,深搜寻找路径
//int n = 0;
//for (i = bmpHeight - 1; i >= 0; i--) {
// for (j = 0; j < bmpWidth; j++) {
// if (che(i, j) && (vis[i][j] == 0)) {
// int f = 0;
// for (int k = 0; k < 4; k++) {
// int x = i + dir[k][0],
// y = j + dir[k][1];
// if (*(pBmpBuf + x * lineByte + y * 3 + 0) == 255) f++;
// }
// if (f == 3 && vis[i][j] == 0) {//当前位置是新的起点
// vector<poi> tem;
// tem.push_back(poi(i, j));
// a.push_back(tem);
// dfs(i, j, a.size() - 1);
// }
// }
// }
//}
//for (i = 0; i < a.size(); i++) {
// for (j = 0; j < a[i].size(); j++) {
// memset(vis, 0, sizeof(vis));
// vis[a[i][j].x][a[i][j].y] = 1;
// dfs2(i, j, a[i][j].x, a[i][j].y);
// }
//}
for (i = 1; i < bmpHeight - 1; i++) {
for (j = 1; j < bmpWidth - 1; j++) {
if (che(i, j) && che(i + 1, j) && che(i, j + 1) && !che(i - 1, j - 1)) set0(i, j, 255);
else if (che(i, j) && che(i, j + 1) && che(i + 1, j + 1) && !che(i - 1, j + 2)) set0(i, j + 1, 255);
else if (che(i, j + 1) && che(i + 1, j + 1) && che(i + 1, j) && !che(i + 2, j + 2)) set0(i + 1, j + 1, 255);
else if (che(i, j) && che(i + 1, j) && che(i + 1, j + 1) && !che(i + 2, j - 1)) set0(i + 1, j, 255);
}
}
memset(dis, 0, sizeof(dis));
memset(vis, 0, sizeof(vis));
for (num1 = 0, num2 = 0; num2 < bmpWidth; num2++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = 0, num2 = 0; num1 < bmpHeight; num1++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = bmpHeight - 1, num2 = 0; num2 < bmpWidth; num2++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = 0, num2 = bmpWidth - 1; num1 < bmpHeight; num1++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
freopen("dis.txt", "r", stdin);
for (i = 0; i < maxn; i++) {
for (j = 0; j < maxn; j++) {
int tem;
cin >> tem;
if (dis[i][j] != 0 && tem != 0) dis[i][j] = (dis[i][j] + tem) / 2;
else dis[i][j] = 0;
}
}
int x = 0, y = 0;
printf("最长路径的起点:%d, %d; 终点:%d, %d\n\n\n", _max_x0, _max_y0, _max_x1, _max_y1);
//printf("最长路径的起点:%d, %d; 终点:%d, %d; num10: %d, num20: %d; num11: %d, num21: %d\n\n\n", _max_x0, _max_y0, _max_x1, _max_y1, _max_num10, _max_num20, _max_num11, _max_num21);
//将图像在控制台输出
if(biBitCount == 24) {//彩色图像
for (i = bmpHeight - 1; i >= 0; i--) {
cout << " ";
for (j = 0; j < bmpWidth; j++) {
//for (x = 0; x < a.size(); x++) {
// for (y = 0; y < a[x].size(); y++) {
// if (i == a[x][y].x && j == a[x][y].y) {
// printf("\b\b\b\b\b%2d,%2d", x, y);
// }
// }
//}
if (dis[i][j] != 0) {
double len = 19.5 / 200 * dis[i][j];
if (len < 10) printf("\b\b\b%.1f", len);
else printf("\b\b\b\b%.1f", len);
}
cout << (*(pBmpBuf + i * lineByte + j * 3 + 0) > 200 ? " " : "#");
}
cout << "\n";
}
}
freopen("dis.txt", "w", stdout);
for (i = 0; i < maxn; i++) {
for (j = 0; j < maxn; j++) {
printf("%d ", dis[i][j]);
}
cout << "\n";
}
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 0) = 0;
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 1) = 255;
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 2) = 255;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 0) = 0;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 1) = 0;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 2) = 255;
//将图像数据存盘
char writePath[] = "PICCPY.BMP";
saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);
//清除缓冲区,pBmpBuf和pColorTable是全局变量, 在文件读入时申请的空间
delete []pBmpBuf;
if (biBitCount == 8) delete []pColorTable;
return 0;
}
上面主函数代码是结果大于实际的,下面是结果小于实际的(也就是包含汇总输出的):
int main() {
//读入指定BMP文件进内存
char readPath[] = "PIC.BMP";
readBmp(readPath);
//输出图像的信息
printf("width=%d,height=%d,biBitCount=%d\n\n\n", bmpWidth, bmpHeight, biBitCount);
//循环变量,图像的坐标
int i, j;
//每行字节数
int lineByte = (bmpWidth*biBitCount / 8 + 3) / 4 * 4;
for (i = 1; i < bmpHeight - 1; i++) {
for (j = 1; j < bmpWidth - 1; j++) {
if (che(i, j) && che(i + 1, j) && che(i, j + 1) && !che(i - 1, j - 1)) set0(i, j, 255);
else if (che(i, j) && che(i, j + 1) && che(i + 1, j + 1) && !che(i - 1, j + 2)) set0(i, j + 1, 255);
else if (che(i, j + 1) && che(i + 1, j + 1) && che(i + 1, j) && !che(i + 2, j + 2)) set0(i + 1, j + 1, 255);
else if (che(i, j) && che(i + 1, j) && che(i + 1, j + 1) && !che(i + 2, j - 1)) set0(i + 1, j, 255);
}
}
memset(dis, 0, sizeof(dis));
memset(vis, 0, sizeof(vis));
for (num1 = 0, num2 = 0; num2 < bmpWidth; num2++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = 0, num2 = 0; num1 < bmpHeight; num1++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = bmpHeight - 1, num2 = 0; num2 < bmpWidth; num2++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
for (num1 = 0, num2 = bmpWidth - 1; num1 < bmpHeight; num1++) {
if (che(num1, num2) && vis[num1][num2] == 0) {
vis[num1][num2] = 1;
dfs2(num1, num2, 1);
}
}
freopen("dis.txt", "r", stdin);
for (i = 0; i < maxn; i++) {
for (j = 0; j < maxn; j++) {
int tem;
cin >> tem;
dis[i][j] = (dis[i][j] + tem) / 2;
}
}
int x = 0, y = 0;
printf("最长路径的起点:%d, %d; 终点:%d, %d\n\n\n", _max_x0, _max_y0, _max_x1, _max_y1);
//printf("最长路径的起点:%d, %d; 终点:%d, %d; num10: %d, num20: %d; num11: %d, num21: %d\n\n\n", _max_x0, _max_y0, _max_x1, _max_y1, _max_num10, _max_num20, _max_num11, _max_num21);
//将图像在控制台输出
if(biBitCount == 24) {//彩色图像
for (i = bmpHeight - 1; i >= 0; i--) {
cout << " ";
for (j = 0; j < bmpWidth; j++) {
if (dis[i][j] != 0) {
double len = 19.5 / 200 * dis[i][j];
if (len < 10) printf("\b\b\b%.1f", len);
else printf("\b\b\b\b%.1f", len);
}
cout << (*(pBmpBuf + i * lineByte + j * 3 + 0) > 200 ? " " : "#");
}
cout << "\n";
}
}
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 0) = 0;
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 1) = 255;
*(pBmpBuf + _max_x0 * lineByte + _max_y0 * 3 + 2) = 255;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 0) = 0;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 1) = 0;
*(pBmpBuf + _max_x1 * lineByte + _max_y1 * 3 + 2) = 255;
//将图像数据存盘
char writePath[] = "PICCPY.BMP";
saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);
//清除缓冲区,pBmpBuf和pColorTable是全局变量, 在文件读入时申请的空间
delete []pBmpBuf;
if (biBitCount == 8) delete []pColorTable;
return 0;
}