Qt图像标注应用如何绘制像素掩码并获取鼠标扫过的全部像素坐标
解决方案
操作系统对鼠标事件的采样频率是固定的,Qt本身不会也不可能返回鼠标扫过的每一个像素的事件点,快速移动时相邻两次事件的坐标间隔变大是正常现象。你不需要尝试获取全量鼠标事件,只需要对相邻两个采样点做线段插值补全中间点即可。
具体实现步骤
- 首先在你的标注相关类中新增两个私有成员:存储上一次鼠标事件坐标的
QPointF lastMousePos,以及标记当前是否处于手绘状态的bool isDrawing = false。 - 在
mousePressEvent回调中:- 记录当前点击坐标到
lastMousePos - 将
isDrawing置为true - 把当前点加入你的掩码像素列表,完成单点绘制
- 记录当前点击坐标到
- 在
mouseMoveEvent回调中:- 先判断
isDrawing状态,非绘制状态直接返回 - 获取当前事件对应的场景坐标
currentMousePos = event.scenePos() - 调用插值算法生成
lastMousePos到currentMousePos之间的所有像素坐标,全部加入掩码像素列表 - 更新
lastMousePos为当前的currentMousePos - 触发场景重绘
- 先判断
- 在
mouseReleaseEvent回调中:- 将
isDrawing置为false
- 将
插值实现
因为你需要存储所有掩码像素的坐标列表,所以不能直接调用QPainter::drawLine绘制,需要先生成插值点。最常用的轻量方案是Bresenham直线算法,也可以用简单的线性插值实现,示例代码如下:
// 生成两点之间所有像素坐标的线性插值实现 QVector<QPoint> getLinePoints(QPoint start, QPoint end) { QVector<QPoint> points; int dx = abs(end.x() - start.x()); int dy = abs(end.y() - start.y()); int step = dx > dy ? dx : dy; if (step == 0) { points.append(start); return points; } float xIncrement = (float)(end.x() - start.x()) / step; float yIncrement = (float)(end.y() - start.y()) / step; float currentX = start.x(); float currentY = start.y(); for (int i = 0; i <= step; i++) { points.append(QPoint(round(currentX), round(currentY))); currentX += xIncrement; currentY += yIncrement; } // 去重避免重复存储相同坐标 auto last = std::unique(points.begin(), points.end()); points.erase(last, points.end()); return points; }
优化建议
- 如果要保证坐标和底层图片像素完全对齐,需要把
scenePos转换为QGraphicsPixmapItem的局部坐标后再做取整,避免场景缩放、平移带来的坐标偏移 - 绘制时可以仅新增绘制两次事件之间的插值点,不需要每次都全量重绘所有掩码点,能大幅提升大尺寸图片标注时的性能
- 如果需要支持不同画笔粗细,可对每个插值点做对应半径的膨胀处理,把覆盖范围内的所有像素都加入掩码列表
内容的提问来源于stack exchange,提问作者Zézouille
相关产品推荐
相关产品推荐

