プログラミング 演 習 3 - 集 中 講 義 版 - 2 日 目 資 料 & 課 題 花 泉 弘
この 回 の 目 標 1. 画 像 ファイルの 構 造 を 知 る 2. 画 像 ファイル(バイナリファイル)を 読 み 込 む 読 み 込 みには1 日 目 に 作 成 したreadcharline( ) を 使 用 します 3. 画 像 を 作 成 して 出 力 する 4. 読 み 込 んだ 画 像 データを 処 理 できる 画 像 上 の 座 標 (ラインとカラム)で 画 素 を 指 定 して 濃 度 値 を 得 る 指 定 された 領 域 の 画 素 濃 度 の 平 均 値 をR,G,Bごとに 求 める
高 さ(height) 1. 画 像 ファイルの 中 身 を 知 る その1 白 黒 画 像 のイメージ: 画 素 が 並 んでいる それぞれの 画 素 は 濃 淡 値 を 持 つ 0~255 (データは1byte/ 画 素 ) 幅 (width) カラー 画 像 の 場 合 は 三 原 色 の 組 合 せで 色 を 表 すため 白 黒 画 像 の 3 枚 分 の 容 量 が 必 要 になる カラー 画 像 では 白 黒 画 像 の 画 素 1 個 に 対 して 対 応 する RGB 各 成 分 合 わせて3つ 分 の 数 値 が 連 続 して 記 録 されている (RGBの 順 ) これらの 数 値 を 画 素 濃 度 と 呼 びます それぞれ 赤 緑 青 の 波 長 帯 (spectral band)における 観 測 値 なので これ らの 画 像 を3バンドデータと 呼 んだり バンド 数 は3である な どと 言 います センサによっては 可 視 から 近 赤 外 までN 個 の 波 長 帯 で 観 測 するものもあり これらはNバンドデータと 呼 ばれ ます これらの 情 報 がファイルに 記 録 されている 画 像 の 情 報 ヘッダー 情 報 : 画 像 のサイズ( 幅 と 高 さ)や1 画 素 のビット 数 バンド 数 (カラー 3 白 黒 1) データ 本 体 : 幅 高 さ バンド 数 分 のバイナリデータ(テキストではない)
2. 画 像 ファイルの 構 造 を 知 る その2 pnm ファイルとよばれる 種 類 の 画 像 を 例 にとって ファイルの 構 造 を 見 ていきます pnm ファイルには pgm ファイル ppm ファイル pbmファイルがあります pgm ファイル: 白 黒 画 像 で1 画 素 を8ビット(0~255 0x00 ~ 0xff)で 表 す 拡 張 子 は.pgm ppm ファイル:カラー 画 像 で3バンド 8ビット/ 画 素 拡 張 子 は.ppm pbm ファイル:2 値 画 像 1ビット/ 画 素 拡 張 子 は.pbm 構 造 :ファイルの 初 めにヘッダー 部 (テキストデータ)があり 続 いて 画 像 データ(バイナリデータ)が 順 に 詰 まっている ヘッダー 部 画 像 データ (1024 x 768 x 3 bytes) マジックナンバー 6:ppm, 5:pgm, 4:pbm width height P6 1024 768 255 画 素 値 の 最 大 値 この 例 では 1 byte/pixel
3. 画 像 ファイルの 構 造 を 知 る その3 pmmファイルは pnmファイル 形 式 を 拡 張 して 3つを 超 えた 数 の 観 測 波 長 帯 (バンド)の 画 像 を 同 じ 形 式 で 取 り 扱 えるようにしたもので マジックナンバーは0 次 の 行 に 画 像 の 幅 高 さ バンド 数 が 入 り 最 後 に 濃 度 の 最 大 値 が 書 かれています ヘッダー 部 画 像 データ (1024 x 768 x 4 bytes) width height band P0 1024 768 4 255 画 素 値 の 最 大 値 この 例 では 1 byte/pixel
例 題 2-1 画 像 ファイル(pnm)の 構 造 を 確 認 しなさい 1では 確 認 してみましょう ブラウザを 使 って 好 きな 画 像 をダウンロードしてください 以 前 ダウンロードしたものがあれば それで 構 いません 2ダウンロードしたものが.jpg ファイルであれば pnm ファイルに 変 換 します 3 画 像 の 入 っているディレクトリに 移 動 5 変 換 には convert を 用 います 4 画 像 を 確 認.jpg ファイルです 拡 張 子 で 区 別 6 確 認 します.ppm と.pgm ファイルができて いることがわかります 7 画 像 も 見 てみます *pnm ファイルとは ppm, pgm, pbm ファイルの 総 称 です
例 題 2-1 つづき 1 less コマンドを 使 います 2 y を 入 力 します 3ヘッダー 部 が 確 認 できました マジックナンバーの 違 いに 注 意 4 q を 入 力 して 終 了 します
4. 画 像 ファイル(バイナリファイル)を 読 み 込 む 画 像 ファイルのオープンには fopen()を 用 います ここでは バイナリファイルを 扱 うので fopen(file.ppm, rb ) のように b を 追 加 して 指 定 します テキストファイルは r だったわけですが 違 いがわかりますか? r: 入 力 時 は r, n n へ 変 換 (ファイルへの) 出 力 時 には n r, n への 変 換 rb: 何 もしない の 違 いがあります ヘッダー 部 (テキスト) 画 像 データ(バイナリ) (1024 768 3 bytes) pnm/pmm 形 式 の 画 像 ファイルは 左 図 の ように 最 初 にテキストデータがあり その 後 ろにバイナリデータがあります 方 針 : テキストデータに 関 しては 行 末 の n に 着 目 し その 内 容 から 読 み 込 むべき 後 半 の 画 像 データの 個 数 を 知 り その 個 数 分 バッファに 蓄 える 注 意 : 画 素 データはunsigned char 型 の 配 列 に 代 入 します ( 関 数 等 の 宣 言 もchar 型 からunsigned char 型 へ 変 更 )
例 題 2-2 画 像 データのヘッダー 部 を 読 み 込 む 作 成 した readcharline() を 使 って pnm/pmm ファイルのヘッダー 部 を 読 んでみましょう 指 定 したバイト 数 を 読 み 込 むだけでなく 1ライン 分 のデータも 読 めることがわかります あとで 画 像 を 読 むので 配 列 は 大 きめに bを 追 加 して ここは3 限 定 で pmm と ppmの 違 いが わかりますか? チェック 用 の main 文 ex202/main_charline.c
さて ここまでの 処 理 で pnm/pmm 画 像 のヘッダー 部 を 文 字 列 と して 読 み 込 むことができました ただ 文 字 列 のままでは 使 えま せんのでそれらを 数 値 として 認 識 することが 必 要 です Cでは keyboard からの 入 力 用 の 関 数 として scanf( %d, &k); を 用 意 していますが それと 同 様 にバッファからの 読 み 取 りには sscanf(buf, %d, &k); が 使 えます pnm/pmm 画 像 をから3 回 続 けて1 行 分 読 み 取 ることを 考 えます buf には 文 字 列 としてそれぞれの 回 の 読 み 込 みに 対 して 例 えば P6 1024 768 255 が 入 るので 1 回 目 に1 行 分 読 み 取 ったところで buf[0] == P が 真 なら pnm/pmm であることがわかり さらに buf[1] == 6 が 真 なら ppm 画 像 で band = 3 であることがわかり buf[1] == 5 が 真 なら pgm 画 像 で band = 1 とわかります また buf[1] == 0 が 真 なら pmm 画 像 なので 未 決 ということで 仮 にband = 0 としておきます
2 回 目 に1 行 分 読 み 取 ったところで band > 0 が 真 ならすでに pnm 画 像 であることがわかっているので sscanf(buf, %d%d, &width, &height); とすることで width = 1024, height = 768 といった 具 合 にint 型 の 変 数 width と height に 数 値 を 格 納 することができます 同 様 に band == 0 が 真 であれば sscanf(buf, %d%d%d, &width, &height, &band); とすることで width, height に 加 えて band にも 正 しい 値 が 入 ります 3つの 値 がそろったら 続 けて 3 回 目 に1 行 分 読 み 取 ります その 時 の 値 が255であれば 1 画 素 1バイトでこの 画 像 が 構 成 されていることがわかります なお この 値 は65535 のこともあり この 場 合 は 1 画 素 2バイトで 構 成 されて いることになります 当 然 画 像 を 読 み 込 むバッファもunsigned char ではなくて unsigned short int になります 話 を 戻 します 3つの 値 がそろったら 読 み 込 むべき 画 素 数 は width * height * band のように 求 めることができます この 値 を 設 定 すれば buf に 画 像 データを 読 み 込 むことができます
例 題 2-3 画 像 データを 読 み 込 む 結 局 のところ pnm/pmm ファイルを 読 み 込 むには テキスト 部 を 読 み 込 んで ファイルの 種 類 (pgm/ppm/pmm)を 判 別 し(pgm/ppmの 場 合 はバンド 数 がわかる) 画 像 の 幅 と 高 さ(とpmmの 場 合 は バンド 数 )を 知 る 濃 度 の 最 大 値 も 知 る 求 めた 幅 高 さ バンド 数 の 情 報 から 連 続 して 必 要 なバイト 数 分 だけデータを 読 み 込 む という 流 れで 画 像 を 読 み 込 むことができる これまでは char *buf だったもの int readimage(char *imagefile, unsigned char *img, int *width, int *height, int *band) を 作 成 しなさい 仕 様 は 以 下 の 通 り main 側 からは k = readimage(filename, img, &width, &height, &band); のように 用 いる 以 下 演 習 題 ごとにディレクトリを 区 別 して( 例 えば ex202 など)に 作 成 すること *imagefile : 画 像 ファイルの 名 前 が 格 納 された 配 列 *img : 画 像 を 格 納 するバッファ( 大 きめに 確 保 しておく; これまでは *buf であった) *width, *height, *band : 画 像 の 幅 高 さ バンド 数 戻 り 値 : 正 常 読 み 取 りはEXIT_SUCCESS 異 常 はEXIT_FAILURE(ファイルが 存 在 しない pnm/pmmでない 読 み 込 みエラー( 個 数 分 だけデータがない))
例 題 2-3( 続 き) 画 像 ファイルの 読 み 込 み ここは unsigned char で ex203/readimage.c 負 数 を 与 えて1 行 目 を 読 む 4 行 目 を 読 む 2 行 目 を 読 む 正 数 を 与 えて 画 素 数 分 読 む
例 題 2-3( 続 き) 動 作 確 認 ( 画 像 を 表 示 させてみる) ex203/imgout.c 使 い 方 $ imgout mountain.ppm xv - & で 画 像 が 表 示 されたらOK.
いかがですか? 無 事 画 像 は 出 力 されましたか? ここまでで 一 応 は 画 像 を 読 み 込 むことができるようになりました 例 題 2-3 では 画 像 の 出 力 の 仕 方 についても 少 し 書 いてあったの ですが 後 ほど 詳 しく 見 ていきます まず ちょっと 不 便 なところが 残 っているreadimage( )をもう 少 し 便 利 にすることを 考 えます 不 便 な 点 とは readimage( ) を 呼 んで width, height, band を 知 る 前 に 画 像 を 読 み 込 むための 配 列 の 大 きさを 決 定 しなければなら ない 点 です width, height, band を 知 ってから 配 列 を 設 定 すること ができれば すごくすっきりするはずです ということで 動 的 なメモリ 確 保 を 行 ってみましょう
5. 動 的 なメモリ 領 域 の 割 り 当 て 読 み 込 んだ 画 素 データを 格 納 する 配 列 を 動 的 に 作 成 するには malloc( )や calloc( ) を 用 い ます 画 素 濃 度 を 格 納 する 配 列 (ポインタ)を img とすると img = calloc( 割 り 当 てる 要 素 数, 1 要 素 あたりのバイト 数 ) か もしくは img = malloc( 必 要 バイト 数 ) のように 用 います これらの 例 では 要 素 数 =width * height * band で 1 要 素 あたりの バイト 数 は sizeof(unsigned char) で 求 めます malloc( ) の 必 要 バイト 数 はそれらの 積 になり ます どちらも 成 功 すると 割 り 当 てられたメモリ 領 域 の 先 頭 アドレスが img に 格 納 され 失 敗 すると NULL が 格 納 されます calloc( ) の 場 合 には メモリは 全 ビット0にクリアされます malloc( ) の 場 合 は 何 もしないので 以 前 の 内 容 が 残 ったままになっています calloc( ) や malloc( ) で 割 り 当 てられたメモリ 領 域 が 不 要 になった 場 合 は free( ) で 開 放 します プログラムが 終 了 すると 自 動 的 にfree( ) が 行 われるので 特 に 意 識 する 必 要 はあり ません 以 下 のように 用 います unsinged char 型 のポインタ If((img = calloc(width * height * band, sizeof(unsigned char))) == NULL){//メモリの 割 り 当 て fprintf(stderr, memory allocation error n ); return (NULL); //これを 含 む 関 数 の 戻 り 値 がunsigned char 型 のポインタなのでNULLを 返 す } 何 かの 処 理 free(img); //メモリの 解 放 ( 通 常 は 意 識 しなくてもよい)
例 題 2-4 ファイル 名 を 与 えてポインタで 受 ける 演 習 2-3 では 画 像 の 大 きさを 想 定 して main 側 で 大 き 目 の 配 列 を 用 意 していましたが ここでは width, height, band が 得 られた 後 に 動 的 に 配 列 用 のメモリを 確 保 します さっそく calloc( ) を 使 ってみましょう せっかくなので readimage( ) を 改 造 していきます 改 造 前 ( 例 題 2-3) int readimage(char *imagefile, unsigned char *img, int *width, int *height, int *band) を 作 成 main 側 からは k = readimage(filename, img, &width, &height, &band); のように 用 いる 改 造 後 ( 名 前 もreadimage2( )に 変 更 ) unsigned char *readimage2(char *imagefile, int *width, int *height, int *band) を 作 成 main 側 からは unsigned char *img; の 宣 言 をした 後 img = readimage2(filename, &width, &height, &band); のように 用 いる ファイル 名 を 与 えて 幅 高 さ バンド 数 と 共 に 画 像 本 体 を 受 け 取 る 形 になり 非 常 にすっきりする
例 題 2-4 ( 続 き) 画 像 ファイルの 読 み 込 み (すっきり 版 ) ex204/readimage2.c j 個 の unsigned char 領 域 ( 合 計 j バイト)を 確 保
例 題 2-4 ( 続 き) 動 作 確 認 ( 画 像 を 表 示 させてみる) ex204/imgout2.c 使 い 方 $ imgout2 mountain.ppm xv - & で 画 像 が 表 示 されたらOK.
例 題 2-5 さらに 一 般 化 する(#で 始 まる 行 の 読 み 飛 ばし) pnm/pmm ファイルには 場 合 によっては 下 の 例 のようにコメントが 付 加 されるので コメントをスキップするように 改 造 する(ex205 に readimage3()として 作 成 する) 変 更 点 :1 行 分 データを 読 んでみて バッファの 先 頭 が#なら 読 み 飛 ばす
例 題 2-5 ( 続 き) # 行 の 読 み 飛 ばし ex205/readimage3.c do while 文 で 行 頭 が#の 場 合 に 読 み 飛 ばす
例 題 2-5 ( 続 き) 動 作 確 認 ( 画 像 を 表 示 させてみる) ex205/imgout3.c 使 い 方 $ imgout3 mountain.ppm xv - & で 画 像 が 表 示 されたらOK.
6.ここまで 作 成 してきた 画 像 読 み 込 み 関 数 の 簡 単 なまとめ readimage( ): 関 数 にファイル 名 を 渡 す 関 数 側 でファイルをオープンし 読 み 取 った 画 像 のサイズ 情 報 に 基 づいて main 側 で 定 義 した 配 列 に 画 素 濃 度 を 格 納 して 戻 る readimage2( ) 関 数 にファイル 名 を 渡 す 関 数 側 でファイルをオープンし 読 み 取 った 画 像 のサイズ 情 報 に 基 づいて 関 数 側 で 画 素 濃 度 を 格 納 する 配 列 にメモリを 動 的 に 割 り 当 て その 配 列 の ポインタを 戻 す readimage3( ) 関 数 にファイル 名 を 渡 し 関 数 側 でファイルをオープンする 画 像 のサイズ 情 報 を 読 み 取 る 際 に 画 像 ヘッダー 部 の#を 読 み 飛 ばす 関 数 側 で 画 素 濃 度 を 格 納 する 配 列 に 動 的 にメモリを 割 り 当 てその 配 列 のポインタを 戻 すところは readimage2( ) と 同 じ readimage2( ) 以 降 では 画 素 濃 度 を 格 納 する 配 列 を 動 的 に 作 成 していますが malloc( )や calloc( ) を 用 います 画 素 濃 度 を 格 納 する 配 列 (ポインタ)を img とすると img = calloc( 割 り 当 てる 要 素 数, 1 要 素 あたりのバイト 数 ) / img = malloc( 必 要 バイト 数 ) のように 用 います これらの 例 では 要 素 数 =width * height * band で 1 要 素 あたりの バイト 数 は sizeof(unsigned char) で 求 めます malloc( ) の 必 要 バイト 数 はその 積 になります どちらも 成 功 すると 割 り 当 てられたメモリ 領 域 の 先 頭 アドレスが img に 格 納 され 失 敗 する と NULL が 格 納 されます calloc( ) の 場 合 に 限 り メモリは 全 ビット0にクリアされます
7. 画 像 ファイルの 出 力 これまで 何 気 なく 画 像 を 出 力 してきましたが 読 み 込 んだ 画 像 ファイルや 処 理 した 結 果 を 画 像 として 表 示 させたり ファイルとして 保 存 する 方 法 として 以 下 の3つの 手 法 を 試 してみ ます 1. 標 準 出 力 に 書 き 出 す( 今 まで 使 っていたもので リダイレクトを 使 って ファイルに 保 存 し たり パイプを 使 って 表 示 ソフトへ 流 し 込 んだりできます) 2.ファイルをオープンして 書 き 出 す 3.popen()を 使 って 表 示 ソフトへ 直 接 送 り 込 む 1. 標 準 出 力 に 書 き 出 す imgout は 次 ページ 参 照 リダイレクト いろいろに 使 えて 便 利 だが ファイルは1 個 しか 出 力 できない ファイルが 生 成 パイプ ソースは 次 ページ
例 題 2-6 標 準 出 力 に 書 き 出 す この 内 容 をex206/imgout4.c に 保 存 し $ gcccomp imgout4 上 で 作 ったものを 早 速 使 っています メモリの 開 放 を 忘 れずに ただし この 例 の ようにすぐに 終 了 するのであれば 不 要 です 次 の 2,3の 例 でも 同 様 です この 部 分 が 出 力 部 4 4 動 作 確 認
例 題 2-7 ファイルをオープンして 書 き 出 す ex207/imgout5.c に 保 存 ファイルをオープン printf() が fprintf(fp, ) になっただけ 動 作 確 認 ファイルクローズ
例 題 2-8 popen()を 使 って 表 示 ソフトへ 直 接 送 り 込 む ex208/imgout6.c に 保 存 慣 れないと 感 じがつかめ ないかもしれませんが 大 変 便 利 なものです 動 作 確 認 コマンドをオープンする 形 です その 他 は fopen()と 変 わりません ただし 画 像 が 出 たら 処 理 はそこで ストップしています xvを 終 了 させ ないと 次 に 進 みません
height 8. 画 像 データの 処 理 - 画 素 データへのアクセスー y.. unsigned char *img, *pa, *pb, *pc, *pd; int I, j, k, width, height, band;. img = readimage3(. 1 2 x 4 (xs, ys) width * band 3 6 5 (xe, ye) 左 のような 宣 言 と 画 像 の 読 み 込 みを 行 った とすると 読 み 込 まれ 画 像 データは 左 下 の ようになっている (xs, ys) (xe, ye) で 決 まる 矩 形 領 域 内 の 画 素 へのアクセスを 考 える ポインタ img は 画 像 データの 開 始 点 を 保 持 しているので 別 の 変 数 を 使 用 する 3 種 類 のポインタを 使 用 する pa : 初 期 値 が2ysで ループが 回 るたびに 次 の 行 4へ 移 動 する(+= width * band) pb : 3(2+band * xs)から 始 まり band 数 ずつ 進 む 3の 次 は5に 移 動 する(+= band) pc : pb 3から 始 まり band の 数 だけ 増 分 1で 移 動 する3 の 次 は6(+= 1) pa = img + ys * width * band; // 初 期 値 pa += width * band; // 増 分 pb = pa + xs * band; // 初 期 値 pb += band; // 増 分 pc = pb; // 初 期 値 pc++; // 増 分
例 題 2-9 画 像 ファイル 入 力 し ネガポジ 変 換 した 画 像 を 表 示 させなさい ある 画 素 の 濃 度 (ピクセルの 持 つ 値 )をd とするとき ネガポジ 変 換 はその 画 素 の 濃 度 を255 d とすることである 画 素 値 のアクセスは3 重 ループを 用 いる for( i = 0; i < height; i++){ //line 数 に 対 するループ for( j = 0; j < width; j++ ){ //column 数 に 対 するループ for( k = 0; k < band; k++){ //band 数 に 対 するループ ここに 処 理 の 手 続 きを 書 く } } }
例 題 2-9 ( 続 き) stdout へ 出 力 する 方 法 で 行 います ex209/nega.c 動 作 確 認 画 像 全 体 なので i = 0, j = 0 かつ i < height, j < width (xs, ys) (xe, ye) の 矩 形 領 域 なら i = ys, j = xs かつ i <= ye, j <= xe
height この 部 分 は 非 常 に 重 要 なので もう 一 度 ポインターの 設 定 法 とプログラム 上 で それらがどのようにふるまうのかを 確 認 してください 画 像 全 体 矩 形 領 域 i = ys i <= ye J = xs j <= xe 1 x width * band y 2 4 3 (xs, ys) 6 5 (xe, ye) 3 種 類 のポインタを 使 用 する pa : 初 期 値 が2ysで ループが 回 るたびに 次 の 行 4へ 移 動 する(+= width * band) pb : 3(2+band * xs)から 始 まり band 数 ずつ 進 む 3の 次 は5に 移 動 する(+= band) pc : pb 3から 始 まり band の 数 だけ 増 分 1で 移 動 する3 の 次 は6(+= 1) pa = img + ys * width * band; // 初 期 値 pa += width * band; // 増 分 pb = pa + xs * band; // 初 期 値 pb += band; // 増 分 pc = pb; // 初 期 値 pc++; // 増 分
本 日 の 課 題 ここまでで 画 像 の 入 出 力 とそれぞれの 画 素 へのアクセス 方 法 が 分 かったと 思 います それを 前 提 として 以 下 の 課 題 に 取 り 組 んで ください 2-1. 一 様 な 色 の 画 像 を 生 成 し 表 示 させてください 画 像 のサイズと 色 はコマンドラインから 与 えるものとしてください $ singlecolor width height red green blue のような 感 じで 使 うことを 想 定 しています red, green, blueは 0~255の 値 をとります 2-2.サイズは 自 由 でよいので 画 像 の 中 に 円 ( 中 心 位 置 (xc, yc)を 指 定 する) 四 角 形 ( 左 上 と 右 下 の 座 標 (xs, ys) (xe, ye)を 指 定 する) 三 角 形 (3つの 頂 点 の 座 標 (x1, y1), (x2,y2), (x3, y3)を 指 定 する) を 何 個 か 描 いてください 色 は 指 定 するか もしくはそこだけ ネガにするのもありです