0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ビットマップファイルをSobel filterによるエッジ検出(C言語)

Last updated at Posted at 2024-06-06

表題の通り、ビットマップファイルのエッジ検出を行うプログラムを書いてみた。

ソースコード

detect_edge.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include "bitmap.h"
#include "grayscale.h"

// detecting by Sobel Filter
void apply_sobel_filter(unsigned char* grayData, unsigned char* edgeData, int width, int height) {
	int gx, gy;
	int edge_strength;
	int x, y;
	for (y = 1; y < height - 1; y++) {
	for (x = 1; x < width - 1; x++) {
		gx = -grayData[(y - 1) * width + (x - 1)] - 2 * grayData[y * width + (x - 1)] - grayData[(y + 1) * width + (x - 1)] +
		grayData[(y - 1) * width + (x + 1)] + 2 * grayData[y * width + (x + 1)] + grayData[(y + 1) * width + (x + 1)];

		gy = -grayData[(y - 1) * width + (x - 1)] - 2 * grayData[(y - 1) * width + x] - grayData[(y - 1) * width + (x + 1)] +
		grayData[(y + 1) * width + (x - 1)] + 2 * grayData[(y + 1) * width + x] + grayData[(y + 1) * width + (x + 1)];

		edge_strength = abs(gx) + abs(gy);
		edgeData[y * width + x] = (unsigned char)(edge_strength > 255 ? 255 : edge_strength);
		}
	}
}

void convert_to_grayscale_and_detect_edges(const char* input_filename, const char* output_filename) {
	FILE* input_file = fopen(input_filename, "rb");
	FILE* output_file = fopen(output_filename, "wb");
	BMPFileHeader fileHeader;
	BMPInfoHeader infoHeader;
	unsigned char pixelData[3];
	unsigned char* grayData = NULL;
	unsigned char* edgeData = NULL;
	unsigned char value;
	int x, y;

	if (!input_file || !output_file) {
		fprintf(stderr, "I could not open file!!\n");
		return;
	}

	// reading BMPHeader
	//fread(&fileHeader, sizeof(BMPFileHeader), 1, input_file);
	fread(&fileHeader.type, sizeof(fileHeader.type), 1, input_file);
	fread(&fileHeader.size, sizeof(fileHeader.size), 1, input_file);
	fread(&fileHeader.reserved1, sizeof(fileHeader.reserved1), 1, input_file);
	fread(&fileHeader.reserved2, sizeof(fileHeader.reserved2), 1, input_file);
	fread(&fileHeader.offset, sizeof(fileHeader.offset), 1, input_file);
	fread(&infoHeader, sizeof(BMPInfoHeader), 1, input_file);

	// malloc for pixel data
	grayData = (unsigned char*)malloc(infoHeader.width * infoHeader.height * sizeof(unsigned char));
	edgeData = (unsigned char*)malloc(infoHeader.width * infoHeader.height * sizeof(unsigned char));
	if (!grayData || !edgeData) {
		fprintf(stderr, "I could not allocate memory area!!\n");
		return;
	}

	// writing BMPHeader to new file
	//frite(&fileHeader, sizeof(BMPFileHeader), 1, output_file);
	fwrite(&fileHeader.type, sizeof(fileHeader.type), 1, output_file);
	fwrite(&fileHeader.size, sizeof(fileHeader.size), 1, output_file);
	fwrite(&fileHeader.reserved1, sizeof(fileHeader.reserved1), 1, output_file);
	fwrite(&fileHeader.reserved2, sizeof(fileHeader.reserved2), 1, output_file);
	fwrite(&fileHeader.offset, sizeof(fileHeader.offset), 1, output_file);
	fwrite(&infoHeader, sizeof(BMPInfoHeader), 1, output_file);

	// converting grayscale
	for (y = 0; y < infoHeader.height; y++) {
		for (x = 0; x < infoHeader.width; x++) {
			fread(&pixelData, sizeof(pixelData), 1, input_file);
			grayData[y * infoHeader.width + x] = rgb_to_gray(pixelData[2], pixelData[1], pixelData[0]);
		}
	}
	// deceting edge
	apply_sobel_filter(grayData, edgeData, infoHeader.width, infoHeader.height);

	// writing pixel date to new file
	for (y = 0; y < infoHeader.height; y++) {
		for (x = 0; x < infoHeader.width; x++) {
			value = edgeData[y * infoHeader.width + x];
			fwrite(&value, sizeof(unsigned char), 1, output_file);
			fwrite(&value, sizeof(unsigned char), 1, output_file);
			fwrite(&value, sizeof(unsigned char), 1, output_file);
		}
	}

	fclose(input_file);
	fclose(output_file);
	free(grayData);
	free(edgeData);

	printf("done.\n");
}

int main() {
	char input_filename[BUFSIZ];
	char output_filename[BUFSIZ];

	printf("input file?:");
	scanf("%s", input_filename);
	printf("output file?:");
	scanf("%s", output_filename);
	convert_to_grayscale_and_detect_edges(input_filename, output_filename);

	return 0;
}

fread(&fileHeader, sizeof(BMPFileHeader), 1, input_file);としなかったのは、構造体の各メンバのサイズが構造体全体のサイズと合わず、読み込んだヘッダ情報がおかしくなって正しく動作しなかったため。
パディングで合わせてコードの可搬性を落とすくらいならと、各メンバのアドレスをfreadの第一引数とした。

grayscale.c
#include "grayscale.h"
#include <stdio.h>

unsigned char rgb_to_gray(unsigned char r, unsigned char g, unsigned char b)
{
	return (unsigned char)(r + g + b) / 3;
}

これくらいなら別に分けなくてもよかった気がする。
grayscale.hは関数プロトタイプのみなので省略。

bitmap.h
#ifndef BITMAP_H
#define BITMAP_H

// bitmap file header
typedef struct {
    uint16_t type;//BM
    uint32_t size;
    uint16_t reserved1;
    uint16_t reserved2;
    uint32_t offset;
} BMPFileHeader;

// bitmap info header
typedef struct {
    uint32_t size;
    int32_t width;
    int32_t height;
    uint16_t planes;
    uint16_t bitCount;
    uint32_t compression;
    uint32_t imageSize;
    int32_t xPixelsPerMeter;
    int32_t yPixelsPerMeter;
    uint32_t colorsUsed;
    uint32_t colorsImportant;
} BMPInfoHeader;

#endif

使い方と使用例

[user@localhost detect_edge]$ ./filter 
Open input file?:sample.bmp
Open output file?:sample_detect.bmp
done.

(ビットマップといいつつ使ったビットマップ画像がうまくアップロード出来なかったので、PNGに変換してアップロードしています)
入力画像(Windows11付属のペイントで書いたもの)
sample.png

出力画像
sample_detect.png

当初はグレースケールに変換した部分もファイル出力したくて、変換→書き出し→書き出したファイルを読み込み→エッジ検出→ファイル書き出しという動作の流れだったが、コマンドラインでやるべきことじゃないと思い、メモリ動的確保で対応することにした。

ノイズをのせた上でエッジ検出を行ってみる

思い付きで関数convert_to_grayscale_and_detect_edges()を改造し、指定した確率でランダムな値(unsigned charの範囲)のRGB値をピクセルデータとして書き込む関数を用意。

convert_to_grayscale_and_detect_edges()for文ブロックを抜粋(他はほぼ同様のコードなので割愛)。

	for (int y = 0; y < infoHeader.height; y++) {
		for (int x = 0; x < infoHeader.width; x++) {
			fread(&pixelData, sizeof(pixelData), 1, input_file);
			if((double)rand()/RAND_MAX <= prob){
				pixelData[2] = rand() % 256;
				pixelData[1] = rand() % 256;
				pixelData[0] = rand() % 256;
			}
			fwrite(&pixelData, sizeof(unsigned char), sizeof(pixelData), output_file);
		}
	}

probdouble型の変数で、0から1の間をとる)

これに適当なビットマップファイルを通し、ノイズ発生確率を0.5%としてノイズを載せる。プログラムの動作コマンドは画像下。

変換前画像(Windows付属のペイント3Dで作成。ペイント3Dはじめて使った)
paint3d.png

[user@localhost detect_edge]$ ./noise 
input file?:paint3d.bmp
output file?:paint3d_noised.bmp
noise occurrence pribability?[min:max=0.0:1.0]:0.005
done.
[user@localhost detect_edge]$ ./detect_edge 
input file?:paint3d_noised.bmp
output file?:paint3d_noised_detect.bmp
done.
[user@localhost detect_edge]$ 

ノイズ入り画像
paint3d_noised.png

これを前述の方法でエッジ検出する。

検出画像
paint3d_noised_detect.png

ソーベルフィルタでエッジ検出を行う場合は、ガウシアンフィルタなどノイズ除去フィルタを通してからでないと案の定の結果になった。

Makefile

CC = gcc
CFLAGS = -Wall
SOURCE = detect_edge.c grayscale.c
OBJECT = $(SOURCE:%.c=%.o)
TARGET = detect_edge

.PHONY : clean

$(TARGET) : $(OBJECT)
	$(CC) -o $(TARGET) $(OBJECT)

$(OBJECT) : $(SOURCE)
	$(CC) $(CFLAGS) -c $(SOURCE)

noise : noise.c
	$(CC) $(CFLAGS) -o noise noise.c

clean : 
	rm -f detect_edge noise $(OBJECT)

おわりに

せっかく(自分で載せたとはいえ)ノイズありの画像に対して特になにも施すことなくSobel filterを適用するということをしたのだから、比較対象としてなんらかのノイズ除去フィルタを通したパターンもやるべきだとは思う。けれども、大昔に大学の授業で習った「画像処理にはこんなフィルタがありますよ」程度の記憶しかないので、ガウシアンフィルタなどは今度また時間を見つけた折にでもやってみたい。

参考文献

1次微分(差分)によるエッジ検出

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?