Skip to content

fix: handle null captured image retry#47

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
fly602:master
May 28, 2026
Merged

fix: handle null captured image retry#47
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
fly602:master

Conversation

@fly602

@fly602 fly602 commented May 26, 2026

Copy link
Copy Markdown
Contributor
  1. Add a null-image guard in ErollThread::processCapturedImage to detect when the captured QImage preview is empty.
  2. When a null image is received, log a warning, wait briefly with QThread::msleep(10), and immediately trigger m_imageCapture->capture() again before returning.
  3. Keep the normal image processing flow unchanged for valid images, including scaling/conversion, sendCapture(img), and the follow-up capture scheduling.
  4. This fixes the issue where the capture workflow could get stuck after receiving an empty image frame, because the previous logic did not explicitly recover from invalid capture results.
  5. The retry path improves robustness for intermittent camera or capture-card failures without affecting successful capture behavior.

Influence:

  1. Test continuous capture under normal conditions and confirm the collection flow still proceeds without regression.
  2. Simulate or reproduce a null QImage from the capture source and verify the warning is printed and capture automatically retries.
  3. Verify the workflow does not hang when empty images are returned intermittently or consecutively.
  4. Check that valid images after a retry can still be processed and sent successfully.
  5. Observe retry behavior frequency to ensure the short sleep does not introduce noticeable performance issues or excessive busy looping.

fix: 处理空采集图片重试

  1. ErollThread::processCapturedImage 中新增空图片判断,用于检测采集 到的 QImage preview 是否为空。
  2. 当收到空图片时,输出警告日志,使用 QThread::msleep(10) 进行短暂等 待,并立即重新调用 m_imageCapture->capture() 后返回。
  3. 对于有效图片,保持原有处理流程不变,包括缩放/转换、sendCapture(img) 以及后续继续触发采集。
  4. 该修改修复了采集过程中收到空图片后流程可能卡住的问题,因为此前逻辑没 有对无效采集结果进行显式恢复。
  5. 该重试分支提升了摄像头或采集卡偶发异常场景下的稳定性,同时不影响正常 采集行为。

Influence:

  1. 测试正常连续采集场景,确认采集流程仍可正常进行且无回归问题。
  2. 模拟或复现采集源返回空 QImage 的情况,验证会输出告警日志并自动重试 采集。
  3. 验证在间歇性或连续返回空图片时,整体流程不会卡住。
  4. 检查重试后恢复返回有效图片时,图片仍可被正常处理并发送。
  5. 观察重试触发频率,确认短暂休眠不会带来明显性能问题或导致过度忙等待。

PMS: BUG-352653
Change-Id: I3cc71b9c41b6b4ddef1c8c6638c3db15fa3a9407

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @fly602, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@fly602 fly602 force-pushed the master branch 3 times, most recently from bc91890 to 445e5e2 Compare May 27, 2026 05:58
@mhduiy

mhduiy commented May 27, 2026

Copy link
Copy Markdown
Contributor

尽量不要使用sleep阻塞主线程

@mhduiy mhduiy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

风险分析

1. capture() 循环链路变更 — 高风险 ⚠️

原代码在 processCapturedImage 末尾无条件调用 m_imageCapture->capture() 来维持采集循环。新代码删除了这个调用,改为:

  • 空图片时 → 手动调 capture() 重试
  • 正常图片时 → 不再调用 capture()

这意味着正常图片处理后,下一次 capture() 的触发完全依赖 readyForCapture(true) 被外部调用。如果 readyForCapture 没有被及时触发,正常采集流程也会卡住。建议确认 readyForCapture 的调用时机和频率是否能保证采集链路不断。

2. msleep(100) 阻塞 — 中风险

processCapturedImage 是 Qt 信号槽回调,运行在接收者线程中。msleep(100) 会阻塞该线程 100ms。如果连续 10 次空图片,总阻塞约 1 秒。

另外 PR 描述写的 msleep(10),实际代码是 msleep(100),存在不一致。如果这个槽运行在主线程,会导致 UI 卡顿,建议确认该对象所在的线程。

3. 竞态条件 — 中风险

if (m_stopCapture)     // check
    return;
m_imageCapture->capture();  // act

msleep(100) 之后、capture() 之前存在时间窗口,另一个线程可能设置了 m_stopCapture = true。虽然 sleep 后又检查了一次,但 check-then-act 不是原子的。不过 Qt 信号槽机制下实际触发概率较低。

4. m_bFirst 替代 id == 1 — 低风险

从依赖 QImageCapture 返回的 id 变为依赖布尔标志,行为有变更。如果 QImageCapture 的 id 编号机制有特殊情况,这个改动会改变行为,但更可能是修正了原来不够健壮的判断方式。

5. 超过 10 次重试后的行为 — 低风险

超过阈值后发出 FaceEnrollException 信号并 return,不再继续采集。需确认上层对该信号的处理是否完善(是否有用户提示、是否有重试入口)。


最需要关注的是第 1 点:建议确认正常采集路径中 readyForCapture(true) 的调用链路完整且时机正确,否则这个"修复空图片卡住"的 PR 可能反而导致正常场景下采集中断。

Comment thread workmodule.h Outdated
int m_fileSocket;
bool m_bFirst;
bool m_checkDone;
int m_nullCount;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果是临界资源,用原子变量保护一下

Added m_nullCount member variable to track null image captures.
Reset m_nullCount on thread start and when capture is stopped.
In readyForCapture, added condition to check if capture is not stopped.
In processCapturedImage, added logic to handle null previews: retry up
to 10 times with sleep, else emit error.
Changed first image condition from checking id to using m_bFirst flag.
Removed unnecessary capture call after image processing.

Influence:
1. Test capture functionality with normal and null image scenarios.
2. Verify that capture does not get stuck when images are null.
3. Test the retry mechanism and error emission after multiple failures.
4. Check that first image handling works correctly with the new flag.
5. Ensure capture stops properly when requested.
6. Test edge cases like rapid stop/start actions.

修复: 防止采集空图片时卡住

添加 m_nullCount 成员变量以跟踪空图片采集次数。
在线程启动和采集停止时重置 m_nullCount。
在 readyForCapture 中添加条件检查采集是否未停止。
在 processCapturedImage 中添加处理空预览的逻辑:最多重试10次并带延迟,否
则发出错误信号。
将首张图片条件从检查 id 改为使用 m_bFirst 标志。
移除处理图片后不必要的采集调用。

Influence:
1. 测试正常和空图片场景下的采集功能。
2. 验证当图片为空时采集不会卡住。
3. 测试重试机制和多次失败后的错误发出。
4. 检查使用新标志的首张图片处理是否正确。
5. 确保在请求时采集能正确停止。
6. 测试快速停止/启动操作等边界情况。

PMS: BUG-352653
Change-Id: I3a6964c991e6c8aef8385195ae9f54eb84455d41
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已仔细审查了你提供的Git Diff代码。本次修改主要增加了对捕获图像为空(isNull)的重试与异常处理机制,修复了使用硬编码id判断首帧的逻辑,并优化了停止捕获时的状态检查。

整体来看,这些改进增强了代码的健壮性,但在语法逻辑、代码质量、性能和安全性方面,还有一些值得注意和改进的地方。以下是详细的审查意见:

1. 语法与逻辑

  • 在GUI/事件循环线程中使用阻塞睡眠 (QThread::msleep)
    • 问题:在 processCapturedImage 中,当图像为空时使用了 QThread::msleep(100)。如果 ErollThread 是依附于Qt事件循环的(特别是如果它处理信号槽),在槽函数中调用睡眠会阻塞整个线程的事件循环,导致其他信号(如停止信号 m_stopCapture)无法及时响应,甚至可能导致界面卡顿或相机操作超时。
    • 建议:不要在槽函数中主动睡眠。如果需要延时重试,可以使用 QTimer::singleShot 进行异步延时调用。
  • 重试机制可能触发信号风暴
    • 问题:当 preview.isNull() 时,代码直接调用了 m_imageCapture->capture()。由于 readyForCapture 逻辑的存在,一旦捕获就绪,又会立刻触发 capture()。如果相机持续返回空图像且就绪状态频繁切换,这种在槽函数内直接调用 capture() 的方式可能会导致捕获请求堆积,形成类似递归的信号风暴。
    • 建议:重试捕获的请求应当由 readyForCapture 统一管理,而不是在 processCapturedImage 中强制下发。可以设置一个标志位(如 m_needRetry),在 processCapturedImage 中置位,然后在 readyForCapture 中根据标志位决定是否重新 capture()
  • m_nullCount 类型与逻辑溢出风险
    • 问题m_nullCount 被声明为 std::atomic<int>,但在 if (++m_nullCount > 10) 中,前置++在原子操作中返回的是自增后的值,逻辑上没问题。但是,如果由于某种异常情况,m_nullCount 在到达10之前没有被正确重置,它可能会继续增长甚至溢出(虽然int溢出在实际上需要很久,但逻辑上不够严谨)。
    • 建议:既然 > 10 就会触发异常并重置为0,建议将其类型改为 std::atomic<int8_t>int 即可,节省内存对齐开销。同时,确保所有退出 processCapturedImage 的分支都正确处理了该计数(当前代码在 m_stopCapture 时重置为了0,这是好的)。

2. 代码质量

  • 日志级别的合理使用
    • 改进:将 readyForCapture 中的 qInfo() 改为 qDebug() 是一个很好的做法,因为这只是调试信息,不应污染正式的Info级别日志。
  • 魔法数字
    • 问题:重试次数 10 和睡眠时间 100 是硬编码的魔法数字。
    • 建议:将其定义为类成员常量、枚举或全局配置常量,提高代码可读性和可维护性。
  • 多余的空行与格式
    • 问题:在 sendCapture(img);m_imageCapture->capture(); 之间删除了空行,使得两段逻辑紧贴,稍微降低了可读性。
    • 建议:保留一个空行,将“处理并发送当前图像”与“发起下一次捕获请求”这两个不同的逻辑块隔开。

3. 代码性能

  • 图像缩放的性能损耗
    • 问题:代码中 preview.scaled(800, 600, Qt::IgnoreAspectRatio, Qt::FastTransformation) 每次捕获都会执行。图像缩放是非常消耗CPU的计算。如果相机的默认分辨率能设置为800x600,就可以省去这一步软缩放的开销。
    • 建议:如果硬件/相机驱动支持,尽量在相机初始化时设置输出分辨率为 800x600,避免在CPU侧进行高频的图像缩放运算。
  • 原子变量的开销
    • 问题m_nullCount 使用了 std::atomic<int>。如果 processCapturedImagereadyForCaptureStart 都是在同一个线程(或通过信号槽排队执行),其实不需要原子变量,普通的 int 即可,原子操作会带来不必要的内存屏障和CPU开销。
    • 建议:确认这些方法是否仅在单线程上下文执行。如果是,移除 std::atomic;如果确实存在跨线程直接调用(非信号槽),则保留。

4. 代码安全

  • 空指针解引用风险
    • 问题:在 processCapturedImage 中,当 preview.isNull() 时,直接调用了 m_imageCapture->capture(),但没有检查 m_imageCapture 是否为空。虽然在 readyForCapture 中做了检查,但这里直接调用存在隐患。
    • 建议:在调用 m_imageCapture->capture() 前,增加 if (m_imageCapture) { m_imageCapture->capture(); } 的保护。
  • 逻辑优化:m_bFirst 的判断
    • 改进:将 1 == id 修改为 m_bFirst 是非常正确的。依赖底层捕获的 id 是非常脆弱的,因为某些平台或相机驱动可能会在首帧返回非1的ID,使用状态标志位 m_bFirst 更加安全和符合业务逻辑。

改进后的代码建议

基于以上分析,我为你提供一份优化后的 processCapturedImage 及相关部分的代码参考:

// workmodule.h
// 建议将重试次数定义为常量,并评估是否真的需要 atomic
private:
    static constexpr int MAX_NULL_RETRY_COUNT = 10;
    static constexpr int RETRY_DELAY_MS = 100;
    // 如果只在同一线程访问,使用 int 即可;否则保留 std::atomic<int>
    int m_nullCount; 
    bool m_needRetry; // 新增:重试标志位,避免在槽函数中直接触发捕获

// workmodule.cpp
void ErollThread::Start(QString actionId, int socket)
{
    qDebug() << "ErollThread::Start thread:" << QThread::currentThreadId();
    m_stopCapture = false;
    m_nullCount = 0;
    m_needRetry = false; // 初始化
    m_actionId = actionId;
    m_fileSocket = socket;
    m_bFirst = true;
    // ...
}

void ErollThread::readyForCapture(bool ready)
{
    // 如果需要重试,且相机就绪,且未停止,则发起捕获
    if (m_imageCapture && ready && !m_stopCapture) {
        if (m_needRetry || m_bFirst /* 或其他需要捕获的条件 */) {
            m_imageCapture->capture();
            qDebug() << "ErollThread::readyForCapture";
        }
    }
}

void ErollThread::processCapturedImage(int id, const QImage &preview)
{
    if (m_stopCapture) {
        m_nullCount = 0;
        m_needRetry = false;
        return;
    }

    if (preview.isNull()) {
        if (++m_nullCount > MAX_NULL_RETRY_COUNT) {
            qWarning() << "captured image is null too many times, aborting";
            m_nullCount = 0;
            m_needRetry = false;
            Q_EMIT processStatus(m_actionId, FaceEnrollException);
            return;
        }
        qWarning() << "captured image is null, scheduling retry capture";
        m_needRetry = true; // 标记需要重试
        
        // 替换 QThread::msleep,使用异步定时器延时触发,不阻塞事件循环
        // 注意:如果 readyForCapture 会自动触发,这里甚至不需要 Timer,直接 return 即可
        QTimer::singleShot(RETRY_DELAY_MS, this, [this]() {
            if (!m_stopCapture && m_imageCapture) {
                m_imageCapture->capture();
            }
        });
        return;
    }

    // 成功获取图像,重置状态
    m_nullCount = 0;
    m_needRetry = false;

    QImage img;
    if (preview.size() == QSize(800, 600)) {
        img = preview;
    } else {
        img = preview.scaled(800, 600, Qt::IgnoreAspectRatio, Qt::FastTransformation);
    }

    if (m_bFirst) {
        sendCapture(img);
        m_bFirst = false;
    } else {
        // ... 原有逻辑
        sendCapture(img);
    }

    // 继续下一次捕获
    if (m_imageCapture) {
        m_imageCapture->capture();
    }

    if (!m_checkDone) {
        // ...
    }
}

总结:你的修改方向非常正确,解决了空帧卡死和首帧判断的Bug。主要的改进点在于避免在槽函数中进行阻塞睡眠,以及更安全地处理重试逻辑以避免信号堆积。希望这些建议对你有所帮助!

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fly602, mhduiy

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fly602

fly602 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

This pr force merged! (status: blocked)

@deepin-bot deepin-bot Bot merged commit b81f4ba into linuxdeepin:master May 28, 2026
11 checks passed
@fly602

fly602 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

This pr force merged! (status: unknown)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants