減色処理 雑談
2022.04.04

YouTube でも紹介しています。画像をクリックすると再生できます。
 元画像 |
 LCD表示画像 |
24ビット(1677万色)画像を一般的なTFT LCD(Thin Film Transistor Liquid Crystal Display)に表示させています。
しかし、画像処理確認用のディスプレイとして使用する際には注意が必要です。
■検証環境

ノートパソコンから、TeraTermによりラズベリーパイにSSH接続して操作します。
ビルドには、PlatformIOを使用しています。
→ Arduino開発環境構築 PlatformIO
■Adafruit 2.2" 18-bit color TFT LCD display with microSD card breakout
ディスプレイには、ILI9340ドライバ仕様の解像度320x240、16bitカラーモニターを使用します。
・2.2" diagonal true TFT LCD display has 320x240 colour pixels
・The TFT driver (ILI9340) can display full 18-bit color (262,144 shades!)
・ultra-low-dropout 3.3V regulator and a 3/5V level shifter so you can use it with 3.3V or 5V power and logic.
・comes with a microSD card holder so you can easily load full color bitmaps from a FAT16/FAT32 formatted microSD card (card not included)
■Unexpected Maker TinyS2
TinyS2 は、ESP32-S2チップを搭載しており、4MB Flash と 2MB PSRAM を実装していますので、画像解像度320x240程度のLCDを使うのであれば、オンメモリー上での処理が可能です。

■配線
| TinyS2 | - | Adafruit TFT display |
| GND | - | GND |
| 3V3 | - | VIN |
| GPIO[14] | - | DC:SPI data / command selection |
| GPIO[9] | - | RESET |
| GPIO[8] | - | SD CS:Chipselect |
| GPIO[38] | - | LCD CS:Chipselect |
| SPI MO[35] | - | SDI(MOSI):SPI data line |
| SPI MI[36] | - | SDO(MISO):SPI data line |
| SPI SCK[37] | - | SCK:SPI clock line |
| GPIO[4] | - | BACKLIGHT |
■BMPファイル → 配列データ変換
サンプル画像として左の24bit、1677万色BMP形式画像ファイル(240x320)を用います。
BMP画像ファイルの解析ですが、そのままのバイナリーデータでは検証しにくいので、16進データ・テキスト配列に変換します。
配列への変換方法はお好み次第ですが、手っ取り早く、PHPで変換プログラムを作成しました。
bin2hex.php
<?php
$fname = $argv[1];
if(!($file_size = filesize($fname))) return;
list($file_name,$file_type) = explode('.',$fname,);
if (!($infp = fopen($fname,"rb"))) return;
$outfile = $file_name.'.h';
$outfp = fopen($outfile,"w");
fwrite($outfp, "const unsigned long mid_len = ".$file_size.";\n");
fwrite($outfp, "const unsigned char mid_data[".$file_size."] = {\n");
$i = 0; $cnt = 0;
while (!feof($infp)) {
$byte = fread($infp, 1);
$hexChar = '0x'.bin2hex($byte);
if ((++$cnt)==$file_size) {
fwrite($outfp,$hexChar."\n};\n");
break;
} else {
if ((++$i)%16) {
fwrite($outfp,$hexChar.",");
} else {
fwrite($outfp,$hexChar.",\n");
}
}
}
fclose($outfp);
fclose($infp);
?>
BMP画像ファイル nostalgic_eye.bmp を nostalgic_eye.h に変換してみます。
$ php bin2hex.php nostalgic_eye.bmp
PHPスクリプトを実行すると、BMPファイルの拡張子が h に置き換わったデータファイルが生成されます。
【nostalgic_eye.h の内容】
const unsigned long bmp_len = 230454;
const unsigned char bmp_data[230454] = {
0x42,0x4d,0x36,0x84,0x03,0x00,0x00,0x00,0x00,0x00,
0x36,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0xf0,0x00,
0x00,0x00,0x40,0x01,0x00,0x00,0x01,0x00,0x18,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0x0f,
0x00,0x00,0x61,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xbe,0xef,0xfc,0xc1,0xed,0xfa,
0xca,0xed,0xfa,0xcf,0xf2,0xfc,0xcf,0xf3,0xfa,0xd0,
0xf6,0xf9,0xd3,0xf9,0xfa,0xd2,0xf8,0xfa,0xd2,0xf6,
0xfb,0xd1,0xf5,0xfd,0xd0,0xf3,0xfe,0xd1,0xf3,0xfe,
........
};
■Header
■InfoHeader
■Pixel Data
Header内のDataOffset値は[0x36,0x00,0x00,0x00]で、54バイトなので、ColorTableは存在せず、InfoHeaderの次にPixel Dataが続きます。
■BMPファイルの構造
BMPファイルは、Header,Info header,ColorTable,Pixel Dataにより構成されています。
データはリトルエンディアン、つまり後ろのバイトデータの方が上位にある形式で記録されます。
24bitビットマップの場合、1画素あたり24bit(3byte)で、Blue(8bit)、Green(8bit)、Red(8bit)の順番で色の値が記録されます。
→ THE BMP FILE FORMAT
■画像表示
まずは、この画像をそのまま表示させてみます。
プロジェクトを作成します。
$ mkdir ~/TinyS2
$ cd ~/TinyS2
$ pio init -b um_tinys2
ライブラリをインストールします。
$ pio lib install 571
$ pio lib install 13
$ pio lib install 6214
環境設定ファイルを編集します。>
$ vi platformio.ini
[env:um_tinys2]
platform = espressif32
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32
board = um_tinys2
framework = arduino
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
lib_deps =
adafruit/Adafruit ILI9341@^1.5.10
adafruit/Adafruit GFX Library@^1.10.14
adafruit/Adafruit BusIO@^1.11.3
プログラムを作成します。
$ vi src/bitmap.ino
このコードでは、画像をヘッダーファイルには含めず、はファイルから読み込んでいます。
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SD.h>
#define SPI_miso 36
#define SPI_mosi 35
#define SPI_sck 37
#define SPI_sd_ss 8
#define SPI_tft_ss 38
#define SPI_tft_dc 14
#define SPI_tft_rst 9
#define SPI_tft_bl 4
SPIClass SDSPI(HSPI);
Adafruit_ILI9341 tft = Adafruit_ILI9341(SPI_tft_ss, SPI_tft_dc, SPI_mosi, SPI_sck, SPI_tft_rst, SPI_miso);
typedef struct {
uint8_t Signature[2];
uint8_t FileSize[4];
uint8_t reserved1[4];
uint8_t DataOffset[4];
uint8_t Size[4];
uint8_t Width[4];
uint8_t Height[4];
uint8_t Planes[2];
uint8_t BitPerPixel[2];
uint8_t Compression[4];
uint8_t ImageSize[4];
uint8_t XpixelsPerM[4];
uint8_t YpixelsPerM[4];
uint8_t ColorsUsed[4];
uint8_t ImportantColors[4];
} BMP_FORMAT;
uint32_t swap32(uint8_t *pt) {
uint32_t ret;
ret = *pt;
ret |= (*(pt+1)<< 8)&0x0000ff00;
ret |= (*(pt+2)<<16)&0x00ff0000;
ret |= (*(pt+3)<<24)&0xff000000;
return ret;
}
void setup() {
int32_t x, y, width, height;
uint8_t nBuf[16];
uint8_t *pixels, *imgpt;
BMP_FORMAT *bmp_fmt;
File fileSD;
SDSPI.begin(SPI_sck, SPI_miso, SPI_mosi, -1);
SD.begin(SPI_sd_ss, SDSPI);
fileSD = SD.open("/nostalgic_eye.bmp", FILE_READ);
bmp_fmt = (BMP_FORMAT *)malloc(sizeof(BMP_FORMAT));
fileSD.read((uint8_t*)bmp_fmt,sizeof(BMP_FORMAT));
width = swap32(bmp_fmt->Width);
height = swap32(bmp_fmt->Height);
pixels = (uint8_t *)malloc(width * height * 3);
if (swap32(bmp_fmt->DataOffset)>54) fileSD.read(nBuf,swap32(bmp_fmt->DataOffset));
uint8_t mod = (width % 4) * 3;
for(imgpt=pixels,y=0; y < height; y++){
for(x=0; x < width; x++,imgpt+=3){
fileSD.read(nBuf,3);
*imgpt = nBuf[0];
*(imgpt+1) = nBuf[1];
*(imgpt+2) = nBuf[2];
}
if (mod>0) fileSD.read(nBuf,mod);
}
fileSD.close();
SD.end();
SDSPI.end();
tft.begin();
tft.setRotation(2);
tft.fillScreen(ILI9341_BLACK);
for(imgpt=pixels,y=0; y < height; y++){
for(x=width-1; x>=0; x--,imgpt+=3) {
tft.drawPixel(x, y, tft.color565(*(imgpt+2),*(imgpt+1),*imgpt));
}
}
free(bmp_fmt);
free(pixels);
}
void loop() {}
プログラムの書込みは、TinyS2をRaspberry Pi にUSB接続後に、[BOOT]を押しながら[RESET]をクリックして、デバイスをダウンロードモードにします。
$ pio run -t upload -e um_tinys2
TinyS2の[RESET]ボタンを押すことで、ダウンロードモードから抜けて、プログラムが実行されます。

元画像はR、G、B各1バイト計3バイトの色情報を持っていますが、このTFT DISPLAYでは2バイトの色情報が用いられます。
画像表示には、tft.drawPixel()関数を用いて、1ドットずつ表示しています。
tft.drawPixel(x, y, tft.color565( red, green, blue));
tft.color565()関数では、赤を5ビット、緑を6ビット、青を5ビットの計16ビット、2バイトに減色しています。
tft.drawPixel()関数では、この減色された色情報を元に画像を再現します。
独自の関数color565()に置き換えてみても表示結果は変わらないので、tft.color565()は単純にビット処理を行っているだけです。
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue){
return ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
}
3バイトの色情報を2バイトに減色する様子を、画像データ先頭の1ピクセルを例にとって説明します。
| B | G | R |
| 0xbe | 0xef | 0xfc |
| 元画像 | 1011 1110 | 1110 1111 | 1111 1100 |
| ↓3bitシフト | ↓2bitシフト | ↓3bitシフト |
| 減色画像 | 0001 0111 | 0011 1011 | 0001 1111 |
| | ↓↓↓ | |
| LCD表示画像 | 1111 1111 0111 0111 |
24bit BMP画像では、青、緑、赤の順番で色情報が繰り返されます。
青と赤は右に3ビットシフトして、それぞれ5ビットに、緑は2ビットシフトして6ビットの情報に切り詰められています。
赤5ビット、緑6ビット、青5ビットの順でつなぎ合わせて、計16ビット、2バイトにしています。
 元画像 |
 減色画像 |
緑の色情報が、赤と青に比べて1bit多いので、全体的に緑がかった画像になっています。
また、各色のビット数が減ったため、色の数値が小さくなり、黒に近づくため、暗い色合いになっています。
 減色画像 |
 LCD表示画像 |
ディスプレイ表示の際には、減色された2バイトの色情報を用いて画像を再現しています。
結果として、tft.drawPixel()内部で優れた補正処理が行われていることがわかります。
このように、TFT LCDを使った画像の表示では、モジュール自体が適正な補正を実行してしまうため、表示される画像が本来の画像とは異なっていることを理解しておく必要があります。
画像処理の確認に用いるのであれば、24bit画像でれば、各色1バイトをそのまま表現できるフルカラーディスプレイの使用も検討したほうがよいかもしれません。
■補足
tft.color565 では 24bit BMP画像を 16bit BMP画像に変換していました。
16ビットカラーでは、最大65,536色を表現できます。
光の三原色、赤・緑・青のうち、赤と青を5ビット(32段階)、緑を6ビット(64段階)で表現しています。
緑だけ1ビット多いのは、人間の目は緑色の変化に敏感なためと言われています。
単純にビット数を減らしていくと、赤緑青各色が 0x00 に近づくので全体として暗くなっていきます。
 元画像(例:0xbe,0xef,0xfc) |
 各色1bit減色(例:0x5f,0x77,0x7e) |
 各色2bit減色(例:0x2f,0x3b,0x3f) |
 各色3bit減色(例:0x17,0x1d,0x1f) |
■参考文献
・Bitmapファイルを入出力してみる - [物理のかぎしっぽ]
・Bitmapファイルフォーマット
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|