From patchwork Fri Jun 20 12:42:28 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 23615 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D02F9BDE6B for ; Fri, 20 Jun 2025 12:45:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BF30D68E01; Fri, 20 Jun 2025 14:45:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="CIVsR3pT"; dkim-atps=neutral Received: from mail-wr1-x432.google.com (mail-wr1-x432.google.com [IPv6:2a00:1450:4864:20::432]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4985C68DF0 for ; Fri, 20 Jun 2025 14:45:02 +0200 (CEST) Received: by mail-wr1-x432.google.com with SMTP id ffacd0b85a97d-3a577f164c8so288300f8f.2 for ; Fri, 20 Jun 2025 05:45:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; t=1750423502; x=1751028302; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=KWje5sBJ8QU0mBeCf3VYQD9sRSW4DAtdZgufgu9drJU=; b=CIVsR3pTTggLjpouEUuzmNwgjZGWZ6AJ3/6idIHYQ4x8Avj0KBW/lqTlz4tiO1f1hH unfnCl5hTj56uazE077J6O5pvPMnSeNrxSepWIGyAeTDd7PxmVvcbO/sd3fXwAhpuULy BuMQR8Iku5b5Th2L6XX6IH5xBBi7TWtppbYO0GiGsi0hZnt388eqY1AY+6RndmVPU6Y7 mGlZ37WJhMHPhD7cr5IsGxDfmBPPeFQVctZ38yt007OVXKctiTr3r+shMWFltW1s409f up6ImGot5FyPfm58pDONIa751pt9AHPcMs9uYp5tFrwV9KS4Lp3GWTY2IWO906SObg8Q T20g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750423502; x=1751028302; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=KWje5sBJ8QU0mBeCf3VYQD9sRSW4DAtdZgufgu9drJU=; b=bMBHi08bZTAJYelDgbOE9MPwg6j4t/AXWLtHPA7UYIgDiPn5/7+Cyplmv0dDWDl0dl IaF3CvQFvbwdKWQR5riV17xHzTH4lrcqqi5Zth+5kivqioe+Rz1ZKv4FktUphgTo0p+R 3zs8W/HTk9beWT7wRkMfGJdMCXQLcY7/yNbipZRun2N5X70m8elogJrkgoP542A8tmYa 78wWMzy3x6RFkZohUUVAGCGpgPbSVOHFOhqMUwkJZUgeaAVtFMCx+/k5Z79q1DEHizEn TEKgyS4p+hxKdZ/YMnelpFKtXiqhskU3DFh0hyHXoCVE4+ooAGCNRvw4oyj2YY7xVu6U aOJQ== X-Gm-Message-State: AOJu0Yx2NX+yyZiGAiyEMPrhuih0lYNGoTKJr2wzqi1IM4wENYPtw9ge nKGvjvSPiytrNRUh1zj8Mon36WWu4GzCr30Cn6n0mzsWgyIoEGU9wweJbZDZwDGr0p9gOdlU0sw cTsbI X-Gm-Gg: ASbGncuT6/t83MRBVILeCvrWvOmcP1q/CtJntU7C5dzFctt7mqSKa70ii3llYEmato5 +jzNb2x5jXA0ijk6tFpOBN6YCibfjHo7DBuAUQO0hToVl1h37aNv8GlzDi9/R374YQ7lxs7Uc/d cyJzVde8khZFr9gD5gGXhso25Nl5SQH9SUuwCN1i1t2SXpQ/PXauWVij7rW2+sMfpxFsPC44yW5 sPsyDVapID+knRG0JFZ1pw07UKnB9ixerS2teqByyObOu7vOPl4a72hLtPi47e5IjYjLBEFhY0o YQvbs36ZWu84tW4iew4Cm2wnikt4pK0ln1mv21djPgUX8m/rpv6EAv4oOKF0Flb0cUmi0YCpvTT 39I5fBA== X-Google-Smtp-Source: AGHT+IEVkEHsgfVFjJkozYT/AL6UaYdLLn8b7+QJL8QWOK+kgxzXG1a0CGqxRAXLtMHmklxlvlw8EA== X-Received: by 2002:a05:600c:1d02:b0:450:d4b4:92d0 with SMTP id 5b1f17b1804b1-453653c105emr9273005e9.3.1750423501409; Fri, 20 Jun 2025 05:45:01 -0700 (PDT) Received: from NAUSH-P-DELL.pitowers.org ([93.93.133.154]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45361461375sm41561525e9.14.2025.06.20.05.45.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Jun 2025 05:45:01 -0700 (PDT) From: Naushir Patuck To: libcamera-devel@lists.libcamera.org Cc: Nick Hollinghurst , Naushir Patuck Subject: [PATCH v1 7/8] ipa: rpi: controller: AutoFocus bidirectional scanning Date: Fri, 20 Jun 2025 13:42:28 +0100 Message-ID: <20250620124452.557855-8-naush@raspberrypi.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250620124452.557855-1-naush@raspberrypi.com> References: <20250620124452.557855-1-naush@raspberrypi.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Nick Hollinghurst To reduce unnecessary lens movements, allow the CDAF-based search procedure to start from either end of the range; or if not near an end, from the current lens position. This sometimes requires a second coarse scan, if the first one started in the middle and did not find peak contrast. Shorten the fine scan from 5 steps to 3 steps; allow fine scan to be omitted altogether when "step_fine": 0 in the tuning file. Move updateLensPosition() out of startProgrammedScan() to avoid calling it more than once per iteration. Signed-off-by: Nick Hollinghurst Signed-off-by: Naushir Patuck Reviewed-by: Naushir Patuck --- src/ipa/rpi/controller/rpi/af.cpp | 97 ++++++++++++++++++++----------- src/ipa/rpi/controller/rpi/af.h | 9 +-- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp index 4396420a0277..eaaca4bc9d7a 100644 --- a/src/ipa/rpi/controller/rpi/af.cpp +++ b/src/ipa/rpi/controller/rpi/af.cpp @@ -200,6 +200,7 @@ Af::Af(Controller *controller) sceneChangeCount_(0), scanMaxContrast_(0.0), scanMinContrast_(1.0e9), + scanStep_(0.0), scanData_(), reportState_(AfState::Idle) { @@ -251,13 +252,14 @@ void Af::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *met << statsRegion_.height; invalidateWeights(); - if (scanState_ >= ScanState::Coarse && scanState_ < ScanState::Settle) { + if (scanState_ >= ScanState::Coarse1 && scanState_ < ScanState::Settle) { /* * If a scan was in progress, re-start it, as CDAF statistics * may have changed. Though if the application is just about * to take a still picture, this will not help... */ startProgrammedScan(); + updateLensPosition(); } skipCount_ = cfg_.skipFrames; } @@ -543,31 +545,42 @@ void Af::doScan(double contrast, double phase, double conf) scanMinContrast_ = contrast; scanData_.emplace_back(ScanRecord{ ftarget_, contrast, phase, conf }); - if (scanState_ == ScanState::Coarse) { - if (ftarget_ >= cfg_.ranges[range_].focusMax || - contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) { - /* - * Finished course scan, or termination based on contrast. - * Jump to just after max contrast and start fine scan. - */ - ftarget_ = std::min(ftarget_, findPeak(scanMaxIndex_) + - 2.0 * cfg_.speeds[speed_].stepFine); - scanState_ = ScanState::Fine; - scanData_.clear(); - } else - ftarget_ += cfg_.speeds[speed_].stepCoarse; - } else { /* ScanState::Fine */ - if (ftarget_ <= cfg_.ranges[range_].focusMin || scanData_.size() >= 5 || - contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) { - /* - * Finished fine scan, or termination based on contrast. - * Use quadratic peak-finding to find best contrast position. - */ - ftarget_ = findPeak(scanMaxIndex_); + if ((scanStep_ >= 0.0 && ftarget_ >= cfg_.ranges[range_].focusMax) || + (scanStep_ <= 0.0 && ftarget_ <= cfg_.ranges[range_].focusMin) || + (scanState_ == ScanState::Fine && scanData_.size() >= 3) || + contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) { + double pk = findPeak(scanMaxIndex_); + /* + * Finished a scan, by hitting a limit or due to constrast dropping off. + * If this is a first coarse scan and we didn't bracket the peak, reverse! + * If this is a fine scan, or no fine step was defined, we've finished. + * Otherwise, start fine scan in opposite direction. + */ + if (scanState_ == ScanState::Coarse1 && + scanData_[0].contrast >= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) { + scanStep_ = -scanStep_; + scanState_ = ScanState::Coarse2; + } else if (scanState_ == ScanState::Fine || cfg_.speeds[speed_].stepFine <= 0.0) { + ftarget_ = pk; scanState_ = ScanState::Settle; - } else - ftarget_ -= cfg_.speeds[speed_].stepFine; - } + } else if (scanState_ == ScanState::Coarse1 && + scanData_[0].contrast >= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) { + scanStep_ = -scanStep_; + scanState_ = ScanState::Coarse2; + } else if (scanStep_ >= 0.0) { + ftarget_ = std::min(pk + cfg_.speeds[speed_].stepFine, + cfg_.ranges[range_].focusMax); + scanStep_ = -cfg_.speeds[speed_].stepFine; + scanState_ = ScanState::Fine; + } else { + ftarget_ = std::max(pk - cfg_.speeds[speed_].stepFine, + cfg_.ranges[range_].focusMin); + scanStep_ = cfg_.speeds[speed_].stepFine; + scanState_ = ScanState::Fine; + } + scanData_.clear(); + } else + ftarget_ += scanStep_; stepCount_ = (ftarget_ == fsmooth_) ? 0 : cfg_.speeds[speed_].stepFrames; } @@ -622,7 +635,7 @@ void Af::doAF(double contrast, double phase, double conf) /* else fall through to waiting for a scene change */ } } - if (scanState_ < ScanState::Coarse && mode_ == AfModeContinuous) { + if (scanState_ < ScanState::Coarse1 && mode_ == AfModeContinuous) { /* * In CAF mode, not in a scan, and PDAF is unavailable. * Wait for a scene change, followed by stability. @@ -642,7 +655,7 @@ void Af::doAF(double contrast, double phase, double conf) sceneChangeCount_++; if (sceneChangeCount_ >= cfg_.speeds[speed_].retriggerDelay) startProgrammedScan(); - } else if (scanState_ >= ScanState::Coarse && fsmooth_ == ftarget_) { + } else if (scanState_ >= ScanState::Coarse1 && fsmooth_ == ftarget_) { /* * CDAF-based scanning sequence. * Allow a delay between steps for CDAF FoM statistics to be @@ -714,15 +727,27 @@ void Af::startAF() oldSceneContrast_ = 0.0; sceneChangeCount_ = 0; reportState_ = AfState::Scanning; - } else + } else { startProgrammedScan(); + updateLensPosition(); + } } void Af::startProgrammedScan() { - ftarget_ = cfg_.ranges[range_].focusMin; - updateLensPosition(); - scanState_ = ScanState::Coarse; + if (!initted_ || mode_ != AfModeContinuous || + fsmooth_ <= cfg_.ranges[range_].focusMin + 2.0 * cfg_.speeds[speed_].stepCoarse) { + ftarget_ = cfg_.ranges[range_].focusMin; + scanStep_ = cfg_.speeds[speed_].stepCoarse; + scanState_ = ScanState::Coarse2; + } else if (fsmooth_ >= cfg_.ranges[range_].focusMax - 2.0 * cfg_.speeds[speed_].stepCoarse) { + ftarget_ = cfg_.ranges[range_].focusMax; + scanStep_ = -cfg_.speeds[speed_].stepCoarse; + scanState_ = ScanState::Coarse2; + } else { + scanStep_ = -cfg_.speeds[speed_].stepCoarse; + scanState_ = ScanState::Coarse1; + } scanMaxContrast_ = 0.0; scanMinContrast_ = 1.0e9; scanMaxIndex_ = 0; @@ -785,7 +810,9 @@ void Af::prepare(Metadata *imageMetadata) else status.pauseState = AfPauseState::Running; - if (mode_ == AfModeAuto && scanState_ != ScanState::Idle) + if (scanState_ == ScanState::Idle) + status.state = AfState::Idle; + else if (mode_ == AfModeAuto) status.state = AfState::Scanning; else status.state = reportState_; @@ -907,7 +934,7 @@ void Af::setMode(AfAlgorithm::AfMode mode) pauseFlag_ = false; if (mode == AfModeContinuous) scanState_ = ScanState::Trigger; - else if (mode != AfModeAuto || scanState_ < ScanState::Coarse) + else if (mode != AfModeAuto || scanState_ < ScanState::Coarse1) goIdle(); } } @@ -923,11 +950,11 @@ void Af::pause(AfAlgorithm::AfPause pause) if (mode_ == AfModeContinuous) { if (pause == AfPauseResume && pauseFlag_) { pauseFlag_ = false; - if (scanState_ < ScanState::Coarse) + if (scanState_ < ScanState::Coarse1) scanState_ = ScanState::Trigger; } else if (pause != AfPauseResume && !pauseFlag_) { pauseFlag_ = true; - if (pause == AfPauseImmediate || scanState_ < ScanState::Coarse) + if (pause == AfPauseImmediate || scanState_ < ScanState::Coarse1) goIdle(); } } diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h index e1700f998f29..d35a39d12049 100644 --- a/src/ipa/rpi/controller/rpi/af.h +++ b/src/ipa/rpi/controller/rpi/af.h @@ -75,7 +75,8 @@ private: Idle = 0, Trigger, Pdaf, - Coarse, + Coarse1, + Coarse2, Fine, Settle }; @@ -90,8 +91,8 @@ private: }; struct SpeedDependentParams { - double stepCoarse; /* used for scans */ - double stepFine; /* used for scans */ + double stepCoarse; /* in dioptres; used for scans */ + double stepFine; /* in dioptres; used for scans */ double contrastRatio; /* used for scan termination and reporting */ double retriggerRatio; /* contrast and RGB ratio for re-triggering */ uint32_t retriggerDelay; /* frames of stability before re-triggering */ @@ -177,7 +178,7 @@ private: unsigned sameSignCount_; unsigned sceneChangeCount_; unsigned scanMaxIndex_; - double scanMaxContrast_, scanMinContrast_; + double scanMaxContrast_, scanMinContrast_, scanStep_; std::vector scanData_; AfState reportState_; };