智能水表项目 - 记一次完整的修改日志
一、前言
写给亲爱的师弟鹿同学,这是一份关于增加梅花针和指针精搜索功能的开发日志文档,希望你尽快接手这个项目,要不然师兄的论文真的就来不及啦!
二、准备工作
2.1 明确需求
在做动笔改代码之前,第一件事情就是明确我们的需求是什么,我近期简单整理了一下需要修改的内容,放到了 README.md中,这个文件相当于github的欢迎页,打开网站就能看到。
今天我们主要完成梅花针和指针的精定位功能。
2.2 梳理思路
在原有的代码中,前端web页面标记了梅花针和指针的大致位置之后,将相关的位置坐标写入到了我们的配置文件中,如:
1 | |
这里面code_x1,code_x2表示二维码的左上和右下两个角点,meihua_x meihua_y则分别代表了可视化标记时的圆心、半径等信息。因此,我们的需要读取这部分的值和图像,然后进行圆搜索或矩形搜索,再将值写回配置文件中。
那么接下来我们需要提纲挈领的思考一下整体的设计:
Q: 在哪执行这个操作?
很明显我们应该在程序初始化时执行一次这样的操作。后续我们会和甲方沟通,在执行一次测量之前,先执行一次初始化(init),再开始(start)。接下来我将展示如何以API层为突破口看懂整个程序的。
既然是在初始化部分添加内容,当然不能像无头苍蝇一样在整个程序搜索init。从业务逻辑或者说人的直觉也好,都应该从API层作为入口进行思考。阅读app/web/api_handlers搜索init,或者看我之前写的接口文档也好,不难找到执行init的代码:
1 | |
这里需要稍微一点点Fastapi、装饰器、异步、路由、try语法的知识,不过不用太探究,你只需要了解到:函数init_measurement绑定到了api/init上,当外部执行POST http:// { 你的ip }:8000/api/init时,会调用这个函数。接下来,使用了tryu - except的语法,如果执行init失败,则抛出异常,返回给用户。
可以看到,主要是执行了一个measure_service.init()函数。按住ctrl点击这个函数,即可跳转。他在/app/services/measure_service中。
1 | |
简单分析一下这个函数,refresh主要是刷新了一下和产品相关的参数,然后把config中的参数传给了检测器。后面init_code是检测二维码,以及光源和焦距的控制部分,不过这部分不是我们现在需要关心的。
1 | |
分析代码,我们现在需要进行圆搜索的代码需要添加在这个refresh之前。
Q: 需要做什么?
我们首先需要写一个圆检测器,接收圆心、半径等信息,然后对图像进行圆搜索,把搜索到的圆心和半径返回。
再在
measure.service写一个init_search,负责调用刚刚写的圆搜索器。首先从参数服务中读取对应的值,然后获取到精搜索之后的值,再写回参数中。最后,我们在
api_handler中写一个接口,用于单独测试这个功能是否正常,然后看你心情写到前端中。(前端可以交给AI)
可能看起来有点乱,为什么要这么写?
在软件工程中,需要遵循“高内聚、低耦合”的特点,以实现功能的复用。因此最基础的圆搜索器,他更像是一个工具箱,不参与整个业务逻辑。无论你是用搜索器搜索梅花针还是指针,可以调用两次圆搜索器复用代码。之后可能还需要搜索整个水表的位置,进行位置修正,搜索水表表盘这个大圆显然是也需要调用圆搜索器的,如果设计之初就参杂了一些业务逻辑,在之后复用的时候会很麻烦。
Q: 为什么只搜索圆?
我尝试过矩形搜索,因为我们的矩形搜索是搜索二维码的位置,然后裁剪区域送到二维码搜索器,供其检测。但是矩形搜索对二维码和条码的识别效果特别差。于是我想的办法是,用二维码搜索结果解析出的角点值,取xy最小为左上角点,xy最大为左下角点,然后写入配置文件中。这一部分在measure_service的init_code中。缺点是二维码识别的精度特别特别差,这个需要你以后进行改进,比如说用二值化、形态学处理等算法识别二维码。
三、圆搜索器
我在app/algorithm/detector中创建了一个新的文件searcher.py用于实现圆搜索器。然后把我们的需求以注释的形式补充。
1 | |
接下来就是AI大显身手的时候了。参考提示词:
1 | |

具体代码内容参照修改后的就好,我就不进行赘述了。经过测试,发现他能搜索到红色指针的表盘圆,但是无法识别得到梅花针的圆。
显然梅花针并不是一个规则的圆形,看来我们只能是想想别的办法了。
由于梅花针呈中心对称的特性,因此我们可以写一个质心搜索器。
四、质心搜索器
所谓质心搜索器,简而言之就是去找算平均值,即梅花针区域的黑色像素平均坐标。思路有了,我们就可以接着动笔了:
1 | |

嗯,非常完美接下来就可以写服务的部分了。(其实也没有那么完美,AI给的质心搜索部分的代码半径算的不对,让他改了几次才改好)
五、初始化搜索服务
在/app/services/measure_service中新建一个函数,init_search,为了后面异常返回和方便调试,我们先把函数的框架搭好:
1 | |
在这一部分,就要引入日志系统和异常返回,因此程序的框架必须是try-except结构。关于日志部分,可以参考我发现很多程序员都不会打日志 哔哩哔哩_bilibili
接着,我们参照刚刚的给出的调用示例,完善这个函数。
六、API调用
刚刚写好了精搜索的服务,但是还没有办法测试他好不好用,因此我们还需要写一个api调用测试。在/app/web/api_handlers中添加一个:
1 | |
没什么好说的,把之前的复制一下,换一下try肚子里的函数就好了。
七、调试
好了,万事俱备,让我们打开apipost调试,注意使用POST,这里多提一嘴,POST比GET安全,一般获取参数的时候使用GET,修改参数或者控制系统用POST,符合规范。
没什么问题,接下来把这部分加到init中。其实也有点BUG,填错了参数,稍微改了一下
由于在init_search的时候refresh了,因此删掉init的refresh取而代之就行。
最后,我们完整的测试标记 -> 初始化 这个流程,并测试乱标记能否正常报错返回提示
有点BUG,圆搜索的阈值需要调整一下,参照:OpenCV-Python教程:霍夫变换~圆形(HoughCircles)cv2.houghcircles-CSDN博客
测试乱标记能否正常报错:
好,没什么问题了,这个功能就算是添加完成了。
八、结语
8.1 单例
可能你对线程和进程的概念比较懵,不知道在哪里执行的init,这里我给你大概展示一下:
首先我们在measure.service的文件结尾有一句:
1 | |
这个写法叫单例。整个测量服务就变成了全局变量(全局对象)。参考Python单例模式详解:从原理到实战的完整指南_python 单例-CSDN博客
在其他文件调用时,就调用这个实例化后的对象即可。比如:
1 | |
因此我们刚刚写的函数是web线程中运行的。事实上这种初始化部分的图像处理,对于实时性要求
并不高,因此没必要把这部分放到处理线程中。
8.2 绘图
调试的时候我是没有单独动过绘制部分的,是不过我认为有必要介绍一下图像绘制。这部分在app\algorithm\process_thread.py的draw_overlay部分,他是直接读取config.yaml的参数来进行绘制的。
8.3 上传
最后的最后,我们要把今天的工作上传到github,以便同步大家的进度。我这里再以VScode为例演示:
首先要填写提交的message,这部分可以参考约定式提交,不过我们没什么讲究,大家能看懂就行。

大功告成!