引子:

做了一个实时展示的小程序,一开始做的比较粗糙,每次呢,也就是每天的早上,都需要手动去执行,然后用鼠标去一点点调整展示窗口的大小和排列,一共三个窗口,一大两小,排列方式是固定的,但是由于每次都是一点点拖动摆放,因此每一次自己都需要对排列后的样子有一个预期,尽可能调整成看上去还不错的样子,尽管如此这般,也很难做到每次的排列都相同。做到这里,虽然没用到多少时间,几分钟吧,但耗费了不少注意力,因为每天都要想着:早上的第一件事是做这个,而且很有可能哪一天就忘了。晚上的话,还要想着去关掉它,手动关闭,费不了多少时间,但同时消耗了不少注意力,原因和早上的一样:一整天的潜意识里都会挂念着,晚上了得把它关掉。如此,一日复一天,天天如此,重复着同样的工作,而且似乎并没有意识到什么问题……

都2024年了啊!还是个会敲代码的,天天就干这些事?不知道让这个小程序自己打开再自己关掉嘛?时间一直往前走,怎么写代码的意识还不如从前了呢!

是啊,已经不知不觉陷入了一种“忙忙碌碌,不思进取,每天做着同样的事,为了活着而活着的”行尸走肉之中了。一天,两天,一周,一个月,大半年,就是这么混过来的,有进步嘛?有一点,但仔细瞧瞧只不过是错觉,很可能还是个负方向的。有反思嘛?好像有过,但借口一句没时间啊就过去了,正如此时敲击着键盘,如果现在不写,那么可能又是,一天过去,一周过去,一个月过去,于是乎,2025年2月20日啦。确实很打脸,因为上一次想要写写文字反思的时候,是2023年2月18日,而当下此刻,是2024年2月19日。。。

什么时候种树都不迟,只要你意识到了就抡起铲子!也许当下的形势让你觉得自己的所学一文不值,那么就立刻开始,多学习点,想学的就去学,从自己的所爱出发,而不是犹豫、惆怅,还徘徊……

说回来了,原本这一篇记录是想偏向于“自学的总结”,会涉及一些比较“专业”的东西,一些代码啊,一些指令啥的,昨晚借口一句:“困了先睡觉了”,就没写。欺骗自己说早上醒来写,实际上自己心里很清楚,大概率不可能。人的惰性太自然了,躺着多舒服啊,但如果一直躺着,那不如成为一具尸首。

正篇:意识到了就立刻行动

我知道此前的我,也是成了“每天重复做着同样工作”的讨厌样子了。

新的一年,那就由此改变吧!

实时展示的小程序是吧,加一个自动启动和退出的功能咯!怎么做呢?

我最开始的想法是将这个小程序软件化,也就是使用QT的可视化图形界面来改写,无奈,尝试了大半天,代码编译看着没问题了,但用不起来,这很大原因和自己没有系统学习有关。现在的chatGPT啊,写代码很厉害了,可能目前我的水平还比较低,用不好它,以至于说是让它辅助我改写,实际上我很多时候并不过脑子,直接复制粘贴,那哪行哦!这不就成了工具的奴隶了,连工具都不如了,这不更容易被食物链中的腐食者给吃了嘛。

于是,我开始想,能不能通过一些技巧来实现我的需求呢?这些技巧背后依赖的功能应该是存在的,但是我不知道,此时我就是可以通过chatGPT协助了。

  • 现在是三个窗口(此前实现的时候,使用了多线程,因此同时展示的问题是解决的),我能不能把窗口数量减少,比如两个小窗口直接合成成一个?
  • 之前每次手动打开都需要手动调整窗口大小和位置,能不能直接在代码中实现预先的设置呢?
  • 自动打开,Linux上似乎有守护进程可以?但是没有在这里尝试过(这个也是很古老的知识了,至少六年前我就用过),想到过,但是没有行动起来!!
  • 自动关闭呢?之前设置了通过键盘输入q实现程序的退出,能不能让Linux自动模拟键入q呢?

按照这个思路我就去做了~

合并窗口:将思维打开,不要局限

一开始,我是用两个窗口来同时显示两个图像的,最简单也是最笨的操作。为啥之前我就没有想到将两个图像放在一个窗口里呢?

  • 可能是思维惯性,之前怎么做的,就跟着怎么做了。
  • 也可能就是偷懒,从而导致的不动脑子,以及相应会产生的拖延。

认识到自己的思维存在僵化之后,开始改变:

首先想到,直接拼接两张图像,上下拼接就能完成。但是在显示上不友好,上下拼接后变成一个长方形了,但是由于显示器很大,把窗口拉长之后上下图居中在一起,上下有空白的,显得很丑,从美观的角度来看的话,如果两个拼接的图像在上下位置能够保证对称,比如上方、两图中间、下方各自留出一块空间,这样就看着舒服了。按照这个思路,那就改~

直接拼接不成,那就先考虑创建一个大的窗口,然后将两张图像分别搁进去,具体操作起来特别像前端的UI设计,要考虑padding的填充。这样的方法可行,然后还需要加入图像的标题,也采用往大窗口里搁置的思路,同样的,还是考虑了要设置padding的填充,使得标题能够局中。之后就是根据实际的情况,不断地调整窗口的尺寸,padding的大小,以达到自认为满意的比例了。至此,合并窗口完成了,现在的小程序只需要两个窗口就能满足了。

代码备忘录📝

cv::Mat merged_image;
if (left_image_color_global && !left_image_color_global->empty() && disparity_color_global && !disparity_color_global->empty())
{
    int space = 25;                      // 标题和图像之间的空间
    int test_height = 50;                // 文本标题的高度(大致估计)
    cv::Scalar textColor(255, 255, 255); // 文本颜色
    int font_face = cv::FONT_HERSHEY_SIMPLEX;
    double font_scale = 1;
    int thickness = 2;
    int top_padding = 20;
    int between_padding = 60;
    int bottom_padding = 20;

    int merged_height = left_image_color_global->rows + disparity_color_global->rows + 3 * space + 2 * test_height + top_padding + bottom_padding + between_padding;
    int merged_width = std::max(left_image_color_global->cols, disparity_color_global->cols);
    merged_image = cv::Mat::zeros(merged_height, merged_width, left_image_color_global->type());

    // 绘制第一个图像和标题
    int title1_x = (merged_width - cv::getTextSize("Left Image", font_face, font_scale, thickness, nullptr).width) / 2;
    cv::putText(merged_image, "Left Image", cv::Point(title1_x, test_height), font_face, font_scale, textColor, thickness);
    left_image_color_global->copyTo(merged_image(cv::Rect(0, top_padding + test_height + space, left_image_color_global->cols, left_image_color_global->rows)));

    // 绘制第二个图像和标题
    int title2_x = (merged_width - cv::getTextSize("Dense Disparity", font_face, font_scale, thickness, nullptr).width) / 2;
    int merged_mid = left_image_color_global->rows + 2 * space + test_height + top_padding;
    cv::putText(merged_image, "Dense Disparity", cv::Point(title2_x, merged_mid + test_height), font_face, font_scale, textColor, thickness);
    disparity_color_global->copyTo(merged_image(cv::Rect(0, merged_mid + bottom_padding + test_height + space, disparity_color_global->cols, disparity_color_global->rows)));
}

提前设置窗口位置:不知道的不代表没有,以前是搜索,现在是“问”

设置好窗口大小之后,还需要能够实现打开小程序后,两个窗口能够自动“站”到合适的位置。应该是有这样的方法的,只不过是我不知道,在没有chatGPT之前,可以用Google进行搜索,现在有了chatGPT,只要将需求描述清楚,一般互联网上已有的信息就能够获取到。因此我就直接“问”了有没有这样的方法,果然,有的,而且还很简洁。

忽然想到一点,这个不就是信息差嘛!以前靠信息差吃某一种职位铁饭碗的想法应该在此之后自己消亡了吧?也许不会,因为好逸恶劳,因为懒惰,因为路径依赖?总之,现在自己意识到了还不算迟。

所以,很可能以后还是很缺“产品经理、项目经理”这种明确知道需求是什么的,能够用精炼的语言将其描述清楚的总揽全局的人,而不缺重复的、低级的、复制粘贴的所谓“程序员”码农。

代码备忘录📝

cv::namedWindow("Color & Disparity", cv::WINDOW_NORMAL);
cv::resizeWindow("Color & Disparity", 1548, 2160);
cv::moveWindow("Color & Disparity", 0, 0);  // 将窗口移动到左上角

// ----------------------------------------------------------
// 创建点云可视化器
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("Dense PointCloud Viewer"));
viewer->setBackgroundColor(0, 0, 0);
viewer->addPointCloud<pcl::PointXYZRGB>(point_cloud_ptr_global, "Dense PointCloud");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "Dense PointCloud");
viewer->initCameraParameters();
viewer->setCameraPosition(
    0, 0, -1.5,         // 控制相机的XYZ位置
    0, 0.25, 1, 
  	0, 1, 0 						// 后面点云转了旋转,这里Y为1
);
viewer->setSize(2220, 2160);
// 因为使用了VTK的窗口
vtkSmartPointer<vtkRenderWindow> renderWindow = viewer->getRenderWindow();
renderWindow->SetPosition(1900, 0);

自启动:不懂的就多探究,不要是一个“半瓶水响叮当”

之前知道一种方法是用守护进程:通过crontab -e进行设置,我也用这个方法尝试了,发现不起作用。分析原因得到:

因为,我启动的小程序需要调用显示窗口,OpenCV提供的简易窗口和VTK提供的可视化窗口,而守护进程启动命令时,是一种“文本”方式,无法唤起显示的窗口。

**以前在服务器上用crontab自启动一些命令,因为只会有文本输出,比较简易,那会儿使用远程服务器,自然也不会涉及显示,也就忽略了这个问题。**所以,我此前对于守护进程的理解还只是皮毛,今天多了一些使用的经验。但更重要的是从这次的经历中学会一些“遇到问题、分析现象、思考解决”的思维。

发现这条路走不通,于是,将自己的需求重新整理了一遍:

“我希望在当前显示器下,当前的命令行终端下,定时地执行某一个命令”

在这个思路下,找到了一个可行的方式:

在终端中,持续运行一个判断:获取当前时间,判断是否等于设定的需要自动打开的时间,如果是就执行命令,然后之后一段时间里不再执行。

在Linux环境下,这个判断可以比较直接的实现:通过Bash脚本的方式。(本质就是Bash脚本自动化)

这里明白了一点,一条路走不通很可能是这路条本身就不对,因为很多时候对一项事物也是处于一种了解皮毛的状态,所以遇到问题了要学会打破“自以为是”,从整体的层面来进行分析,对于这里的问题就是:“守护进程不行,为啥不行,那我把运行的日志打印出来,然后分析一下错误信息,再考虑这种方法是不是本身就不支持。”

代码备忘录📝

#!/bin/bash
TARGET_TIME="09:00"

while true; do
    CURRENT_TIME=$(date +%H:%M)
    if [ "$CURRENT_TIME" == "$TARGET_TIME" ]; then
        /home/metoak/Projects/RealTimeDensePointCloud/C++/build/tensorrt_inference_one_camera /home/metoak/Projects/RealTimeDensePointCloud/C++/models/model_mobilev2_960_512.trt 960 512
        sleep 60  # 避免在目标时间多次执行
    fi
    sleep 30  # 每30秒检查一次时间
done

自关闭:脑子是越用越灵光的,多想想,不要固化,不要陷入路径依赖

在这里遇到的问题和自启动时差不多。在最开始实现时,也想到了要实现手动关闭,所以就设置了一个通过键盘关闭的方法。我在想能不能自动关闭时,也就顺着这个思路走下去了:“能不能用指令来模拟键盘输入,来达到跟手动按下键盘一样的效果呢?

想法是好的,也通过chatGPT进行“询问”和尝试,是有这种方法的,但是无法应用在这个问题的解决上。原因简单分析如下:

通过一个指令或者一个程序模拟键盘输入,它所生效的位置是这个指令或者程序运行的空间;而我通过键盘控制关闭时,实际上是做了两件事:一、先选中窗口,二、按下键盘上的q键;模拟键盘输入可以完成第二步,但是完成不了第一步,因此这种方法在这里不起作用。

至少,我在思考问题上,会多想一步了。但也陷进了路径依赖中!

想让程序自己关闭的最快捷方式难道不应该是在程序内部实现嘛?而且很方便的只需要一个判断就行了,不然要你编码做何用?

是啊,我忘记了,整个演示程序的代码都是我写的,那我想控制什么时候关闭,不是很容易嘛!(什么时候开启不太行,但用了上面的持续运行判断是可以的,因为开启需要从外界开启。断电的电脑怎么自动开机呢?但开机的电脑可以设置定时关闭,如此常识,自己之前咋就忘了呢。。。)

所以,有时候可以让自己适当放空,然后重新梳理,一步一步来分析解决。

路径依赖,说到底也是因为懒,能不能打破懒,还得看自己够不够狠心和韧性了,愈挫愈勇,这个方法不行,大不了从头来,用新的思路来解决!

代码备忘录📝

// 获取当前时间
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm = *std::localtime(&now_c);

// 检查时间是否超过19:00
if (now_tm.tm_hour >= 19) {
    std::cout << "Current time is " << std::put_time(&now_tm, "%F %T") << ", the machine need to rest, See you tomorrow!" << std::endl;
    running = false;
}

// 原来的手动关闭操作
int key = cv::waitKey(1);
if (key == 'q' || key == 'Q') {
    running = false;
}

结束语:继续坚持,从1到2到3,到100,到一直能坚持

人类最可贵的,不就是会思考嘛,机器智能只会越来越强盛的今后,尤其显得珍贵!

继续坚持,明天再继续~

2024年2月19日