Computer Vision面接対策

Computer Visionエンジニア面接問題

レベル別にまとめた、選りすぐりの computer vision 面接問題 15問。基礎、実践上のトレードオフ、シニアレベルの本番運用での判断を確認できます。

Computer VisionのAI面接を開始クレジットカードは不要です。1回分の無料セッションがあります。
英語での技術面接練習非ネイティブ話者が技術面接の合格を目指して練習できるモードです。

初級向け質問

1画像に対する離散2D (Two-Dimensional) 畳み込みとは何ですか?また、カーネル、フィルタ、相互相関(cross-correlation)とはどのような関係がありますか?

画像に対する離散2D畳み込みは、重みの行列(カーネル)を入力画像上でスライドさせる線形かつ空間的なフィルタリング処理です。空間上の各位置において、カーネルの重みと重なり合う局所的な画像領域(受容野)との要素ごとの積を計算し、それらを合算して(多くの場合バイアス項を加算)、出力特徴マップ(feature map)の単一ピクセル値を生成します。 古典的な数学や信号処理における厳密な畳み込みでは、スライディング内積を計算する前にカーネルを水平および垂直方向に反転(180度回転)させます:$(I * K)(i, j) = \sum_m \sum_n I(i - m, j - n) K(m, n)$。これに対し、相互相関(cross-correlation)はカーネルを反転させずにスライディング内積を直接計算します:$(I \star K)(i, j) = \sum_m \sum_n I(i + m, j + n) K(m, n)$。 PyTorchやTensorFlowなどのディープラーニングフレームワークにおいて、「畳み込み(convolution)」という名前で実装されている処理は、実際には相互相関です。カーネルの重みは誤差逆伝播法(backpropagation)によって直接最適化される学習可能なパラメータであるため、ネットワークは適切な向きの重みをそのまま学習します。したがって、学習時および推論時のいずれにおいても、数学的なカーネルの反転処理を行う計算上の必然性はありません。

import numpy as np
from scipy.signal import convolve2d, correlate2d

image = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]], dtype=float)

# Asymmetric directional kernel
kernel = np.array([[1, 0],
                   [0, -1]], dtype=float)

# Convolve flips kernel by 180 degrees: kernel[::-1, ::-1]
conv_res = convolve2d(image, kernel, mode='valid')
corr_res = correlate2d(image, kernel, mode='valid')

print("Convolve output:\n", conv_res)
print("Correlate output:\n", corr_res)
AI コーチを使ってこの質問に答えてみる

2ビジョンパイプラインにおいて、RGB (Red, Green, Blue)、HSV (Hue, Saturation, Value)、Lab、YCbCr、またはグレースケール表現はそれぞれどのような場合に使用しますか?

色空間ごとに視覚データの物理的、知覚的、統計的な特性の分離度合いが異なるため、それぞれ特定のコンピュータビジョンタスクに適しています。 1. **RGB / BGR**: カメラセンサやディスプレイ機器の標準フォーマットであり、加法混色の三原色(Red、Green、Blue)を表します。色チャンネルが輝度と強く相関しているため、照明の変化が3つのチャンネルすべてに同時に影響します。ディープニューラルネットワーク(CNN、Vision Transformer)への標準入力として使われます。実運用上の大きな注意点としてRGBとBGRのチャンネルの入れ違い(例えばOpenCVはBGRで読み込むのに対し、PILやPyTorchはRGBを想定する)があり、気づかないうちにモデルの精度を低下させる原因になります。 2. **HSV / HSL**: 色度(Hue = 色相、Saturation = 彩度)と強度(Value / Lightness = 明度)を明示的に分離します。色相(Hue)は影や明るさの変化に対して比較的頑健であるため、従来の閾値処理による色抽出、色に基づく物体追跡、照明が変動する環境での色セグメンテーションに最適です。 3. **CIE Lab**: 2点間のユークリッド距離($\Delta E$)が人間の知覚上の差異を直接反映する、知覚的に均等な色空間です。明度($L^*$)と反対色軸($a^*$:緑-赤、$b^*$:青-黄)を分離します。色差検査、色補正、画質評価、カラー化タスクなどで用いられます。 4. **YCbCr**: 輝度($Y$)を、青色色差($Cb$)および赤色色差($Cr$)のクロマ(色差)成分から分離します。人間の視覚系は色の詳細情報に対する感度が低いため、クロマサブサンプリング(例: 4:2:0)を行って帯域幅や計算量を削減でき、動画および静止画の圧縮規格(JPEG、MPEG、H.264/H.265)の基盤となっています。 5. **グレースケール**: 単一チャンネルの輝度表現です($Y \approx 0.299R + 0.587G + 0.114B$)。色が重要ではない幾何学的・テクスチャベースのタスク(例: オプティカルフロー、SLAM、SIFT/ORBなどの古典的特徴量抽出、エッジ検出)において、メモリと計算量を約66%削減するのに最適です。ただし、信号機の点灯状態の分類のように、色が重要な識別特徴となるタスクには使用すべきではありません。

import cv2
import numpy as np

# Sample image: saturated red under normal and shaded lighting
img_bgr = np.zeros((100, 100, 3), dtype=np.uint8)
img_bgr[:, :50] = [0, 0, 255]    # Bright Red in BGR
img_bgr[:, 50:] = [0, 0, 120]    # Shaded/Dark Red in BGR

# Convert to HSV (OpenCV H: 0-179, S: 0-255, V: 0-255)
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# Red Hue wraps around 0/180; track red with single Hue range [0, 10] & high saturation
lower_red = np.array([0, 100, 50])
upper_red = np.array([10, 255, 255])
mask_hsv = cv2.inRange(img_hsv, lower_red, upper_red)

print("HSV segmented pixels:", np.count_nonzero(mask_hsv), "out of", mask_hsv.size)
AI コーチを使ってこの質問に答えてみる

3コンピュータビジョンシステムにおいて、チャンネルごとの画像正規化、画像単位の正規化、および一貫したピクセルスケーリングが重要である理由は何ですか?

画像正規化は、数値的安定性の維持、勾配降下法の収束加速、および学習時と推論時における分布の不一致(train-serving distribution shift)の防止を目的として、ビジョンパイプラインにおいて極めて重要です。 1. **ピクセルスケーリング([0, 1] または [-1, 1])**: 元の画像ピクセルは符号なし8ビット整数(`uint8` $\in [0, 255]$)として格納されています。生の $[0, 255]$ の値をそのままニューラルネットワークに入力すると、初期の活性化値が極端に大きくなり、勾配爆発や勾配飽和を引き起こします。255.0 で除算して $[0.0, 1.0]$ または $[-1.0, 1.0]$ へスケーリングすることで、入力をゼロ中心化し、標準的な重み初期化手法(He、Xavier)に適した状態にします。 2. **データセット単位のチャンネル正規化(平均・標準偏差による標準化)**: 事前に計算したチャンネルごとの大域的統計情報(例: ImageNet の平均 `[0.485, 0.456, 0.406]`、標準偏差 `[0.229, 0.224, 0.225]`)を用い、$x_{norm} = \frac{x - \mu_c}{\sigma_c}$ によって入力を標準化します。これによりデータセット全体にわたって各チャンネルが平均ゼロ、分散1に揃えられ、チャンネル間でのバランスの取れた勾配伝播が保証されます。運用上の重大な障害要因として**学習と推論の乖離(train-serving skew)**があります。推論時に255による除算を失念したり、異なる平均・標準偏差の定数を使用したり、誤ったチャンネル順序(RGBとBGRの取り違え)を適用したりすると、モデルの性能が著しく低下します。 3. **画像単位の正規化(Instance Normalization / Min-Max / Zスコア)**: 個々の画像ごとに平均と標準偏差を計算します($x_{norm} = \frac{x - \mu_{img}}{\sigma_{img}}$)。これにより、撮影条件の違いによる大域的なコントラストや照明の変動が除去されます。スタイル変換、MRI/CTなどの医療画像処理、衛星画像解析では有効である一方、絶対的なピクセル輝度自体が物理的な意味を持つタスク(昼夜分類、物質の反射率測定、校正された照明下での欠陥検出など)では悪影響を及ぼす可能性があります。

import torch
import torchvision.transforms as T
from PIL import Image
import numpy as np

# Create a mock uint8 RGB image
raw_img = Image.fromarray(np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8))

# Standard transform pipeline:
# 1. ToTensor scales uint8 [0, 255] -> float32 [0.0, 1.0] and permutes HWC -> CHW
# 2. Normalize standardizes per-channel using dataset mean and std
preprocess = T.Compose([
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

tensor_img = preprocess(raw_img)
print("Tensor shape:", tensor_img.shape)
print("Tensor dtype:", tensor_img.dtype)
print("Channel 0 min/max:", round(tensor_img[0].min().item(), 2), round(tensor_img[0].max().item(), 2))
AI コーチを使ってこの質問に答えてみる

4幾何学的データ拡張および測光学的(フォトメトリック)データ拡張はどのように不変性を促進しますか。また、あるデータ拡張がラベルを保持しているかどうかはどのように判断しますか。

幾何学的データ拡張(回転、平行移動、拡大縮小、反転など)はピクセルの空間座標マッピングを変更しますが、測光学的データ拡張(明度、コントラスト、色相のジッター、ぼかしなど)は空間座標を変更せずにピクセル値の強度や色チャンネルを変更します。分類タスクにおいて、両手法はモデルに現実的な変動を与えることで、姿勢、視点、または照明条件の変化に対しても予測が安定するような不変な表現の学習を促進します。空間予測タスク(物体検出やセグメンテーション)では、同変性(equivariance)を維持するために、幾何学的変換に合わせて正解(ground-truth)のバウンディングボックスやマスクにも同一の変換を適用する必要があります。ある拡張がラベルを保持しているかどうかの判断には、対象ドメインの意味論と物理的な事前知識を評価することが求められます。変換後の画像が、その意味的同一性を損なうことなく元の対象クラスの妥当なインスタンスであり続ける場合、その変換はラベル保持的(label-preserving)であると言えます。例えば、水平方向の反転は一般的な物体(車や動物など)の同一性を保持しますが、OCR(Optical Character Recognition:光学文字認識)や数字分類では同一性を損なったり文字を無効化したりします。同様に、360度の自由な回転は基準となる向きが存在しない組織病理画像や衛星画像では有効ですが、上下逆さまの物体が現実世界の物理的制約に反する自動運転環境では不自然となります。

import albumentations as A

# Natural scene classification / object detection: horizontal flip is valid
natural_scene_transform = A.Compose([
    A.HorizontalFlip(p=0.5),
    A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['category_ids']))

# OCR / Digit classification: horizontal flip corrupts semantics
ocr_transform = A.Compose([
    A.Affine(scale=(0.9, 1.1), rotate=(-10, 10), p=0.5),
    A.ColorJitter(brightness=0.2, contrast=0.2, p=0.5),
])
AI コーチを使ってこの質問に答えてみる

5最近傍補間、バイリニア補間、バイキュービック補間、および面積平均補間とは何ですか?また、画像のリサイズによってエイリアシングやぼやけがどのように生じるかを説明してください。

補間手法は、新たな離散グリッド上へ再サンプリングする際に、連続座標における画素の輝度値を推定します: - **最近傍補間(Nearest-Neighbor):** 最も近い離散画素の輝度値を割り当てます。計算量は $O(1)$ であり、元の値をそのまま保持しますが、アップサンプリング時にはブロック状のアーティファクトが生じ、ダウンサンプリング時には顕著なジャギーが発生します。 - **バイリニア補間(Bilinear Interpolation):** 両軸に沿った線形補間を用いて、$2 \times 2$(近傍4点)の画素の距離に応じた重み付け平均を計算し、適度な平滑化を伴う滑らかな遷移を生成します。 - **バイキュービック補間(Bicubic Interpolation):** 3次多項式カーネルを用いて $4 \times 4$(近傍16点)の領域をサンプリングします。局所的な輝度勾配を捉えるため、バイリニア補間よりもシャープなエッジと滑らかな曲線が得られますが、急峻な遷移部分でオーバーシュートやリンギングといったアーティファクトが発生することがあります。 - **面積平均補間(Area / Box Interpolation):** 変換先画素を元画像画素へ投影した際の重複面積の割合を計算し、下層にある元画像の輝度値を平均化します。モアレを発生させずにダウンサンプリングする際に特に有効です。 **エイリアシングとぼやけの発生原理:** - **エイリアシング**は、十分なローパスフィルタ処理を行わずにダウンサンプリングした結果、高周波成分が新たなサンプリングレートのナイキスト周波数($f_s / 2$)を超えた場合に発生します。高周波が低周波帯域へと折り返され(フォールディング)、本来存在しないパターンやモアレ、ジャギーを引き起こします。 - **ぼやけ**は、補間カーネルがローパスの平滑化フィルタとして機能し、保持すべき高周波成分を減衰させた場合(例:バイリニア補間による平滑化)、またはダウンサンプリング中に高周波の詳細情報が平均化されて失われた場合に発生します。

import cv2

# Severe downsampling: area downsampling prevents aliasing
img = cv2.imread('high_res_pattern.jpg')
downscaled_area = cv2.resize(img, (400, 300), interpolation=cv2.INTER_AREA)
downscaled_nn = cv2.resize(img, (400, 300), interpolation=cv2.INTER_NEAREST)  # heavy aliasing/moiré

# Upscaling: bicubic preserves edge sharpness
upscaled_cubic = cv2.resize(downscaled_area, (400, 400), interpolation=cv2.INTER_CUBIC)
upscaled_linear = cv2.resize(downscaled_area, (400, 400), interpolation=cv2.INTER_LINEAR)
AI コーチを使ってこの質問に答えてみる

6現実環境における視覚的な破綻モード(failure mode)としてビジョンシステムが評価されるべきものには何があり、それらは入力データの分布をどのように変化させますか?

現実世界における視覚的な破綻モードは、いくつかの大まかなカテゴリに分類されます。環境条件(低照度、逆光や強い反射光、雨、雪、霧)、センサおよび光学的な影響(モーションブラー、焦点ボケ、高ISOセンサノイズ、ローリングシャッター歪み)、圧縮や伝送による劣化(JPEGブロックノイズ、動画ビットレート制限、ダウンサンプリング)、意味的/空間的な変化(部分的な遮蔽、フレームアウトによる途切れ、学習分布外の物体姿勢)などです。これらの破綻モードは、入力分布を通常の学習ドメインから複数のレベルで逸脱(シフト)させます。ピクセルレベルでは、輝度や色度のヒストグラムを変化させ、局所的なコントラストを破壊し、信号対雑音比(SNR)を低下させ、人工的な高周波ノイズやブラーカーネルをもたらします。構造および意味レベルでは、エッジの勾配を減衰させ、重要な視覚的特徴を覆い隠し、輪郭を歪ませ、判定に不可欠な領域を隠してしまいます。クリーンなデータで学習されたモデルがこれらのシフトした入力に遭遇すると、低レベルの畳み込みやAttentionによる特徴抽出が適切に機能しなくなり、検出漏れ、ノイズパターンに対する誤検出(偽陽性)、過信による誤認が生じます。

Category              | Physical Mechanism             | Input Shift & Feature Impact
-----------------------------------------------------------------------------------------
Low Light / Noise     | Low photon flux, high sensor gain | Drops SNR, suppresses gradient edges, fires spurious texture filters
Motion / Defocus Blur | Scene/camera motion, out-of-focus| Attenuates high spatial frequencies, smears object boundaries
Adverse Weather/Glare | Fog/rain scattering, lens flare  | Compresses dynamic range, lowers contrast, masks semantic regions
Occlusion / Cutoff    | Physical foreground obstructions | Masks critical keypoints/parts, breaks holistic spatial priors
Compression Artifacts | Block DCT quantization, subsampling| Introduces 8x8 grid boundaries, removes fine textural details
AI コーチを使ってこの質問に答えてみる

7HOG(Histogram of Oriented Gradients)などの古典的な記述子は画像の構造をどのように表現しますか。また、学習されたCNN(Convolutional Neural Network)特徴量と比較してどのような限界がありますか?

HOG(Histogram of Oriented Gradients)は、輝度勾配の方向の分布を通じて物体の局所的な形状や外観を捉えるために設計された、古典的な人手設計(ハンドクラフト)の特徴記述子です。 HOGの計算パイプラインは以下の通りです。 (1) 勾配の強度と方向を求めるため、1次微分フィルタ(例: [-1, 0, 1])などを用いて画像の水平方向および垂直方向の勾配を計算します。 (2) 画像を「セル」と呼ばれる小さな局所領域(8x8ピクセルなど)に分割し、勾配の強度で重み付けした勾配方向の1次元ヒストグラムを集計します。 (3) 隣接するセルを重複を許してより大きな「ブロック」(2x2セルなど)にまとめ、ブロック特徴量ベクトルを正規化(L2ノルムやL1-sqrtなどを使用)することで、局所的な照明、コントラスト、陰影の変動に対する耐性を獲得します。 (4) 正規化されたブロックベクトルを連結して最終的な1次元特徴量表現を生成します。歴史的には歩行者検知や物体検知のために線形SVM(Support Vector Machine)と組み合わせて使用されてきました。 学習ベースのCNN(Convolutional Neural Network、畳み込みニューラルネットワーク)特徴量と比較して、HOGには大きな限界があります。第一に、HOG特徴量は固定の人手設計であり、タスク固有の階層的表現(テクスチャ、物体のパーツ、意味的概念など)を学習する能力を持たず、低レベルな局所エッジ方向の統計量しか捉えられません。第二に、幾何学的変化に対する不変性が極めて限定的です。ブロック正規化によって単調な輝度変化やセル内の微小な空間的移動には対処できますが、3次元の面外回転、顕著なスケール変化、非剛体な姿勢の変化、複雑な背景クラッターに対しては脆弱です。これに対し、CNNは目的関数に向けてエンドツーエンドで最適化された多層の非線形な抽象表現を学習できます。

Input Image Patch (e.g., 64x128)
  │
  ▼
[ 1. Compute Gradients ] ──> Gx, Gy -> Mag = sqrt(Gx^2 + Gy^2), Angle = atan2(Gy, Gx)
  │
  ▼
[ 2. Cell Histograms   ] ──> 8x8 pixel cells accumulate 9-bin orientation histograms (0°-180°)
  │
  ▼
[ 3. Block Normalization] ──> Overlapping 2x2 cell blocks normalized (e.g., v / sqrt(||v||_2^2 + eps^2))
  │
  ▼
[ 4. Feature Vector    ] ──> Concatenate block vectors -> Feed to Linear SVM / Classifier
AI コーチを使ってこの質問に答えてみる

中級向け質問

8ストライド(stride)、パディング(padding)、ダイレーション(dilation)、カーネルサイズは、畳み込みの出力サイズと受容野(receptive field)をどのように決定しますか?

畳み込み層の空間的な出力次元と累積受容野(receptive field)は、カーネルサイズ $k$、ストライド $s$、パディング $p$、およびダイレーション $d$ によって決定されます。 1. **出力の空間サイズ**: ダイレーションはカーネル要素間に $(d - 1)$ 個の隙間を挿入するため、有効カーネルサイズは $k' = d(k - 1) + 1$ となります。特定軸に沿った出力次元は次式で与えられます: $$O = \left\lfloor \frac{I + 2p - k'}{s} \right\rfloor + 1 = \left\lfloor \frac{I + 2p - d(k - 1) - 1}{s} \right\rfloor + 1$$ ここで、ストライドはカーネルをステップごとに $s$ ピクセル進めることで解像度をダウンサンプリングし、パディングは仮想的な境界ピクセルを追加して空間次元を維持または調整し、ダイレーションは追加のパラメータを増やすことなくフィルタの適用範囲(フットプリント)を拡大します。 2. **受容野(RF: Receptive Field)**: 理論上の受容野とは、特定のアクティベーションに影響を与える入力画像内の空間的な広がりを表します。複数層を重ねた場合、受容野 $RF_l$ と累積ストライド(ジャンプ $j_l$)は再帰的に更新されます: - ジャンプ:$j_l = j_{l-1} \cdot s_l$(初期値: $j_0 = 1$) - 受容野:$RF_l = RF_{l-1} + (k'_l - 1) \cdot j_{l-1}$(初期値: $RF_0 = 1$) 後続層のカーネルステップが元の入力座標空間におけるより大きなピクセルジャンプに対応するため、ストライドやプーリングは層の深さに応じて受容野を乗算的に拡大します。一方、ダイレーションは空間的なダウンサンプリングを伴わずに $k'_l$ を増加させることで、単一レイヤー内で受容野を加算的に拡大します。

def compute_conv_output_and_rf(layers, input_size=224):
    current_size = input_size
    rf = 1
    jump = 1
    
    for idx, layer in enumerate(layers, 1):
        k, s, p, d = layer['k'], layer['s'], layer['p'], layer['d']
        k_eff = d * (k - 1) + 1
        current_size = ((current_size + 2 * p - k_eff) // s) + 1
        rf = rf + (k_eff - 1) * jump
        jump = jump * s
        print(f"Layer {idx}: Out={current_size}x{current_size}, RF={rf}x{rf}, Jump={jump}")

layers = [
    {'k': 3, 's': 1, 'p': 1, 'd': 1},  # Conv1: 3x3 standard
    {'k': 3, 's': 2, 'p': 1, 'd': 1},  # Conv2: 3x3 strided
    {'k': 3, 's': 1, 'p': 2, 'd': 2},  # Conv3: 3x3 dilated (d=2)
]
compute_conv_output_and_rf(layers, 224)
AI コーチを使ってこの質問に答えてみる

9畳み込みカーネルは古典的な線形画像フィルタとどのように対応しており、2次元フィルタはどのような場合に分離可能にできますか?

古典的な線形信号処理および画像処理において、畳み込みカーネルは線形シフト不変(LSI: Linear Shift-Invariant)システムの空間インパルス応答(点広がり関数)を表します。カーネルの適用は空間領域における線形フィルタリング処理であり、画像の周波数応答(2次元フーリエ変換)を変化させます。低域通過カーネル(ガウシアンフィルタ、ボックスブラーなど)は高空間周波数を減衰させてノイズを除去しテクスチャを平滑化する一方、高域通過カーネルまたは帯域通過カーネル(Sobel、Laplacian、Prewittなど)は高周波数を強調してエッジや勾配を検出します。 2次元フィルタカーネル $K \in \mathbb{R}^{M \times N}$ は、2つの1次元フィルタの外積として $K = u \cdot v^T$(ただし $u \in \mathbb{R}^{M \times 1}$、かつ $v \in \mathbb{R}^{N \times 1}$)のように分解できる場合、空間的に分離可能です。線形代数の観点では、2次元行列が分離可能であるための必要十分条件は、その行列のランクが1であることです。これは特異値分解(SVD: Singular Value Decomposition)によって確認でき、特異値のうちちょうど1つだけが非ゼロ($\sigma_1 > 0, \sigma_2 = \dots = 0$)となります。 分離可能性により、計算量は大幅に削減されます。$H \times W$ の画像に分離不可能な $K \times K$ のカーネルを適用するには $O(H \cdot W \cdot K^2)$ の乗算と加算が必要です。2つの連続する1次元パス(水平方向、続いて垂直方向)に分解した場合、計算量は $O(H \cdot W \cdot 2K)$ に削減されます。例えば $15 \times 15$ のカーネルの場合、約7.5倍の高速化が実現されます。

import numpy as np

# Gaussian kernel is rank-1 (separable)
gaussian_1d = np.array([1, 2, 1], dtype=float)[:, None]
gaussian_2d = gaussian_1d @ gaussian_1d.T
gaussian_2d /= gaussian_2d.sum()

# Sobel-X kernel is rank-1 (separable)
sobel_x = np.array([[-1, 0, 1],
                    [-2, 0, 2],
                    [-1, 0, 1]], dtype=float)

# Diagonal kernel is rank-2 (non-separable)
non_sep = np.array([[1, 0],
                    [0, 1]], dtype=float)

print("Gaussian rank:", np.linalg.matrix_rank(gaussian_2d))
print("Sobel-X rank:", np.linalg.matrix_rank(sobel_x))
print("Diagonal rank:", np.linalg.matrix_rank(non_sep))

# SVD decomposition for Sobel-X
U, S, Vt = np.linalg.svd(sobel_x)
print("Sobel-X singular values:", np.round(S, 4))
AI コーチを使ってこの質問に答えてみる

10リサイズ、中央クロップ、ランダムリサイズクロップ、アスペクト比を無視した引き伸ばし(stretching)、およびレターボックス化は、入力データの分布をどのように変化させますか?

リサイズやクロップの戦略の違いにより、幾何学的構造、スケール、被写体のカバー率、空間的な境界にわたって入力データの分布が変化します:1. **直接的なリサイズ(引き伸ばし/圧縮):** 元のアスペクト比を歪ませ、画像を異方的に目標サイズ $(H, W)$ にリサイズします。これにより、モデルは変形した物体の形状(円形の物体が細長い楕円になるなど)を処理することを強いられます。2. **中央クロップ(Center Cropping):** 固定サイズまたは固定比率で中央部分を切り出します。元のアスペクト比とローカルスケールを維持しますが、強い中心バイアス(対象が中央に配置されているという前提)を生み出し、周辺の文脈情報を破棄したり端にある物体を切断したりします。3. **ランダムリサイズクロップ(Random-Resized Cropping: RRC):** 様々な面積スケールとアスペクト比でランダムに部分領域を切り出し、固定寸法にリサイズします。これにより学習時のスケール分布が広がり、局所的な特徴の学習が促進されますが、極端な切り抜きによって対象物体が完全に除外される可能性があります。4. **レターボックス化(パディングを伴う等方リサイズ):** 最長辺が目標サイズに収まるまで画像を均等にスケーリングし、余った境界部分を一定値でパディングします。本来の物体の比率とアスペクト比を維持しますが、人工的な高コントラストの境界線や情報を持たないパディングピクセルが導入されます。

import cv2
import numpy as np

def letterbox_image(image, target_size=(640, 640), pad_color=(114, 114, 114)):
    h, w = image.shape[:2]
    target_w, target_h = target_size
    scale = min(target_w / w, target_h / h)
    new_w, new_h = int(w * scale), int(h * scale)
    
    resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
    canvas = np.full((target_h, target_w, 3), pad_color, dtype=np.uint8)
    
    top = (target_h - new_h) // 2
    left = (target_w - new_w) // 2
    canvas[top:top+new_h, left:left+new_w] = resized
    return canvas, scale, (left, top)
AI コーチを使ってこの質問に答えてみる

11コンピュータビジョン向けデータセットで発生するアノテーションノイズの種類にはどのようなものがあり、それらをどのように検出または軽減しますか?

コンピュータビジョンのデータセットには、主に4つのアノテーションノイズのパターンが存在します。 1. **カテゴリラベルのノイズ(Categorical Label Noise):** 画像またはオブジェクトに誤ったクラスラベルが割り当てられている状態(例: 猫を犬と誤分類する、細分化された類似クラス間での混同)。 2. **アノテーションの欠落(Omission Noise):** 対象となる前景オブジェクトにアノテーションが付与されていない状態。物体検出タスクでは、見落とされた対象が背景(ネガティブサンプル)として扱われるため、学習中にモデルの正しい検出に対してペナルティが科されることになります。 3. **バウンディングボックス/キーポイントのズレ(Localization Noise):** アノテーター間の作業のばらつきによる、大まかすぎる・位置がずれている・不正確なバウンディングボックスの座標やランドマークの位置。 4. **マスク境界の曖昧さ(Mask Boundary Ambiguity):** 複雑な境界や不鮮明な輪郭(例: 毛髪、半透明の表面、モーションブラーなど)に沿ったセグメンテーションのアウトラインが粗かったり、不統一だったりする状態。 **検出および軽減戦略:** - **損失の外れ値追跡とコンフィデントラーニング(Confident Learning):** 学習エポック全体でサンプルの損失を追跡し、一貫して高い損失を示す外れ値を特定します。これらはラベルの誤りや欠落を示しているケースが多く見られます。また、Confident Learningのような手法を用いてノイズ分布を推定し、エラーのプルーニング(除外)や修正を行います。 - **Out-of-Fold(OOF)予測の乖離検出:** クロスバリデーションモデルを学習し、ホールドアウト検証データの予測結果と正解ラベル(ground truth)を比較することで、誤ラベルの画像や欠落したバウンディングボックスを洗い出します。 - **アノテーター間一致率(IAA: Inter-Annotator Agreement)と調停:** 複数人のアノテーターによる結果を比較し、Cohen's Kappa(分類タスク)や平均IoU(Intersection-over-Union、局所化タスク)などの指標を測定することで、一致率が低いサンプルを特定して合議による確認に回します。 - **ロバストな損失関数の採用:** ラベルスムージング、ノイズ耐性のある分類損失、あるいは外れ値に強いIoUベースの回帰損失を採用することで、位置のズレやラベルの誤りに対するモデルの過敏性を抑止します。

import torch
import torch.nn.functional as F

def find_label_noise_candidates(model, dataloader, device, top_k=50):
    model.eval()
    sample_losses = []
    
    with torch.no_grad():
        for batch_idx, (images, targets, sample_ids) in enumerate(dataloader):
            images, targets = images.to(device), targets.to(device)
            logits = model(images)
            loss_per_sample = F.cross_entropy(logits, targets, reduction='none')
            
            for sid, l, target, pred in zip(sample_ids, loss_per_sample.cpu(), targets.cpu(), logits.argmax(dim=-1).cpu()):
                sample_losses.append({'id': sid, 'loss': l.item(), 'target': target.item(), 'pred': pred.item()})
                
    sample_losses.sort(key=lambda x: x['loss'], reverse=True)
    return sample_losses[:top_k]
AI コーチを使ってこの質問に答えてみる

12クラス不均衡は、画像分類、物体検出、および高密度予測(dense prediction)の学習にどのような影響を与えますか?

クラス不均衡は、コンピュータビジョンの各タスクにおいて最適化ダイナミクスと損失ランドスケープに影響を与えます。 1. **画像分類:** 多数派クラスのデータ数が少数派クラスを大きく上回る場合、経験的リスク最小化によってパラメータ更新が多数派クラスの勾配に支配されます。モデルは経験的なクラス事前分布 $P(Y)$ を学習し、決定境界が希少クラスに対して不利に偏るようになります。その結果、全体のTop-1正解率は高くなる一方で、少数派クラスの再現率(recall)が著しく低下(崩壊)します。 2. **物体検出:** - *前景・背景の不均衡:* 高密度なシングルステージ検出器やアンカーベースの検出器では、画像1枚あたり数十万の候補領域が評価され、その99%以上が背景となります。個々の分類しやすい背景アンカーの損失が小さくても、それらの膨大な累積勾配が、スパースな前景オブジェクトからの有益な勾配シグナルを圧倒してしまいます。 - *前景クラス間の不均衡:* 一般的な物体カテゴリは希少なカテゴリに比べて桁違いに多く出現するため、希少クラスに対する有効サンプルサイズと学習シグナルが減少します。 3. **高密度予測(セグメンテーション):** 広大な背景や「stuff(非個別領域)」クラス(道路、空など)が数百万ピクセルを占めるのに対し、小さな「thing(個別物体)」クラス(標識、歩行者など)は画像ピクセルの0.1%未満しか占めない場合があるため、ピクセルレベルの不均衡が深刻になります。標準的なピクセル単位のクロスエントロピー損失は大面積のクラスに支配され、小さく細い構造のセグメンテーション不足や完全な消失につながります。

# Illustrating cumulative gradient dominance by background anchors
import torch

num_bg_anchors = 100_000
num_fg_anchors = 10

bg_loss_per_sample = torch.tensor(0.001, requires_grad=True)
fg_loss_per_sample = torch.tensor(1.5, requires_grad=True)

total_loss = (num_bg_anchors * bg_loss_per_sample) + (num_fg_anchors * fg_loss_per_sample)
total_loss.backward()

print(f'Total BG gradient contribution: {num_bg_anchors * bg_loss_per_sample.grad.item():.1f}')
print(f'Total FG gradient contribution: {num_fg_anchors * fg_loss_per_sample.grad.item():.1f}')
AI コーチを使ってこの質問に答えてみる

上級向け質問

13大規模な分散ビジョントレーニングにおいて、NaN (Not a Number) の発生や損失の不安定性をどのようにデバッグしますか?

大規模なDDP(Distributed Data Parallel)ビジョントレーニングにおけるNaNや損失の不安定性のデバッグでは、根本原因が不正な入力データにあるのか、混合精度(AMP)における数値のオーバーフロー/アンダーフローにあるのか、あるいはワーカー間の最適化の不安定性にあるのかを切り分ける必要があります。 まず、決定論的動作の確保と安全のためのフックを設定します。異常検知(`torch.autograd.set_detect_anomaly(True)`)を有効化し、勾配や活性化のフックを登録してNaNが発生した正確なレイヤーを特定します。また、DataLoaderに厳格なデータ検証を追加します(有限値のアサーション、0バイトの破損画像、空のバウンディングボックス、正規化時の分散ゼロによる除算のチェックなど)。ミニバッチ単位のロギングでは、ランクごとの損失、入力URI、クリッピング前の勾配ノルム、およびGradScalerのスケール係数を記録すべきです。 次に、自動混合精度(AMP)と動的損失スケーリングを調査します。FP16では大きな勾配が容易にオーバーフロー(`> 65504`)し、損失スケーラーがステップをスキップしてスケール係数を半減させ続け、最終的にスケールがゼロに達することがあります。不安定な演算(softmax、LayerNorm、Focal Lossの指数計算、バウンディングボックスのIoU分母など)をFP32に切り替えるか、FP32と同等のダイナミックレンジを持つBF16を採用することで、通常は学習が安定します。 最後に、単一ワーカーのNaNが全ランクに伝播するall-reduce処理や、学習率のウォームアップスケーリング(大規模なグローバルバッチサイズに対する線形スケーリング則など)、勾配クリッピングといったDDP特有の落とし穴を確認します。

import torch
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)

for step, (images, targets, uris) in enumerate(dataloader):
    # 1. Input sanitization
    if not torch.isfinite(images).all():
        print(f"Corrupt input detected from URIs: {uris}")
        continue
        
    optimizer.zero_grad(set_to_none=True)
    
    with autocast(dtype=torch.float16):
        outputs = model(images)
        loss = criterion(outputs, targets)
    
    if not torch.isfinite(loss):
        print(f"NaN/Inf loss at step {step} on rank {torch.distributed.get_rank()}; skipping step.")
        continue
        
    scaler.scale(loss).backward()
    
    # Unscale before clipping to inspect true gradient norms
    scaler.unscale_(optimizer)
    grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    
    if torch.isnan(grad_norm) or torch.isinf(grad_norm):
        print(f"Warning: Grad norm is {grad_norm}. Scaler will adjust.")
        
    scaler.step(optimizer)
    scaler.update()
AI コーチを使ってこの質問に答えてみる

14フレームの揺れ(ジッター)が発生するカメラ向けに、オンラインのデジタル動画像手ブレ補正コンポーネントをどのように設計しますか?

リアルタイムのカメラの揺れに対応するオンラインデジタル動画像手ブレ補正パイプラインの設計は、主に動き推定、動き平滑化、動き補正(ワーピング)、境界処理の4つのステージで構成されます。 1. **フレーム間動き推定:** 高速な特徴点検出器(ORB、FAST、Shi-Tomasi法など)を用いて連続するフレーム間で2Dスパースキーポイントを抽出し、Lucas-Kanade法によるオプティカルフローまたは特徴量記述子のマッチングで対応点を計算します。独立して動く前景オブジェクトによる外れ値マッチングを除外するため、RANSACを用いてフレーム間の幾何変換(アフィン変換やホモグラフィモデルなど)を推定します。 2. **運動軌跡の累積とオンライン平滑化:** フレーム間の変換を行列の積として時間軸に沿って累積し、カメラの累積軌道 $P_t = P_{t-1} \cdot H_t$ を保持します。1D/2Dカルマンフィルタや短いウィンドウの因果的移動平均などのオンライン平滑化フィルタを適用し、意図しない高周波の揺れと意図的な低周波のカメラパンを分離します。 3. **補正と画像ワーピング:** 補正変換 $C_t = S_t \cdot P_t^{-1}$($S_t$ は平滑化された軌道)を計算し、バイリニア補間またはバイキュービック補間を用いて現在のフレームをワーピング(幾何補正)します。 4. **境界処理とレイテンシ制約:** ワーピングによって生じる外周ピクセルの欠落(黒枠)に対しては、境界外挿や適応的スケーリングを組み合わせた固定の動的クロップ率(5〜10%のズームインなど)を適用して解決します。オンライン環境では処理遅延を抑えるため、バッファリングを最小限(先読み1〜3フレーム程度)にとどめます。

import cv2
import numpy as np

class OnlineStabilizer:
    def __init__(self, crop_ratio=0.9):
        self.prev_gray = None
        self.smoothed_x = 0.0
        self.smoothed_y = 0.0
        self.smoothed_a = 0.0
        self.alpha = 0.85  # Low-pass filter smoothing coefficient
        self.crop_ratio = crop_ratio

    def process_frame(self, frame):
        curr_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        h, w = curr_gray.shape
        
        if self.prev_gray is None:
            self.prev_gray = curr_gray
            return frame
            
        # 1. Feature detection & tracking
        p0 = cv2.goodFeaturesToTrack(self.prev_gray, maxCorners=200, qualityLevel=0.01, minDistance=30)
        p1, status, _ = cv2.calcOpticalFlowPyrLK(self.prev_gray, curr_gray, p0, None)
        
        good_p0 = p0[status == 1]
        good_p1 = p1[status == 1]
        
        # 2. Estimate rigid motion (dx, dy, da) with RANSAC
        T, inliers = cv2.estimateAffinePartial2D(good_p0, good_p1, method=cv2.RANSAC)
        if T is None: 
            return frame
            
        dx = T[0, 2]
        dy = T[1, 2]
        da = np.arctan2(T[1, 0], T[0, 0])
        
        # 3. Online low-pass filtering of jitter
        self.smoothed_x = self.alpha * self.smoothed_x + (1 - self.alpha) * dx
        self.smoothed_y = self.alpha * self.smoothed_y + (1 - self.alpha) * dy
        self.smoothed_a = self.alpha * self.smoothed_a + (1 - self.alpha) * da
        
        diff_x = self.smoothed_x - dx
        diff_y = self.smoothed_y - dy
        diff_a = self.smoothed_a - da
        
        # 4. Warp and crop frame
        M = np.array([
            [np.cos(diff_a), -np.sin(diff_a), diff_x],
            [np.sin(diff_a),  np.cos(diff_a), diff_y]
        ])
        stabilized = cv2.warpAffine(frame, M, (w, h))
        
        # Center-crop to remove boundary artifacts
        cw, ch = int(w * self.crop_ratio), int(h * self.crop_ratio)
        x1, y1 = (w - cw) // 2, (h - ch) // 2
        stabilized = cv2.resize(stabilized[y1:y1+ch, x1:x1+cw], (w, h))
        
        self.prev_gray = curr_gray
        return stabilized
AI コーチを使ってこの質問に答えてみる

15検出器の処理速度が目標フレームレートよりも遅い場合、リアルタイムのビジョンパイプラインをどのように最適化しますか?

物体検出器が目標フレームレートを維持できない場合(例: 60 FPSの動画入力に対して15〜20 FPSでしか動作しない場合)、本番環境向けの堅牢なアーキテクチャでは、重い検出処理と軽量なトラッキングを組み合わせた非同期マルチスレッドパイプラインを用いて、検出処理を表示・描画ループから切り離します。このハイブリッドアーキテクチャでは、フレーム取り込みスレッドが継続的に動画フレームを取得します。重い検出器は、バックグラウンドのワーカースレッド上で「キーフレーム検出器」として非同期に動作します。一方で、軽量なトラッカー(Optical Flow Lucas-Kanade法、ByteTrack/BoT-SORT、高速な相関フィルタ/カルマンフィルタなど)は、全フレームに対して60 FPSで同期的に実行され、物体の状態、一意識別子(ID)、滑らかなバウンディングボックスの軌跡を維持します。レイテンシの増大や古いフレームの蓄積を防ぎつつ遅延とバックプレッシャーを制御するために、サイズ制限付きリングバッファや最新フレーム優先(古いフレームの破棄)ポリシーを採用します。検出器が時刻 T_curr において過去のフレーム T_0 の処理を完了した際、その出力は過去のものとなっています。そのため、パイプラインは座標の再調整(バックプロジェクション)を実行します。具体的には、遅延した検出結果を T_0 時点の過去の追跡状態(トラックレット)と関連付け、IDの更新や未検出トラックの補正を行い、トラッキングの移動ベクトルやカルマンフィルタの予測ステップを介して T_curr まで補正を伝播させます。

import queue
import threading

frame_queue = queue.Queue(maxsize=1)  # Drop stale frames, keep latest
det_result_queue = queue.Queue()

def detector_worker():
    while True:
        frame, frame_id, timestamp = frame_queue.get()
        boxes, scores, classes = heavy_detector.infer(frame)
        det_result_queue.put({'frame_id': frame_id, 'boxes': boxes, 'timestamp': timestamp})

def realtime_pipeline(video_stream):
    tracker = FastTracker()  # e.g. Optical flow or Kalman tracker
    
    for frame, frame_id, timestamp in video_stream:
        if frame_queue.empty():
            frame_queue.put((frame, frame_id, timestamp))
            
        if not det_result_queue.empty():
            det_result = det_result_queue.get()
            tracker.reconcile_and_correct(det_result, current_frame_id=frame_id)
            
        active_tracks = tracker.update(frame)
        display_or_downstream(frame, active_tracks)
AI コーチを使ってこの質問に答えてみる