---
title: "エッジ検出とは？Canny法と微分フィルタの使い分けをOpenCVの実装で解説"
url: "https://www.issoh.co.jp/tech/details/16854/"
published: 2026-08-23
updated: 2026-08-23
categories: ["AI"]
publisher: "株式会社一創"
---

# エッジ検出とは？Canny法と微分フィルタの使い分けをOpenCVの実装で解説

エッジ検出は、隣り合う画素の明るさがどれだけ急に変わるかを見て、物の境目の位置を取り出す処理です。原理は微分に尽きます。それでも実装で結果が揺れるのは、どの微分フィルタで勾配を取るか、どの高さで切るかの2つに条件が集まるからです。この記事では、3種類の微分フィルタが拾うエッジの差、Canny法が内部で踏む工程、OpenCVの実装が持つ入力型とパラメータの制約、findContoursへ渡して寸法や欠陥を数値にするまでを実装の解像度で整理しました。

## まとめ｜エッジ検出で決まるのは勾配の取り方と二段しきい値の置き方

設計で実際に決めることは2つです。

1つ目は**勾配をどのフィルタで取るか**。一次微分（Sobel・Scharr）は変化の傾きが大きい場所を、二次微分（Laplacian）は傾きの符号が反転する場所を拾います。輪郭を線として取り出すなら一次微分側が起点です。

2つ目は**どの高さで切るか**で、ここがCanny法の二段しきい値です。OpenCVのヘッダにも「小さい方の値がエッジの連結に、大きい方の値が強いエッジの初期セグメントの検出に使われる」と書かれており、2つの数字は役割が別物だと理解して動かしてください。

そして押さえておきたいのが、**教科書どおりの5工程のうち最初の平滑化を、OpenCVの`cv2.Canny`は自分で行わない**という点です。canny.cppを追うと、入力に対していきなりSobelを呼んでいます。ぼかしを掛けずに渡してノイズが偽のエッジになる事故は、この仕様を知らないまま起きます。

## エッジ検出とは何か｜明るさの変化率から輪郭の位置を取り出す仕組み

エッジ検出は、画像の中で明るさが急に変わる位置を見つけ、そこを「境目」として出力する処理です。出力は境目だけが白く残った8ビットの単チャンネル画像になります。物体の形を測る、部品を数えるといった後段の精度は、この境目が正しく出ているかで決まります。

工程全体の中でエッジ検出がどこに位置し、どこから学習ベースの認識に切り替わるのかを先に俯瞰するなら、親記事の[画像認識AIとは？仕組み・できること・開発の進め方をわかりやすく解説](https://www.issoh.co.jp/column/details/12928/)で前処理と認識の分担を押さえておくと、以降の判断が早くなります。

### 一次微分と二次微分でエッジのどの位置を捉えるのかが変わる違い

明るさが暗から明へなだらかに変わる断面を思い浮かべてください。1回微分すると変化が最も急な位置に山ができ、もう1回微分するとその頂点は値がゼロを跨ぐ点になります。一次微分は「傾きの大きさ」を、二次微分は「傾きが反転する場所（ゼロ交差）」を見ているわけです。

拾い方の性格も違います。一次微分は境目を幅のある帯として出すため、後段で1画素幅に絞る処理が要る。二次微分は位置決めが鋭い反面ノイズを二重に増幅し、事前のぼかしがないと点状の偽エッジで埋まります。OpenCVでは一次微分が`cv2.Sobel`と`cv2.Scharr`、二次微分が`cv2.Laplacian`に対応します。

### 二値化が明るさで切るのに対しエッジ検出が変化率で切る役割の差

二値化とエッジ検出は同じ「白と黒に分ける処理」に見えて、見ている量が違います。二値化は画素の明るさそのものをしきい値と比べ、対象を面として塗り分ける。エッジ検出は隣接画素との差を見て、対象を線として取り出します。

背景と対象の明るさが分かれているなら二値化のほうが速く確実です。明るさが近く境目にだけコントラストがある場面ではエッジ側に分があります。しきい値処理そのものの手法選定は[二値化とは？しきい値の決め方と大津・適応的の使い分けをOpenCVの実装で解説](https://www.issoh.co.jp/tech/details/16847/)で扱っているので、面で切るか線で切るかを先に決めてから読み分けてください。

## 微分フィルタの選び分け｜SobelとScharrとLaplacianが拾うエッジの差

Canny法の内側で使われている微分フィルタを先に押さえます。3種類の性格を知っておくと、結果が期待とずれたときの切り分けが効きます。

| フィルタ      | 微分の次数   | カーネルサイズ   | 向く場面    |
| --------- | ------- | --------- | ------- |
| Sobel     | 一次（方向別） | 3・5・7から選択 | 汎用の勾配計算 |
| Scharr    | 一次（方向別） | 3×3に固定    | 細部の角度精度 |
| Laplacian | 二次（等方性） | 3×3ほか奇数   | 細線と点の強調 |

実務でまず触るのはSobelです。Scharrは3×3のまま精度を上げたいときの差し替え先になります。

### Sobelのksizeが1と3と5と7に限られる制約と出力深度の罠

OpenCVのヘッダによれば、Sobelの`ksize`に指定できるのは1・3・5・7のいずれかです。1を渡した場合だけ3×1または1×3のカーネルになり、ガウシアン平滑化の効果を伴いません。特殊値の`FILTER_SCHARR`（値は-1）を渡すとScharrに切り替わります。

つまずくのは`ddepth`です。ヘッダには「8ビット入力画像の場合は導関数が切り捨てられる」と明記されています。微分の結果は負の値を持つのに、出力先を`CV_8U`にすると負側が0に潰れ、片側の境目だけが消えた出力になる。

```
import cv2

img = cv2.imread("part.png", cv2.IMREAD_GRAYSCALE)

# 出力は CV_16S で受け、絶対値を取ってから8ビットへ戻す
gx = cv2.Sobel(img, cv2.CV_16S, 1, 0, ksize=3)
gy = cv2.Sobel(img, cv2.CV_16S, 0, 1, ksize=3)

edge = cv2.addWeighted(cv2.convertScaleAbs(gx), 0.5,
                       cv2.convertScaleAbs(gy), 0.5, 0)
```

いったん`CV_16S`か`CV_32F`で受け、`convertScaleAbs`で絶対値を取ってから8ビットへ戻す。この2段構えが定石です。境界の外挿では`BORDER_WRAP`だけが非対応です。

### Scharrを3×3で使うとSobelより角度の精度が上がる条件

Scharrのx微分カーネルは、ヘッダの記載どおり(-3,0,3 / -10,0,10 / -3,0,3)です。3×3のSobel（-1,0,1 / -2,0,2 / -1,0,1）と比べて中央行の重みが相対的に大きく、回転に対する勾配方向の誤差が小さくなります。

効くのは勾配の「方向」を後段で使う場合です。斜めの境目の角度を測る処理では素直な値が出る。強さしか使わないなら差はほとんど見えません。

### Laplacianの二次微分がノイズを拾いやすい理由とksizeの既定

OpenCVの`Laplacian`は`ksize`の既定値が1で、このとき(0,1,0 / 1,-4,1 / 0,1,0)の3×3カーネルで計算されます。1より大きい場合はSobelで求めたxとyの二次微分の和で、サイズは正の奇数に限られます。

二次微分は一次微分をもう一度掛ける操作で、ノイズの増幅も二乗で効きます。ぼかしを先に入れずに掛けると、砂粒のようなノイズが一斉にゼロ交差を作り輪郭が判別できない出力になる。ガウシアンぼかしと組み合わせたLoGが定番の形なのは、この性質への対処です。

## Canny法の内部処理｜平滑化から非最大抑制とヒステリシス処理まで

Canny法は1986年にJohn F. Cannyが発表したアルゴリズムで、OpenCVのヘッダでも Canny86 の文献として参照されています。単体の微分フィルタとの違いは、勾配を取ったあとに「細らせる」「つなぐ」の2工程を持つ点にあります。

### Canny法が踏む5工程と各工程が落とすノイズと偽エッジの内訳

アルゴリズムとしてのCanny法は、次の順に処理を進めます。

1. ガウシアンぼかしでノイズを落とす
2. Sobelでxとyの勾配を求める
3. 勾配の強さと方向を計算する
4. 非最大抑制でエッジを1画素幅に細らせる
5. 二段しきい値とヒステリシスで弱いエッジを取捨する

注意したいのが工程1の扱いです。canny.cppを読むと、OpenCVの`Canny`は入力に対していきなり`Sobel`を呼んでおり、関数の内部でガウシアンぼかしを掛けていません。Sobelのカーネル自体の平滑化効果はありますが、明示的なノイズ低減は呼び出し側の責任です。ざらついた画像で偽のエッジが大量に出るときは、まず`cv2.GaussianBlur`を前段に入れてください。

工程4の中身も実装を見ると分かります。勾配方向は`TG22`という定数（値は13573で、tan22.5度を15ビットの固定小数にしたもの）で4方向に量子化され、その方向で隣接する2画素より強くない画素が捨てられる。これが「1画素幅に細らせる」の正体です。

### 二段しきい値の低い側が連結に高い側が起点として使われる仕組み

二段しきい値は強いエッジと弱いエッジを別扱いする仕掛けです。高いほうを超えた画素は無条件でエッジ確定、低いほうにも届かない画素は破棄。中間の画素は確定エッジと連結している場合にだけ生き残ります。これがヒステリシス処理です。

実装内部では各画素に0（可能性あり）・1（エッジではない）・2（確定）の3値が振られ、確定画素から連結をたどって中間の画素を引き上げます。コントラストが途中で落ちる長い輪郭が途切れずに残るのは、この引き上げによるものです。

なお引数の順序を間違えても例外にはなりません。canny.cppでは低いほうが高いほうより大きい場合に`std::swap`で入れ替えています。動くけれど意図とは違う結果になる形で表面化するので、値の意味は把握しておいてください。

### L2gradientを有効にすると拾うエッジが減る計算上の理由

`L2gradient`の既定値はfalseで、このとき勾配の強さはL1ノルム、つまりxとyの微分の絶対値の和で計算されます。trueにするとユークリッドノルム（二乗和の平方根）に切り替わります。

同じ画素でもL1のほうが値は大きく出ます。斜め45度に走る境目では最大でおよそ1.41倍の差になり、しきい値を据え置いたままtrueへ切り替えると拾えるエッジが減る。精度を上げたつもりで輪郭が痩せた、という現象の正体はこれです。切り替えるならしきい値も一緒に下げ直してください。

## OpenCVでのCanny実装｜入力型とapertureSizeが結果を変える条件

ここからは呼び出し側の話です。導入手順や基本操作は[OpenCVをPythonで使う画像処理入門｜インストールから顔検出まで実コードで解説](https://www.issoh.co.jp/tech/details/1344/)に譲り、パラメータが結果をどう動かすかに絞ります。なお2026年8月23日時点のopencv-pythonは、5系が5.0.0.93（2026-07-02公開）、4系が4.14.0.94（2026-07-29公開）です。

```
import cv2

img = cv2.imread("part.png", cv2.IMREAD_GRAYSCALE)
blur = cv2.GaussianBlur(img, (5, 5), 1.4)

edges = cv2.Canny(blur, 80, 160, apertureSize=3, L2gradient=False)
cv2.imwrite("edges.png", edges)
```

### 入力が8ビット限定という制約と16ビットの産業用カメラの扱い

canny.cppの入口には`CV_Assert( _src.depth() == CV_8U )`が置かれています。8ビット以外の画像を渡すとそこで例外になる。16ビットで出力する産業用カメラの画像はそのまま渡せません。

回避策は2つあります。1つは`convertScaleAbs`や正規化で8ビットへ落として渡す方法。もう1つは16ビットのまま自分でSobelを掛け、勾配を受け取るオーバーロードへ渡す方法で、`dx`と`dy`はCV\_16SC1かCV\_16SC3を求められます。階調の分解能を保ったまま非最大抑制とヒステリシスだけを任せられるため、微弱なコントラストの検査では後者が効きます。

### apertureSizeを7にするとしきい値が16分の1に割られる仕様

`apertureSize`は内部のSobelのアパーチャサイズで、指定できるのは3から7の奇数か-1（Scharr）です。範囲外は「Aperture size should be odd between 3 and 7」というエラーで止まります。

知られていないのは7を指定したときの挙動です。canny.cppでは7のときだけ低いしきい値と高いしきい値を16.0で割り、Sobel側にも`scale`として16分の1を渡しています。両者は比較の上で打ち消し合うため、しきい値の意味はSobelの生の出力基準のまま。桁あふれを避ける保険であって、アパーチャを広げてもしきい値を据え置いてよいという意味ではありません。7のカーネルは係数の絶対値が桁で大きく、同じ数値のまま広げれば拾うエッジは一気に増えます。

### しきい値の初期値を画素の中央値から算出して現場で追い込む手順

しきい値の初期値を勘で置くと、画像が変わるたびに調整をやり直すことになります。広く使われる出発点が、グレースケール画像の画素値の中央値mから `lower = 0.67 * m`、`upper = 1.33 * m` を出す方法です。

```
import cv2
import numpy as np

gray = cv2.imread("part.png", cv2.IMREAD_GRAYSCALE)
blur = cv2.GaussianBlur(gray, (5, 5), 1.4)

m = float(np.median(blur))
lower = int(max(0, 0.67 * m))
upper = int(min(255, 1.33 * m))
edges = cv2.Canny(blur, lower, upper)
```

現場で追い込む順序も決まっています。まず高いほうを動かし、拾いたい輪郭の起点が確実に残る値を探す。次に低いほうを下げ、途切れた輪郭がつながる最小の値まで持っていく。最後にぼかしのカーネルサイズで残ったノイズを落とす。3つを同時に動かすと、どれが効いたのか分からなくなる。照明を固定できるラインなら初期値は定数で書いてよく、明るさが振れる環境では中央値からの自動算出が安定します。

### カラー画像のまま渡すとチャンネル別の最大勾配で拾う挙動との差

Cannyはグレースケール変換が必須だと思われがちですが、実装は多チャンネル入力を受け付けます。canny.cppでは、チャンネルごとに勾配を計算し、画素ごとに勾配の大きさが最大のチャンネルを選んで採用しています。

差が表面化するのは、明度がほぼ同じで色相だけが違う境目です。グレースケール変換は3チャンネルを重み付き平均で潰すため、赤と緑の境目のように明度が近い組み合わせは境目ごと消えます。カラーのまま渡せば赤チャンネルの勾配が立ち上がるので境目が残る。色つきの部品やラベルの検査では、グレースケール化を挟まないほうが拾えます。

## 前処理と輪郭抽出のつなぎ方｜平滑化の位置とfindContoursへの受け渡し

エッジ検出は単体で完結しません。前後に何を置くかで、最終的な数値の安定度が変わります。

### 平滑化をCannyの前に置く理由と二値化を先に挟むと崩れる条件

前段に置くのはガウシアンぼかしです。カーネルは3×3か5×5から始め、ノイズが残るなら7×7まで。大きくするほど細い輪郭も消えるので、拾いたい最小の特徴の幅を基準に上限を決めます。

二値化をCannyの前に置くのは避けてください。二値化した画像は明るさが0か255しか持たず、境目の勾配が理論上の最大値に張り付きます。しきい値をどう動かしても輪郭は同じ形でしか出ない。二値化とエッジ検出は前後関係ではなく、面で切るか線で切るかの並列の選択肢です。

後段では、Cannyの出力を`cv2.dilate`で1回膨張させてから輪郭を取ると、微小な途切れがつながって輪郭の数が安定します。

### findContoursの入力要件と輪郭の取り出し方を決める2つの指定

`findContours`の入力は8ビット単チャンネルで、非ゼロ画素が1として扱われます。Cannyの出力はそのまま条件を満たす。OpenCV 3.2以降は入力画像を書き換えない仕様に変わっており、コピーを渡す必要はありません。

指定するのは取得モードと近似方法の2つです。モードはRETR\_EXTERNAL（最外周のみ）、RETR\_LIST（階層なしで全部）、RETR\_CCOMP（2階層）、RETR\_TREE（完全な階層）の4種類。部品を1つずつ数えるならRETR\_EXTERNAL、穴の有無まで判定するならRETR\_TREEが基準です。近似方法はCHAIN\_APPROX\_SIMPLEが実務の既定で、直線部分を端点だけに圧縮するため長方形の輪郭は4点になります。

```
import cv2

edges = cv2.Canny(blur, 80, 160)
dilated = cv2.dilate(edges, None, iterations=1)

contours, hierarchy = cv2.findContours(
    dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("contours:", len(contours))
```

版による差も押さえます。ヘッダの注記によれば、OpenCV 4.14以降はモードがRETR\_LISTで階層を要求しない場合に限りTRUCOという並列アルゴリズムへ自動で切り替わり、それ以外は従来のSuzuki85が使われる。輪郭数が多い画像で速度を稼ぐなら、階層を使わない設計にしてRETR\_LISTを選ぶ意味が出ます。5系では近似方法にCHAIN\_CODEとLINK\_RUNSが加わった点も移行時の確認事項です。

### 輪郭の面積と周長と最小外接矩形から寸法と欠陥を数値にする手順

輪郭が取れたら判定に使う数値を作ります。実務で使う関数は4つです。

- `contourArea`：面積。欠品や異物の大きさ判定に使う
- `arcLength`：周長。周長の二乗を面積で割ると形状の複雑さが出る
- `minAreaRect`：最小外接矩形。傾いた部品の幅と長さを測る
- `approxPolyDP`：頂点数の削減。角の数で形状を分類する

落とし穴が`contourArea`の値です。ヘッダに明記されているとおり、この関数はグリーンの公式で面積を算出するため、`drawContours`や`fillPoly`で塗った非ゼロ画素の数とは違う値になりえます。自己交差する輪郭ではまず正しい値が返りません。画素数で判定したいなら`countNonZero`で数えるほうが素直です。

`minAreaRect`が返す角度は必ず-90度以上0度未満に収まります。回転が90度を超えると次の辺で測り直すためで、-5度と-85度が同じ姿勢を指す場合がある。角度をそのまま判定条件に使うと、境界をまたいだ瞬間に不良と判定する事故が起きる。幅と高さの大小関係とセットで正規化してください。

## エッジ検出で押し切れない条件｜学習ベースへ切り替える判断の線引き

エッジ検出はどこまで粘るべきか、どこで別の手法へ切り替えるか。条件を付けて言い切ります。

### 形状が既知で照明を固定できる工程はエッジ検出だけで足りる理由

次の3条件がそろう工程なら、学習ベースを持ち込まずエッジ検出で完結させるべきです。対象の形状が図面で決まっていること。照明と撮影距離を固定できること。判定基準が数値で書けること。

この条件下でエッジ検出が有利な点は3つあります。判定根拠が数式で追えるため不良の原因を説明できること。学習データを集める工数がゼロで済むこと。1枚あたりの処理がミリ秒単位で終わること。寸法計測、欠品検査、位置決めはこの領域です。過検出が出ても、しきい値と前処理の2つを追えば原因にたどり着けます。

### テクスチャの欠陥と個体差の大きい対象でエッジ検出を見送る境界

逆に、次の場面ではエッジ検出で粘るべきではありません。

1つ目は、欠陥が境目として現れない場合です。塗装のムラ、布の織りムラ、金属表面のくすみは、明るさが緩やかに変わるだけで勾配の山を作りません。しきい値を下げれば拾えますが、同時にノイズも全部拾って判定が成立しなくなります。

2つ目は、良品の個体差が大きい場合。手作業の溶接ビードのように良品同士でも輪郭の形が毎回違う対象では、形状の数値にしきい値を引けません。3つ目は背景が雑然として対象だけを分離できない場合です。他の部品の輪郭が同じ強さで出れば、どれが対象かをエッジの情報だけでは決められません。

この3つに当たったら、しきい値の調整を続けるのは時間の無駄です。良品学習やセグメンテーションへ切り替える判断を先に下してください。手法の比較と導入判断の材料は[AI外観検査とは？仕組み・ルールベースとの違いと導入判断を解説](https://www.issoh.co.jp/column/details/16007/)にまとめています。

### 自社で実装する範囲と外部へ委託したほうが早い範囲の切り分け方

実装範囲の線引きも条件で決まります。単一の撮影条件で対象が1種類、判定が寸法や個数で書けるなら自社実装が成立する。工数はパラメータの追い込みと現場での再調整が大半です。古典的画像処理と学習ベースのどちらへ倒すかを工程横断で決める段階なら、[画像処理AIとは？古典的手法とディープラーニングの使い分けを実装判断で解説](https://www.issoh.co.jp/tech/details/16856/)に、対象の定義しやすさ・タクトタイム・品種数の3軸で選ぶ手順をまとめています。

外部へ回したほうが早いのは、品種が複数あって条件分岐が増える場合、既存の生産管理システムへ判定結果を戻す場合、学習ベースとルールベースを併用する場合の3つです。とくに3つ目はデータ収集の設計と精度評価の置き方で成否が決まるため、経験のある外部と組むほうが手戻りが少なくなります。[画像認識AI開発](https://www.issoh.co.jp/service/ai/image%5Frecog/)では、ルールベースの画像処理と学習ベースの検査を対象ごとに切り分けたうえで、PoCから運用までを設計しています。

## よくある質問

実装で問い合わせの多い5点に答えます。

### エッジ検出とは何をする処理ですか？

画像の中で明るさが急に変わる位置を見つけ、そこを境目として出力する処理です。隣り合う画素の差、つまり微分を取って変化率が大きい場所を拾います。出力は境目だけが白く残った単チャンネルの画像で、後段の輪郭抽出や寸法計測の入力になります。

### SobelとCannyはどちらを使えばよいですか？

輪郭を線として取り出すのが目的ならCannyです。内部でSobelを呼んだうえで非最大抑制で1画素幅に細らせ、二段しきい値で弱いエッジを取捨します。Sobelを単体で使うのは、勾配の強さや方向を数値のまま後段へ渡す場合に限られる。迷ったらCannyから始めてください。

### Cannyの2つのしきい値はどう決めればよいですか？

グレースケール画像の画素値の中央値mに対して、低いほうを0.67m、高いほうを1.33m付近から始める方法が広く使われています。そこから高いほうを動かして輪郭の起点が残る値を探し、次に低いほうを下げて途切れをつなぐ。この順で1つずつ動かしてください。

### エッジ検出の前にグレースケール変換は必要ですか？

必須ではありません。OpenCVの`Canny`は多チャンネル入力を受け付け、画素ごとに勾配の大きさが最大のチャンネルを採用します。明度が近く色相だけが違う境目はグレースケール変換すると消えるため、カラーのまま渡したほうが拾えます。速度を優先するならグレースケールです。

### エッジ検出と輪郭抽出は何が違いますか？

エッジ検出は境目の画素に印を付けるところまでで、出力は画像です。輪郭抽出（`findContours`）はその画像から連結した境目を1本ずつまとめ、点の並びとして取り出します。面積や周長を計算できるのは点の並びになってからで、寸法を出すには両方を通します。

## 関連記事

- [二値化とは？しきい値の決め方と大津・適応的の使い分けをOpenCVの実装で解説](https://www.issoh.co.jp/tech/details/16847/)：面で切るしきい値処理の手法選定
- [OpenCVをPythonで使う画像処理入門｜インストールから顔検出まで実コードで解説](https://www.issoh.co.jp/tech/details/1344/)：導入手順と基本操作から確認したい場合
- [AI外観検査とは？仕組み・ルールベースとの違いと導入判断を解説](https://www.issoh.co.jp/column/details/16007/)：検査手法の選択と導入判断

---

出典: [エッジ検出とは？Canny法と微分フィルタの使い分けをOpenCVの実装で解説](<https://www.issoh.co.jp/tech/details/16854/>)（株式会社一創）
