C言語で始める画像処理の基礎!手を動かしながら学ぶ15の実例

C言語で画像処理を学ぶ初心者がコードを書きながら理解を深めるためのガイドC言語
この記事は約40分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C言語で始める画像処理の基礎について、手を動かしながら学ぶための具体的な15の実例を紹介します。

プログラミング初心者でも安心して学べるよう、基本的な事項から応用までを分かりやすく解説していきます。

●C言語と画像処理の関係

○C言語の特性

C言語は高速で効率的なコードを記述することが可能なプログラミング言語です。

その特性から、画像処理のような大量のデータを処理する必要がある領域でよく使用されています。

また、C言語は他の多くのプログラミング言語の基盤となっており、C言語を理解することで他の言語を学ぶ上でも有利になります。

○画像処理とは

画像処理は、デジタル画像をコンピュータで解析・操作する技術のことを指します。

色調整、フィルターの適用、エフェクトの追加、顔認識など、様々な処理が可能です。

これらの処理は、ゲーム開発やWeb開発、AIの領域などで広く利用されています。

●画像処理に必要なC言語の基本知識

○変数とデータ型

C言語における変数は、情報を保持するための入れ物です。

データ型は、その変数が何の種類の情報を保持するのかを定義します。

画像処理においては、ピクセルの色情報などを格納するために、整数型や浮動小数点型などのデータ型がよく使用されます。

○制御構文

制御構文は、プログラムの流れを制御します。

条件分岐や繰り返し処理などがあります。

画像処理では、画像の各ピクセルに対して同じ処理を行うためのループ処理などによく用いられます。

○関数

関数は、特定の処理をまとめたもので、プログラムの再利用性や可読性を高めます。

画像処理においても、同じ処理を何度も行う場合に関数を利用することが一般的です。

●実践!C言語で画像を操作する

ここでは、C言語を用いて具体的に画像処理を行う方法について見ていきましょう。

○サンプルコード1:画像ファイルを読み込む

まずは、画像ファイルを読み込む基本的なコードを見ていきましょう。

ここでは、ライブラリとしてOpenCVを使用します。

OpenCVは、C言語やPythonなど様々な言語で利用できる画像処理ライブラリで、広く利用されています。

#include <opencv2/opencv.hpp>

int main() {
    // 画像を読み込む
    cv::Mat img = cv::imread("sample.jpg");

    if(img.empty()) {
        printf("画像ファイルを読み込めませんでした\n");
        return -1;
    }

    // 画像を表示する
    cv::imshow("sample image", img);

    cv::waitKey(0);

    return 0;
}

このコードでは、まず「sample.jpg」の画像を読み込んでいます。

そして、読み込みに成功したらその画像を表示します。読み込みに失敗した場合は、エラーメッセージを表示します。

○サンプルコード2:画像の色調を変える

画像の色調を変えるとは、具体的には画像のRGB値を操作して色の明るさや色相を調整することを指します。

このセクションでは、C言語を使用して画像の色調を変更するシンプルなプログラムを紹介します。

この例では、RGB値を直接操作して、画像に新しい色調を付けています。

#include <stdio.h>
#include <stdlib.h>

// RGB値を保持する構造体
typedef struct {
    unsigned char r, g, b;
} RGB;

// 画像データを保持する構造体
typedef struct {
    int width;
    int height;
    RGB *data;
} Image;

// 画像の色調を変える関数
void change_hue(Image *img, int hue) {
    for (int i = 0; i < img->width * img->height; i++) {
        // RGB値を調整
        img->data[i].r += hue;
        img->data[i].g += hue;
        img->data[i].b += hue;
    }
}

int main() {
    // ここでは詳細を省略しますが、
    // 実際の使用では画像データを読み込んでから上記の関数を呼び出します。
    // 例:change_hue(&img, 50);
    return 0;
}

上記のコードでは、まずRGB値を保持するための構造体と、画像データ全体を保持するための構造体を定義しています。

その後に色調を変える関数を定義し、この関数では画像のすべてのピクセルのRGB値に一定の値を加えることで、画像全体の色調を変更しています。

このコードを実行すると、元の画像の各ピクセルのRGB値が指定した値だけ増加し、結果的に画像全体の色調が変化します。

ただし、色調を大きく変更しすぎると、色が飽和してしまう可能性があるので注意が必要です。

○サンプルコード3:画像のコントラストを調整する

C言語を使った画像処理の実例として、次は画像のコントラストを調整する例を紹介します。

コントラストとは、画像内の明るい部分と暗い部分との差のことを言います。

この差が大きいほど、画像は鮮明に見えます。それでは、どのようにコントラストを調整するのでしょうか。

それは、画像の各ピクセルの色の強度を調節することで達成します。

#include <stdio.h>
#include <stdlib.h>
#include "bmp.h"

void adjust_contrast(BMP_Image* image, double contrast) {
    unsigned char* data = image->data;

    for (int i = 0; i < image->width * image->height * 3; i++) {
        double temp = data[i] / 255.0;
        temp -= 0.5;
        temp *= contrast;
        temp += 0.5;
        temp *= 255;

        if(temp > 255) temp = 255;
        if(temp < 0) temp = 0;

        data[i] = (unsigned char)temp;
    }
}

int main() {
    BMP_Image* image = bmp_image_read("input.bmp");

    if (image == NULL) {
        printf("画像の読み込みに失敗しました。");
        return -1;
    }

    adjust_contrast(image, 2.0);
    bmp_image_write(image, "output.bmp");

    bmp_image_destroy(image);

    return 0;
}

このコードでは、まずbmp.hというヘッダーファイルをインクルードしています。

このヘッダーファイルは、BMP形式の画像を読み書きするための関数を定義しています。

次に、adjust_contrastという関数を定義します。

この関数は、BMP_Image構造体のポインタと、コントラストの調整量を引数に取ります。

その中で各ピクセルの色強度を調節して、コントラストを調整しています。

main関数では、bmp_image_read関数を使って画像を読み込んでいます。

そして、adjust_contrast関数を使って、コントラストを調整します。この例では、コントラストを2.0倍に調整しています。

最後に、bmp_image_write関数を使って、コントラストを調整した画像を出力します。

このコードを実行すると、元の画像のコントラストが2.0倍に調整された画像が出力されます。

このコードは、コントラストを調整するための基本的な方法を表しています。

このコードを理解すれば、自由にコントラストを調整することが可能になります。

○サンプルコード4:画像にフィルターを適用する

C言語を使って画像にフィルターを適用する例を紹介します。

このコードではC言語の配列とループを活用しています。

下記のサンプルコードはグレースケールフィルターを適用する例です。

#include <stdio.h>
#include "bitmap.h"

void grayscale_filter(Bitmap *bmp) {
    for (int i = 0; i < bmp->height; i++) {
        for (int j = 0; j < bmp->width; j++) {
            Pixel *p = get_pixel(bmp, i, j);
            unsigned char gray = 0.3 * p->r + 0.59 * p->g + 0.11 * p->b;
            p->r = gray;
            p->g = gray;
            p->b = gray;
        }
    }
}

int main() {
    Bitmap bmp;
    if (load_bmp("input.bmp", &bmp) == 0) {
        grayscale_filter(&bmp);
        save_bmp("output.bmp", &bmp);
        free_bmp(&bmp);
    } else {
        printf("画像を読み込むことができませんでした。\n");
    }

    return 0;
}

このコードは、RGB値を用いて各ピクセルのグレースケール値を計算し、元のRGB値をグレースケール値に置き換えることで画像全体をグレースケールに変換しています。

ここで、0.3 * p->r + 0.59 * p->g + 0.11 * p->bという計算式は、人間の目が緑色に対して最も敏感であり、青色に対して最も敏感でないという事実に基づいています。

このコードを実行すると、”input.bmp”の画像がグレースケールに変換され、”output.bmp”として保存されます。

結果の画像は全ての色がグレースケールに変換され、モノクロのような外観になります。

ただし、注意しなければならないのは、このコードがbitmap画像専用であるということです。他の形式の画像に対しては適用できません。

もし他の形式の画像に対してこのフィルターを適用したい場合は、まず画像をbitmap形式に変換する必要があります。

また、このコードでは独自のBitmapおよびPixel構造体を使用しています。

これらの構造体は、bitmap画像の各ピクセルの色情報を格納するためのもので、bitmap.hというヘッダーファイルで定義されています。

このヘッダーファイルは自分で作成するか、適切なライブラリを探して使用する必要があります。

●応用!C言語で高度な画像処理を行う

さて、基本的な画像処理を習得したあとは、もう一歩踏み込んでC言語を使った高度な画像処理に挑戦してみましょう。

プログラミングの世界は深く、終わりなき挑戦が続きますが、それがまた楽しいのです。

では、応用的な画像処理の手法を学んでいきましょう。

○サンプルコード5:画像にエフェクトを加える

このコードでは、画像にエフェクトを加える方法を紹介します。

C言語を使って特定のエフェクトを画像に適用し、その結果を出力するコードを実行してみましょう。

下記のサンプルコードは、ネガポジ反転というエフェクトを作成しています。

ネガポジ反転は、画像のピクセルの色情報を反転させることで、通常の色とは反対の色を生成するエフェクトです。

ネガポジ反転は、コントラストの強い画像を生成するのによく用いられます。

#include <stdio.h>
#include "CImg.h"
using namespace cimg_library;

int main() {
    // 画像ファイルを読み込む
    CImg<unsigned char> img("input.jpg");

    // 画像の各ピクセルを反転させる
    cimg_forXY(img, x, y) {
        img(x, y, 0) = 255 - img(x, y, 0);  // 赤
        img(x, y, 1) = 255 - img(x, y, 1);  // 緑
        img(x, y, 2) = 255 - img(x, y, 2);  // 青
    }

    // ネガポジ反転した画像を保存する
    img.save("output.jpg");

    return 0;
}

このコードの最初の部分で、画像ファイルを読み込んでいます。

次に、「cimg_forXY」を用いたループにより、画像の各ピクセルを走査しています。

各ピクセルで、RGB(赤、緑、青)の色情報を255から引くことにより、色情報を反転させています。

最後に、変更を加えた画像を保存しています。

コードを実行すると、元の画像がネガポジ反転した結果の画像が出力されます。

元の画像が明るければ結果の画像は暗く、元の画像が暗ければ結果の画像は明るくなるでしょう。

○サンプルコード6:画像を合成する

次に、複数の画像を合成する方法を見てみましょう。

この例では、2つの画像を読み込み、それらを一緒に重ねて一つの画像に合成します。

このテクニックは、画像編集ソフトウェアでよく用いられます。

下記のコードでは、2つの画像を読み込み、その上に新しい画像を作成し、その結果を出力する一連の作業を行っています。

#include <stdio.h>
#include "CImg.h"
using namespace cimg_library;

int main() {
    // 2つの画像ファイルを読み込む
    CImg<unsigned char> img1("input1.jpg");
    CImg<unsigned char> img2("input2.jpg");

    // 画像のサイズを確認
    int width = img1.width();
    int height = img1.height();

    // 合成する画像を作成
    CImg<unsigned char> combined(width, height, 1, 3, 0);

    // 2つの画像を合成する
    cimg_forXY(img1, x, y) {
        combined(x, y, 0) = (img1(x, y, 0) + img2(x, y, 0)) / 2;  // 赤
        combined(x, y, 1) = (img1(x, y, 1) + img2(x, y, 1)) / 2;  // 緑
        combined(x, y, 2) = (img1(x, y, 2) + img2(x, y, 2)) / 2;  // 青
    }

    // 合成した画像を保存する
    combined.save("combined.jpg");

    return 0;
}

このコードは、2つの画像(input1.jpgとinput2.jpg)を読み込み、それらのサイズが一致することを確認します。

その後、同じサイズの新しい画像(combined)を作成します。

次に、それぞれの画像の色情報を平均化して新しい画像に適用します。

これにより、2つの画像は1つの画像に合成されます。

最後に、新しく作成した合成画像を保存します。

出力される画像は、2つの入力画像が重ね合わせられたものとなります。

○サンプルコード7:画像を回転させる

C言語で画像処理を行う際に避けて通れないのが、画像の回転処理です。

この操作は、画像を任意の角度で回転させ、新たな視点から観察することを可能にします。

CImgライブラリには、「rotate」という関数が含まれています。

この関数を使用すると、指定した角度で画像を回転させることが可能です。引数は回転角度で、単位は度です。

また、2つ目の引数には、回転の中心点を指定します。

下記のコードは、画像を45度回転させる例です。

この例では、画像の中心を回転の基点としています。

#include <stdio.h>
#include "CImg.h"
using namespace cimg_library;

int main() {
    // 画像ファイルを読み込む
    CImg<unsigned char> img("input.jpg");

    // 画像を45度回転させる
    img.rotate(45, img.width()/2.0, img.height()/2.0);

    // 回転させた画像を保存する
    img.save("rotated.jpg");

    return 0;
}

このコードでは、最初に画像を読み込んでいます。

次に、rotate関数を使って画像を45度回転させています。そして、回転させた画像を保存しています。

回転させる角度を変えることで、異なる視点から画像を観察することができます。

また、回転の中心点を移動させることで、画像の回転の様子をさらに細かく調整することも可能です。

なお、画像を回転させると、画像の端が切れる場合があります。

これは、画像の角度を変えると、画像の範囲が元の画像の範囲からはみ出すからです。

この問題を解決するには、画像を回転させる前に画像の範囲を広げ、回転後も全ての情報が含まれるようにすると良いでしょう。

○サンプルコード8:画像を拡大・縮小する

次に取り組むのは、C言語で画像を拡大・縮小するというタスクです。

一般的な画像処理としてよく行われる操作の一つであり、その方法を理解しておくことで、画像解析やAIの分野でも有用なスキルとなります。

まずは、画像を拡大・縮小するためのサンプルコードをご覧ください。

#include <stdio.h>
#include "imageprocessing.h"

int main() {
    // 画像データの読み込み
    Image *img = readImage("input.jpg");

    // 拡大・縮小後の画像データを格納するための変数を作成
    Image *resizedImg = createImage(img->width * 2, img->height * 2, img->depth);

    // 画像の拡大処理
    resizeImage(img, resizedImg);

    // 拡大した画像データをファイルに書き込む
    writeImage(resizedImg, "resized.jpg");

    // 使用したメモリを解放
    freeImage(img);
    freeImage(resizedImg);

    return 0;
}

このコードでは、まずinput.jpgという名前の画像ファイルを読み込みます。

その後、新しい画像データを格納するための空のイメージを作成しますが、ここでは元の画像の2倍のサイズに設定しています。

resizeImage関数は、元の画像と新しい画像の2つのイメージを引数として受け取り、元の画像を新しいサイズに変換します。

なお、画像の拡大処理では、ピクセル間の色情報を適切に補間する必要があります。

これは、特定の補間アルゴリズム(最近傍補間、バイリニア補間、バイキュービック補間など)を用いて行われます。

本サンプルコードでは、これらの詳細は省略していますが、高度な画像処理を行う際には重要な要素となります。

最後に、新しく作成した拡大画像をresized.jpgという名前で保存します。

最終的に、使用したイメージデータを解放してプログラムを終了します。

このコードを実行すると、元の画像が2倍の大きさに拡大された画像が生成されます。

元の画像が十分に大きい場合、2倍に拡大した画像でも詳細がはっきりと見えるでしょう。

ただし、元の画像が小さい場合、または大幅に拡大した場合、画像がぼやけたり、ブロック状に見えるピクセル化(aliasing)が発生する可能性があります。

画像の縮小についても同様のプロセスで行いますが、createImageで作成する新しい画像のサイズを元の画像よりも小さく設定します。

縮小処理では、元の画像の色情報が新しい小さな画像に適切に評価され、平均化される点が拡大処理とは異なります。

○サンプルコード9:顔認識を行う

さて、C言語による画像処理の次のステップに進みましょう。

次に実施するのは「顔認識」です。このコードではOpenCVを使って顔認識を行うコードを紹介しています。

この例では画像から顔を検出して、その範囲に枠を描きます。

#include <stdio.h>
#include <opencv2/opencv.hpp>

int main(void) {
    CvCapture *capture = NULL;
    IplImage *frame = NULL;
    CvHaarClassifierCascade *cvHCC = NULL;
    CvMemStorage *cvMStr = NULL;
    CvSeq *face_rect_seq = NULL;
    CvRect *face_rect = NULL;
    double scale = 1.1;
    int min_neighbors = 3;
    int min_size = 50;
    int i;

    capture = cvCreateCameraCapture(0);
    cvHCC = (CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_default.xml", NULL, NULL, NULL);
    cvMStr = cvCreateMemStorage(0);

    while (1) {
        frame = cvQueryFrame(capture);
        if (!frame) break;
        face_rect_seq = cvHaarDetectObjects(frame, cvHCC, cvMStr, scale, min_neighbors, CV_HAAR_DO_CANNY_PRUNING, cvSize(min_size, min_size), cvSize(0, 0));
        for (i = 0; i < face_rect_seq->total; i++) {
            face_rect = (CvRect*)cvGetSeqElem(face_rect_seq, i);
            cvRectangle(frame, cvPoint(face_rect->x, face_rect->y), cvPoint(face_rect->x + face_rect->width, face_rect->y + face_rect->height), CV_RGB(255, 0 ,0), 3);
        }
        cvShowImage("window", frame);
        if (cvWaitKey(33) >= 0) break;
    }

    cvReleaseMemStorage(&cvMStr);
    cvReleaseHaarClassifierCascade(&cvHCC);
    cvReleaseCapture(&capture);

    return 0;
}

上記のコードは、Webカメラからの映像をリアルタイムで解析し、顔を検出するものです。

OpenCVの「Haar特徴ベースのカスケード分類器」を利用しています。

まず、CvCapture構造体のポインタによりカメラキャプチャを生成します。

次に、カスケード分類器とメモリストレージを生成します。この時、分類器には学習済みの顔検出用データを読み込ませます。

無限ループの中で、各フレームに対しcvHaarDetectObjects関数を適用し、顔を検出します。この関数は、フレーム、カスケード分類器、メモリストレージ、スケール係数、最小近傍数、フラグ、最小オブジェクトサイズを引数にとります。

検出した顔部分には赤い枠が描かれ、リアルタイムで表示されます。

なお、上記のコードはOpenCVがインストールされていること、そして顔認識用のXMLファイル(この例では”haarcascade_frontalface_default.xml”)が存在することが前提となっています。

OpenCVのインストール方法や、XMLファイルの配置方法は、OpenCVの公式ドキュメントや、多くのオンラインリソースを参照してください。

このサンプルコードを実行すると、リアルタイムでWebカメラからの映像が表示され、画像中の顔が赤い枠で囲まれる結果が得られます。

その結果、C言語とOpenCVを使った簡易的な顔認識システムが実現されます。

ここまでが顔認識の基本的な流れです。

この基本的な流れを理解することで、更に様々な応用例、例えば笑顔検出、目の検出などが可能になります。

また、顔認識を行う前に画像処理を適用することで、認識精度を向上させるなどの工夫も可能です。

○サンプルコード10:画像にテキストを追加する

次に行う高度な画像処理は、画像にテキストを追加する操作です。

画像編集では、テキストを挿入して画像に情報を追加することは非常に一般的です。

これは、タイトル、説明、コメンタリーなどを追加するために使用されます。

この作業はC言語でも可能で、独自の画像編集ソフトウェアを開発する際に役立ちます。

ここでは、C言語を使って画像にテキストを追加する手法を解説します。

まず、次のサンプルコードを見てみましょう。

このコードは、C言語のGDライブラリを利用しています。

GDライブラリは、動的に画像を生成したり操作したりするためのオープンソースのコードライブラリで、C言語で書かれています。

#include <gd.h>
#include <stdio.h>
#include <string.h>

void main() {
    gdImagePtr img;
    FILE *out, *in;
    int black, white;
    char *text = "Hello, world!";
    char *error;
    int brect[8];
    double sz = 0.0;
    char *fnt = "arial.ttf";
    in = fopen("example.jpg", "rb");
    img = gdImageCreateFromJpeg(in);
    fclose(in);
    white = gdImageColorAllocate(img, 255, 255, 255);
    black = gdImageColorAllocate(img, 0, 0, 0);
    error = gdImageStringFT(NULL, &brect[0], 0, fnt, sz, 0.0, 0, 0, text);
    gdImageStringFT(img, &brect[0], white, fnt, sz, 0.0, 50, 50, text);
    out = fopen("example_text.jpg", "wb");
    gdImageJpeg(img, out, -1);
    fclose(out);
    gdImageDestroy(img);
}

このコードでは、GDライブラリの関数を用いて、JPEG形式の画像ファイルを開き、読み込んだ画像にテキストを追加し、その結果を新しいJPEGファイルとして出力しています。

文字列は"Hello, world!"として指定され、そのテキストは画像の(50, 50)の位置に描画されます。

また、文字色は白(RGB値:255, 255, 255)、背景色は黒(RGB値:0, 0, 0)と定義されています。

このコードを実行すると、元の画像ファイル(”example.jpg”)の左上から右下へ斜めに”Hello, world!”というテキストが白色で追加された新しい画像ファイル(”example_text.jpg”)が生成されます。

画像の保存形式は、出力関数(ここではgdImageJpeg)により、JPEG形式となります。

しかし、このコードを実行する前に、開発環境にGDライブラリがインストールされていることを確認する必要があります。

また、使用するフォントファイル(ここでは”arial.ttf”)が存在することも確認してください。

次に、このコードをさらに応用して、テキストの位置やサイズ、色などを動的に変更できるようにカスタマイズしてみましょう。

例えば、次のように修正できます。

#include <gd.h>
#include <stdio.h>
#include <string.h>

void main() {
    gdImagePtr img;
    FILE *out, *in;
    int color;
    char *text = "Hello, world!";
    char *error;
    int brect[8];
    double sz = 10.0; // Font size
    int x = 100; // X position
    int y = 200; // Y position
    int red = 255; // Red
    int green = 0; // Green
    int blue = 0; // Blue
    char *fnt = "arial.ttf";
    in = fopen("example.jpg", "rb");
    img = gdImageCreateFromJpeg(in);
    fclose(in);
    color = gdImageColorAllocate(img, red, green, blue);
    error = gdImageStringFT(NULL, &brect[0], 0, fnt, sz, 0.0, 0, 0, text);
    gdImageStringFT(img, &brect[0], color, fnt, sz, 0.0, x, y, text);
    out = fopen("example_text.jpg", "wb");gdImageJpeg(img, out, -1);
    fclose(out);
    gdImageDestroy(img);
}

このコードでは、テキストのサイズ、位置、色が変数で設定できるようになりました。

これにより、プログラムの実行中にこれらの値を動的に変更することが可能になり、より高度な画像編集が可能になります。

○サンプルコード11:画像を切り抜く

画像処理の一つとして、特定の部分を切り抜くという技術もあります。

これは、全体の中から特定のエリアだけを取り出す操作で、一部分だけに注目したい場合や、大きな画像から小さな画像を作成したいときなどに便利です。

例えば、大きな風景写真の中から建物や人物の部分だけを取り出したいときや、あるエリアに特化した分析を行いたい場合などに役立ちます。

今回は、C言語を使って画像を切り抜く操作を行うコードを紹介します。

この例では、元となる画像から特定の範囲を切り抜いて新たな画像を生成しています。

このサンプルでは、C言語のOpenCVライブラリを使用しています。

OpenCVはコンピュータビジョンと呼ばれる技術を扱うためのライブラリで、画像処理や映像処理などを行うための多くの機能を提供しています。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char** argv){
    cv::Mat src = cv::imread("example.jpg");
    if(src.empty()){
        return -1;
    }
    cv::Rect roi(50, 50, 100, 100); // ROI (Region of Interest)を設定
    cv::Mat cropped = src(roi); // 元画像からROIを切り抜く
    cv::imwrite("cropped.jpg", cropped); // 切り抜いた画像を保存
    return 0;
}

このコードでは、まずOpenCVライブラリを読み込み、画像ファイル”example.jpg”を読み込みます。

その後、Rectクラスを使ってROI(Region of Interest、つまり関心領域)を定義します。

この例では、左上の座標を(50, 50)、幅と高さをそれぞれ100ピクセルに設定しています。

そして、このROIを元画像から切り抜くために、元の画像(src)に対してROIを適用します。

これにより、元の画像から特定の範囲だけが取り出され、新たな画像(cropped)が生成されます。

最後に、imwrite関数を使って切り抜いた画像を”cropped.jpg”として保存します。

このコードを実行すると、元の画像から特定の範囲(この例では、左上から50ピクセル右、50ピクセル下の位置から始まる、幅100ピクセル、高さ100ピクセルの範囲)が切り抜かれ、新たな画像ファイル(”cropped.jpg”)として保存されます。

この技術は、大きな画像から小さな画像を作成するだけでなく、特定の部分に焦点を当てて分析を行うためにも使えます。

例えば、画像認識や画像分析を行う際に、全体ではなく特定のエリアに注目したいときに利用できます。

なお、このコードを実行する前に、環境にOpenCVライブラリがインストールされていること、かつ指定した画像ファイルが存在することを確認してください。

これらが満たされていないと、正しく動作しないかエラーが発生する可能性があります。

○サンプルコード12:画像のノイズ除去を行う

画像にノイズが入る原因は様々ですが、例えば、撮影環境の明るさが不足していたり、画像センサーの性能が低い場合などにノイズが発生します。

ノイズが混じった画像をそのまま利用すると、視覚的な情報が失われる可能性があります。

そこで、画像からノイズを除去するためのアルゴリズムを適用します。

ここでは、C言語で書かれたノイズ除去のサンプルコードを見てみましょう。

まず、下記のコードでは「OpenCV」を用いてガウシアンフィルタを適用し、画像のノイズを除去しています。

OpenCVは画像処理に特化したライブラリで、様々なフィルタを適用する機能を持っています。

#include <stdio.h>
#include <opencv2/opencv.hpp>

int main(int argc, char** argv){
    cv::Mat src, dst;

    // 画像をグレースケールで読み込む
    src = cv::imread(argv[1], cv::IMREAD_GRAYSCALE);
    if(src.empty()){
        printf("Could not find the image!\n");
        return -1;
    }

    // ガウシアンフィルタを適用
    cv::GaussianBlur(src, dst, cv::Size(5, 5), 0, 0);

    // 結果を表示
    cv::imshow("Original Image", src);
    cv::imshow("Noise Removed Image", dst);

    cv::waitKey(0);
    return 0;
}

このコードでは、まずopencv2ライブラリをインクルードしています。

次にmain関数内で、元の画像(src)とフィルタ後の画像(dst)を格納するためのMatオブジェクトを定義しています。

画像はグレースケールで読み込んでおり、画像が存在しない場合はエラーメッセージを表示して処理を終了しています。

その後、GaussianBlur関数を用いてガウシアンフィルタを適用し、結果をdstに格納しています。

最後に、元の画像とノイズ除去後の画像を表示しています。

ガウシアンフィルタは、画像の各ピクセルに対してその周辺のピクセルの値をガウス分布に従って重み付けし、その加重平均を新しい値とするものです。

これにより、画像のノイズが除去されます。

このコードを実行すると、元の画像とノイズ除去後の画像がそれぞれ新たなウィンドウで表示されます。

これにより、ノイズ除去の効果を直接確認することができます。

画像のノイズ除去は、高度な画像処理の一つと言えます。

しかし、C言語とOpenCVを用いれば、比較的短いコードで実現することが可能です。

画像からノイズを除去することで、画像の情報をより明確に捉えることができ、より高度な画像解析を行う基盤を築くことができます。

○サンプルコード13:画像のエッジ検出を行う

エッジ検出は、画像内の物体の境界を検出するための一般的な技術です。

この技術は画像認識、セグメンテーション、特徴抽出など、多くの画像処理アプリケーションで使われています。

今回は、C言語でエッジ検出を行うサンプルコードをご紹介します。

まずは、次のコードを見てみましょう。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// グレースケール変換関数
void toGrayscale(unsigned char *img, unsigned char *gray, int width, int height){
    int i, j;
    for(i = 0; i < height; i++){
        for(j = 0; j < width; j++){
            // 元の画像のRGB値をグレースケール値に変換
            gray[i*width+j] = 0.2989 * img[(i*width+j)*3 + 0] + 0.5870 * img[(i*width+j)*3 + 1] + 0.1140 * img[(i*width+j)*3 + 2];
        }
    }
}

// エッジ検出関数
void detectEdges(unsigned char *gray, unsigned char *edge, int width, int height){
    int i, j;
    for(i = 1; i < height - 1; i++){
        for(j = 1; j < width - 1; j++){
            // Sobelフィルタを適用してエッジを検出
            int gx = -gray[(i-1)*width + j-1] - 2*gray[i*width + j-1] - gray[(i+1)*width + j-1] + gray[(i-1)*width + j+1] + 2*gray[i*width + j+1] + gray[(i+1)*width + j+1];
            int gy = -gray[(i-1)*width + j-1] - 2*gray[(i-1)*width + j] - gray[(i-1)*width + j+1] + gray[(i+1)*width + j-1] + 2*gray[(i+1)*width + j] + gray[(i+1)*width + j+1];
            edge[i*width + j] = sqrt(gx * gx + gy * gy);
        }
    }
}

int main(){
    // 画像の幅と高さ
    int width = 512, height = 512;

    // 元の画像データとグレースケール画像、エッジ検出画像のメモリ領域を確保
    unsigned char *img = malloc(width * height * 3);
    unsigned char *gray = malloc(width * height);
    unsigned char *edge = malloc(width * height);

    // 画像データの読み込み、グレースケール変換、エッジ検出を行う

    // 画像データを解放
    free(img);
    free(gray);
    free(edge);

    return 0;
}

このコードでは、エッジ検出を行うためにSobelフィルタを使用しています。

Sobelフィルタは、画像の特定のピクセルに対して隣接するピクセルの値を使ってエッジを検出します。

これにより、画像の色の変化が急激な場所を見つけ出すことができます。

さて、このコードを実行するとどのような結果になるでしょうか。

入力画像がグレースケール画像に変換され、その後、エッジ検出が行われます。

結果の画像では、エッジが白で表示され、エッジでない部分は黒で表示されます。

これにより、物体の輪郭が明確になり、物体の形状を認識しやすくなります。

次に、C言語でのエッジ検出の注意点と対処法をみていきましょう。

エッジ検出は画像の明るさの変化を検出するため、明るさが均一である画像ではうまく機能しない可能性があります。

このような場合、画像のコントラストを調整してからエッジ検出を行うと、より良い結果を得られます。

さらに、エッジ検出の結果は、使用するフィルタによっても異なります。

Sobelフィルタ以外にも、Prewittフィルタ、Laplacianフィルタなど、様々なフィルタがあります。

これらのフィルタはそれぞれ異なる特性を持っているため、用途に応じて適切なフィルタを選択することが重要です。

下記のコードは、Laplacianフィルタを使用したエッジ検出の例です。

void detectEdgesWithLaplacian(unsigned char *gray, unsigned char *edge, int width, int height){
    int i, j;
    for(i = 1; i < height - 1; i++){
        for(j = 1; j < width - 1; j++){
            // Laplacianフィルタを適用してエッジを検出
            int g = -gray[(i-1)*width + j] - gray[i*width + j-1] + 4*gray[i*width + j] - gray[i*width + j+1] - gray[(i+1)*width + j];
            edge[i*width + j] = abs(g);
        }
    }
}

このコードは、前述のSobelフィルタを使用したコードと基本的な構造は同じですが、エッジ検出の方法が異なります。

Laplacianフィルタは、画像の二次微分を計算してエッジを検出します。

これにより、エッジの位置をより正確に検出することができます。

○サンプルコード14:画像のぼかし効果を適用する

画像のぼかし効果は、多くの画像処理において重要な役割を果たします。

特に、ノイズの除去や、画像の詳細をわざとぼかして背景や被写体を区別しやすくするなどの用途で用いられます。

画像のぼかし効果は、画像の各ピクセルをその周囲のピクセルの平均値に置き換えることにより達成できます。

このため、これは一種の平滑化フィルタとも言えます。

C言語を用いて画像にぼかし効果を適用するサンプルコードを紹介します。

このコードでは、3×3の平均化フィルタを画像に適用しています。

void applyBlurEffect(unsigned char *img, int width, int height){
    unsigned char *temp = malloc(width * height * sizeof(unsigned char));
    int i, j, m, n;

    // 一時配列に画像データをコピー
    memcpy(temp, img, width * height * sizeof(unsigned char));

    // 平均化フィルタを適用
    for(i = 1; i < height - 1; i++){
        for(j = 1; j < width - 1; j++){
            int sum = 0;
            for(m = -1; m <= 1; m++){
                for(n = -1; n <= 1; n++){
                    sum += temp[(i+m)*width + (j+n)];
                }
            }
            img[i*width + j] = sum / 9;
        }
    }

    // 一時配列を解放
    free(temp);
}

このコードではまず、元の画像データを一時的に保存するための配列tempを作成しています。

この一時配列に元の画像データをコピーし、その上で3×3の平均化フィルタを適用します。

その結果を元の画像データに上書きしています。

この一時配列を用いるのは、ぼかし効果を適用する途中で元の画像データが変更されてしまうことを防ぐためです。

このコードを実行すると、画像の各ピクセルが周囲のピクセルの平均値に置き換えられるため、画像全体がぼかされます。

この結果、画像内の細かなノイズや詳細が消え、全体的にスムーズな画像が得られます。

このサンプルコードをカスタマイズすることで、ぼかしの程度を調節することも可能です。

例えば、フィルタのサイズを大きくすることで、より広範囲のぼかし効果を適用することができます。

また、フィルタ内の各値を変えることで、重み付け平均化フィルタを作成することも可能です。

これにより、中心のピクセルに対して周囲のピクセルよりも大きな影響力を持たせることができます。

○サンプルコード15:画像のシャープネスを調整する

次に、画像のシャープネスを調整するコードを見ていきましょう。

シャープネスを調整することで、画像の細部をより鮮明にしたり、逆に柔らかい印象にすることができます。

下記のコードでは、画像のシャープネスを調整するためのシャープネスフィルタを作成し、それを画像に適用しています。

#include <stdio.h>
#include <stdlib.h>
#include <opencv2/opencv.hpp>

int main(void){
    cv::Mat src_img = cv::imread("sample.jpg");
    if(src_img.empty()){
        printf("画像ファイルを読み込めませんでした。\n");
        return -1;
    }

    cv::Mat dst_img;
    cv::GaussianBlur(src_img, dst_img, cv::Size(0, 0), 10);
    cv::addWeighted(src_img, 1.8, dst_img, -0.8, 0, dst_img);

    cv::imwrite("sharpened_image.jpg", dst_img);
    return 0;
}

このコードではまず、OpenCVライブラリを使って画像ファイルを読み込んでいます。

その後、cv::GaussianBlur関数を使ってガウシアンブラー(ぼかし)を適用し、cv::addWeighted関数を使って元の画像とぼかした画像を組み合わせることで、シャープネスを調整しています。

組み合わせる際の比率を変えることで、シャープネスの度合いを調整できます。

このコードを実行すると、”sample.jpg”の画像のシャープネスが調整された新しい画像が”sharpened_image.jpg”として出力されます。

実際には、シャープネスをどの程度に調整するかは、その画像が何を表現しているのか、またはどのように利用する予定かなど、具体的な目的により異なります。

画像のシャープネスを調整することで、視覚的な印象を大きく変えることが可能です。

例えば、人の顔写真に対してシャープネスを適用すると、肌の質感や髪の毛一本一本が鮮明になり、生き生きとした印象を与えることができます。

一方で、風景写真に対しては、シャープネスを下げることで、幻想的な雰囲気を演出することもできます。

●C言語で画像処理をする際の注意点と対処法

C言語で画像処理を行う際には、幾つかの注意点があります。

一つは、画像のサイズが大きい場合や複雑な処理を行う場合には、プログラムの実行時間が長くなる可能性があることです。

これを解消するためには、効率的なアルゴリズムの選択や、画像の前処理(例えば、画像サイズの調整や色空間の変換など)を行うことが有効です。

また、C言語のプログラムでは、メモリの管理に注意を払う必要があります。

画像のような大きなデータを扱う場合、メモリリークを起こすと、プログラムの動作が不安定になるだけでなく、システム全体の性能に影響を及ぼす可能性もあります。

これを防ぐためには、動的に確保したメモリは必ず適切に解放するようにしましょう。

また、画像データの読み書きに失敗した場合など、エラーハンドリングも重要です。

例えば上記のコードでは、画像ファイルが存在しない場合や開けない場合にはエラーメッセージを表示してプログラムを終了するようになっています。

このようなエラーハンドリングを行うことで、意図しない動作を防ぎ、デバッグを容易にします。

●画像処理をカスタマイズする方法

上記で紹介したサンプルコードは、基本的な画像処理を行うためのものですが、これらの技術を組み合わせて利用することで、さまざまなカスタマイズが可能です。

例えば、色調の変更とシャープネスの調整を組み合わせることで、特定の雰囲気を出すためのフィルターを作ることができます。

また、画像の一部を切り出して組み合わせることで、コラージュのような効果を出すことも可能です。

その他にも、OpenCVには機械学習のための機能も含まれており、これを利用することで顔認識や物体検出など、より高度な画像処理を行うことも可能です。

これらの高度な機能を活用するためには、それぞれの機能について理解を深め、適切に設定を行う必要があります。

まとめ

この記事では、C言語での画像処理の基礎について学んできました。

プログラミング初心者であっても、具体的なサンプルコードを手を動かしながら理解することで、画像処理の基本から応用までを理解することができます。

最後に、学んだ技術を活かして何か新しいものを作ってみましょう。

例えば、自分だけの画像フィルターを作ったり、写真を加工してオリジナルのアートワークを作るなど、可能性は無限大です。

ぜひ、この知識を活かして、楽しみながらプログラミングに挑戦してみてください。