前面文章中使用LKH算法解决TSP问题我已详细说明,我又重新想了一种使用LKH算法的创新,就是能否使用这个算法来实现一个图像处理,将照片生成点图,然后使用LKH算法进行连接,真正实现一笔画。
理论是成立的,接下来就是实践,我的思路如下:首先我们需要一张图片,然后将这个图片进行灰度处理,定义一个结构体来存储生成的点,在照片中生成随机点,如果点的位置的灰度值大于某一个值,就记录到结构体中,为了避免重复点生成,我们还需要再做一个判断,判断这个点的位置和已生成点的位置是否很近,如果距离很近,则重新生成。这里可以借鉴我前面发表的锤画代码,当生成的点足够密集,我们需要将这些点使用LKH算法连接即可。
为了能够很好地展示这个算法的效果这里使用罗中立的油画父亲。
生成点图如下
点图存在的问题就是生成的密集地方点重合了或者离得太近,这里进行代码的重新调整。
通过控制生成点与点之间的距离,可以看到生成的效果明显有所改善。
接下来就可以使用生成的点连接起来,看看效果。
然后生成了8000多点开始解算最短路径!
漫长的等待中。。。
经过漫长的等待,总算是出来结果了。
结果虽然很抽象,但是可以说是智慧的结晶。
///////////////////////////////////////////////////
// 程序名称:LKH算法生成的图像处理
// 编译环境:Mictosoft Visual Studio 2022, EasyX_20200315(beta)
// 作 者:luoyh <2864292458@qq.com>(V:louyh1207)
// 公 众 号:C语言研究
// 最后修改:2024-7-9
//
typedef struct
{
int x;
int y;
} Point;
// 计算两点之间的距离
double distance(Point p1, Point p2)
{
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
// 检查新点是否与已有点距离很近
int isClose(Point newPoint, Point* points, int numPoints)
{
for (int i = 0; i < numPoints; i++)
{
if (distance(newPoint, points[i]) < CLOSE_THRESHOLD)
{
return 1; // 表示很近
}
}
return 0; // 表示不近
}
// 彩色图像转换为灰度图像
void ColorToGray(IMAGE* pimg)
{
DWORD* p = GetImageBuffer(pimg); // 获取显示缓冲区指针
COLORREF c;
// 逐个像素点读取计算
for (int i = pimg->getwidth() * pimg->getheight() - 1; i >= 0; i--)
{
c = BGR(p[i]);
c = (GetRValue(c) * 299 + GetGValue(c) * 587 + GetBValue(c) * 114 + 500) / 1000;
p[i] = RGB(c, c, c); // 由于是灰度值,无需再执行 BGR 转换
}
}
int H;
int W;
void generateRandomCoordinate(int* x, int* y)
{
*x = rand() % W;
*y = rand() % H;
}
int GetColorNumber(int x, int y, IMAGE* pimg)
{
DWORD* p = GetImageBuffer(pimg); // 获取显示缓冲区指针
COLORREF c;
c = BGR(p[y * W + x]);
int X = GetRValue(c);
return X;
}
int RGBNUMS[256];
bool isover[256];
void GetRGBNUMS()
{
int MaxNum = 15000;
RGBNUMS[0] = 0;
for (int i = 0; i < 256; i++)
{
RGBNUMS[i] = RGBNUMS[i - 1] + (MaxNum - RGBNUMS[i - 1]) / 11000;
}
}
bool is_over()
{
for (int i = 1; i < 256; i++)
{
if (isover[i] == false)
{
return false;
}
}
return true;
}
int* tour = NULL;
void WriteBat()
{
const char* batContent = "@echo off\r\npath=%path%;\r\nLKH-3.exe Par.par\r\n";
const char* fileName = "LKH.bat";
FILE* file = fopen(fileName, "w");
if (file == NULL)
{
perror("无法打开文件");
return;
}
fprintf(file, "%s", batContent);
fclose(file);
}
void WritePar()
{
const char* parContent =
"PROBLEM_FILE = Tsp.tsp\r\n"
"MOVE_TYPE = 5\r\n"
"PATCHING_C = 3\r\n"
"PATCHING_A = 2\r\n"
"RUNS = 1\r\n"
"TOUR_FILE = ./output.txt\r\n";
const char* fileName = "Par.par";
FILE* file = fopen(fileName, "w");
if (file == NULL)
{
perror("Failed to open file");
return;
}
fprintf(file, "%s", parContent);
fclose(file);
}
void WriteTsp(Point* Photo_list, int Photo_list_size)
{
int POINTS = Photo_list_size;
char tspContent[409600]; // 假设内容不会超过4096个字符
char buffer[256];
strcpy(tspContent, "NAME : TSPDATA\r\n");
strcat(tspContent, "COMMENT : drilling problem (Ludwig)\r\n");
strcat(tspContent, "TYPE : TSP\r\n");
sprintf(buffer, "DIMENSION : %d\r\n", POINTS);
strcat(tspContent, buffer);
strcat(tspContent, "EDGE_WEIGHT_TYPE : EUC_2D\r\n");
strcat(tspContent, "NODE_COORD_SECTION\r\n");
for (int i = 0; i < Photo_list_size; i++)
{
sprintf(buffer, "%d %d %d\r\n", i + 1, Photo_list[i].x, Photo_list[i].y);
strcat(tspContent, buffer);
}
strcat(tspContent, "EOF\r\n");
const char* fileName = "Tsp.tsp";
FILE* file = fopen(fileName, "w");
if (file == NULL)
{
perror("Failed to open file");
return;
}
fprintf(file, "%s", tspContent);
fclose(file);
}
void StartProcess(const char* filePath)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE; // 不显示窗口
ZeroMemory(&pi, sizeof(pi));
// 创建进程
if (!CreateProcessA
(
filePath, // 程序路径
NULL, // 命令行参数
NULL, // 进程句柄不可继承
NULL, // 线程句柄不可继承
FALSE, // 设置句柄继承为 FALSE
0, // 没有创建标志
NULL, // 使用父进程的环境块
NULL, // 使用父进程的起始目录
&si, // 指向 STARTUPINFO 结构
&pi) // 指向 PROCESS_INFORMATION 结构
) {
printf("CreateProcess failed (%d).\n", GetLastError());
return;
}
// 等待直到子进程退出
WaitForSingleObject(pi.hProcess, INFINITE);
// 关闭进程和线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int readtxt(const char* filePath)
{
FILE* file;
char line[1024];
int dimension;
int tourSize = 0;
int temp;
// 打开文件
file = fopen(filePath, "r");
if (!file)
{
perror("Failed to open file");
return EXIT_FAILURE;
}
// 跳过文件头信息
while (fgets(line, sizeof(line), file))
{
if (strncmp(line, "DIMENSION : ", 12) == 0)
{
sscanf(line, "DIMENSION : %d", &dimension);
tour = (int*)malloc(sizeof(int) * dimension);
if (!tour)
{
perror("Failed to allocate memory for tour");
fclose(file);
return EXIT_FAILURE;
}
}
else if (strncmp(line, "TOUR_SECTION", 12) == 0)
{
break; // 找到TOUR_SECTION,开始读取路径数据
}
}
// 读取路径数据
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "%d", &temp) == 1 && temp != -1) {
tour[tourSize++] = temp;
}
else if (temp == -1) {
break; // 读到-1表示路径结束
}
}
// 输出读取的路径数据
for (int i = 0; i < tourSize; i++)
{
printf("%d\n", tour[i]);
}
fclose(file);
}
int main()
{
// 加载图片
IMAGE img;
loadimage(&img, _T("AA.jpeg"));
H = img.getheight();
W = img.getwidth();
initgraph(W, H, SHOWCONSOLE);
_getch();
//setbkcolor(WHITE);
//cleardevice();
ColorToGray(&img);
GetRGBNUMS();
int TJRGBNUMS[256];
for (int i = 0; i < 256; i++)
{
TJRGBNUMS[i] = 0;
isover[i] = false;
}
int NUMS = 0;
// 写一个获取位置的函数
Point* points = NULL; // 初始化为NULL,表示还没有分配内存
int numPoints = 0; // 当前存储的点的数量
int maxPoints = 100; // 初始分配的点数,可以根据需要调整
srand(time(NULL)); // 初始化随机数生成器
// 分配初始内存
points = (Point*)malloc(maxPoints * sizeof(Point));
if (points == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
while (true)
{
int Sand_x;
int Sand_y;
generateRandomCoordinate(&Sand_x, &Sand_y); // 获取这个地方的X,Y;
// 将这个地方的XY带入到影像中,看他的值为多少
int ColorNumber;
ColorNumber = GetColorNumber(Sand_x, Sand_y, &img);
// 分段赋值
TJRGBNUMS[ColorNumber]++;
if (TJRGBNUMS[ColorNumber] < RGBNUMS[ColorNumber])
{
if (ColorNumber > 20)
{
Point newPoint;
newPoint.x = Sand_x; // 生成0到100之间的随机数
newPoint.y = Sand_y;
if (!isClose(newPoint, points, numPoints))
{
if (numPoints >= maxPoints)
{
// 如果已经达到最大点数,重新分配内存
maxPoints *= 2; // 可以根据需要调整增长策略
Point* temp = (Point*)realloc(points, maxPoints * sizeof(Point));
if (temp == NULL)
{
fprintf(stderr, "Memory reallocation failed\n");
free(points); // 释放原有内存
return 1;
}
points = temp;
}
points[numPoints++] = newPoint; // 存储新点
putpixel(Sand_x, Sand_y, WHITE);
}
}
}
else
{
isover[ColorNumber] = true;
}
if (is_over())
{
break;
}
if (NUMS > H * W)
{
break;
}
NUMS++;
}
WriteBat();
WritePar();
int Photo_list_size = numPoints;
WriteTsp(points, Photo_list_size);
StartProcess("LKH.bat");
readtxt("output.txt");
setlinecolor(WHITE);
setlinestyle(PS_SOLID, 1);
for (int i = 0; i < numPoints - 1; i++)
{
line(points[tour[i] - 1].x, points[tour[i] - 1].y, points[tour[i + 1] - 1].x, points[tour[i + 1] - 1].y);
Sleep(10);
}
// 首尾相连
//line(mypoints[tour[100] - 1].X, mypoints[tour[100] - 1].Y, mypoints[tour[0] - 1].X, mypoints[tour[0] - 1].Y);
saveimage(_T("a.jpg"));
_getch();
closegraph();
}