gazou-compressor.jp

サムネ自動トリミング:顔検出・焦点領域・SmartCropで“映える”自動生成(Windows/Next.js)

OGP やカードのサムネは自動生成が肝です。手作業だと漏れ・バラつきが出やすく、公開速度も落ちます。 まずは SmartCrop + Sharp で“映える切り抜き”を自動化し、顔検出手動の焦点(focal point)で上書きできる設計にすると安定します。

先に結論(運用ルール)
  • テンプレは1.91:1(1200×630)カード用の2系統を固定。
  • 自動はSmartCrop、あれば顔検出>手動focal>SmartCropの優先順位。
  • 表示は予約サイズAVIF/WebP(関連:CLSゼロ)。

要点(TL;DR)

1. なぜ“自動 + 上書き可”が最短か

サムネの印象は CTR と直結します。毎回手作業だと運用が重く、抜け漏れも発生。SmartCrop は顔・コントラスト・エッジなどをスコア化して ベストな領域を返してくれるため、まずは自動で8割を取り、残りを顔検出や手動focalで補正するのが現実解です。

2. 最短フロー(実装)

  1. Sharp + SmartCrop を導入(Windows対応)。
  2. OGP/カードの固定サイズを決め、バッチ生成スクリプトを用意。
  3. 必要に応じて顔検出の矩形手動focalで上書き。
  4. AVIF/WebPで軽量化、予約サイズでCLSゼロ(関連:OGP安全域)。

3. 実装レシピ(コピペOK)

3.1 依存の導入

// Shell# 1) 依存の導入(Windows PowerShell)
npm i -D sharp smartcrop-sharp
# (任意)顔検出を入れる場合:軽量に試すなら @vladmandic/face-api + canvas
# npm i -D @vladmandic/face-api canvas

3.2 SmartCropで自動トリム(OGP)

// TypeScript// 2) SmartCrop + Sharp:1200x630(OGP用)の自動トリム例
// scripts/crop-ogp.ts
import fs from "node:fs/promises";
import sharp from "sharp";
import smartcrop from "smartcrop-sharp";

const INPUT = "assets/input.jpg";
const OUT = "public/ogp/auto-1200x630.jpg";

async function main() {
  const image = sharp(INPUT);
  const { width, height } = await image.metadata();

  if (!width || !height) throw new Error("サイズ取得に失敗");

  // SmartCropで最適領域を探索(1.91:1 = 1200x630)
  const result = await smartcrop.crop(await fs.readFile(INPUT), { width: 1200, height: 630 });
  const topCrop = result.topCrop; // { x, y, width, height }

  await image
    .extract({ left: topCrop.x, top: topCrop.y, width: topCrop.width, height: topCrop.height })
    .resize(1200, 630)               // 目的サイズ
    .jpeg({ quality: 84, mozjpeg: true }) // まずはJPEGで確認(後述でWebP/AVIFも)
    .toFile(OUT);

  console.log("✔ OGP生成:", OUT);
}
main().catch((e) => { console.error(e); process.exit(1); });

3.3 顔検出のフォールバック(概念)

// TypeScript// 3) 顔検出がある場合はそれを優先(概念例:単一顔の境界箱をSmartCrop前に採用)
type Box = { x: number; y: number; width: number; height: number };

function preferFaceBox(face: Box | null, targetW: number, targetH: number, imgW: number, imgH: number): Box | null {
  if (!face) return null;
  const targetRatio = targetW / targetH;

  // 顔の中心を維持しつつ比率1.91:1の矩形を作成(はみ出しは画面内にクランプ)
  const cx = face.x + face.width / 2;
  const cy = face.y + face.height / 2;

  let w = Math.min(imgW, Math.max(face.width * 2.2, targetW));
  let h = Math.round(w / targetRatio);
  if (h > imgH) { h = imgH; w = Math.round(h * targetRatio); }

  let x = Math.round(cx - w / 2);
  let y = Math.round(cy - h / 2);
  x = Math.max(0, Math.min(imgW - w, x));
  y = Math.max(0, Math.min(imgH - h, y));
  return { x, y, width: w, height: h };
}
// SmartCropの前に preferFaceBox() が返す矩形があればそれで extract() する

3.4 バッチ生成(2サイズ)

// Shell# 4) 一括書き出し(PowerShell例:OGPとカードの2サイズ)
# OGP 1200x630 / Card 400x250
node scripts/crop-ogp.ts
# ※ 同様に 400x250 版のスクリプトを作って node scripts/crop-card.ts

3.5 WebP/AVIFで軽量化

// TypeScript// 5) 仕上げの軽量化(SharpでWebP/AVIFにも出力)
await image
  .extract({ left: topCrop.x, top: topCrop.y, width: topCrop.width, height: topCrop.height })
  .resize(1200, 630)
  .toFile("public/ogp/auto-1200x630.webp");  // 既定画質(q=80程度)
await image
  .extract({ left: topCrop.x, top: topCrop.y, width: topCrop.width, height: topCrop.height })
  .resize(1200, 630)
  .avif({ quality: 45, effort: 4 })
  .toFile("public/ogp/auto-1200x630.avif");

3.6 Next.jsでの表示(CLSゼロ)

// HTML + CSS<!-- 7) Next.jsでの表示(比率予約 + picture + 代替テキスト) -->
<figure class="thumb">
  <picture>
    <source srcset="/ogp/auto-1200x630.avif" type="image/avif" />
    <source srcset="/ogp/auto-1200x630.webp" type="image/webp" />
    <img
      src="/ogp/auto-1200x630.jpg"
      width="1200" height="630"
      alt="記事の主題を示すサムネイル"
      loading="eager" decoding="async"
      style="object-fit: cover"
    />
  </picture>
  <figcaption class="sr-only">記事の主題を示すサムネイル</figcaption>
</figure>

<style>
.thumb { aspect-ratio: 1200 / 630; background:#f3f4f6; }
.thumb > picture, .thumb img { width: 100%; height: 100%; display:block; }
</style>
<!-- 予約サイズでCLSゼロ(関連:/articles/cls-zero-images-ads) -->

4. 応用と使いどころ

5. 公開前チェック

// Checklist# 公開前チェック(サムネ自動生成)
- 目的サイズ:OGP 1200x630(1.91:1)、カード 400x250 等を決めてから実装
- SmartCropで自動化し、顔検出や手動focalを“上書き”できる設計に
- alt/キャプションは“画像の内容”を簡潔に。装飾は alt=""
- 予約サイズ(aspect-ratio or width/height)でCLSゼロ
- WebP/AVIFで軽量化。色はまず sRGB 正規化(/articles/srgb-color-management)
- /compressor と /compare で劣化と容量を確認、OGPは安全域もチェック(/articles/ogp-thumbnail-safe-area)

6. まとめ

サムネは自動で量産し、必要に応じて顔検出/手動focalで仕上げるのが最短です。 予約サイズと軽量コーデックで CWV を守りつつ、OGP安全域背景のレスポンシブ設計も合わせれば、 どの端末でも“映える”サムネを安定運用できます。

FAQ(よくある質問)

画像形式の基本方針は?(写真/スクショ/透過)
写真は AVIF / WebP(画質80–85%目安)、UIやスクショはPNG / WebP Lossless、単色ロゴはSVGが基本です。 実装の詳細は srcset/sizes設計ガイド スクショ最適化 を参照してください。
圧縮しても画質を落とさないコツは?
実表示幅に合わせたリサイズ → 過大ダウンロードを防ぎ、srcset/sizes を 実描画幅に一致させます。画質は写真で 80–85% を起点に、ノイズやエッジを目視確認。 仕上げは /compare で Before/After を見比べるのがおすすめです。
CLSを悪化させない画像の置き方は?
すべての画像に width/height(または親に aspect-ratio)を与え、広告・埋め込みは 予約サイズを先に確保します。詳しくは CLSゼロ設計ガイド を参照。

公開:2025-08-31

gazou-compressor.jp 編集部

画像圧縮・変換・背景除去などの実践テクニックと、Webで“速く・軽く・崩さない”ためのノウハウを発信しています。

関連記事