減色処理 均等量子化法とK平均法
2022.04.18
YouTube でも紹介しています。画像をクリックすると再生できます。
今回は24bit(1677万色)カラーBMP画像を、8bit(256色)カラーBMP画像に減色します。
下記の手順により、画像の減色処理を最適化してみました。
1.均等量子化法で、24ビットから8ビット画像に減色する
2.K平均法により、しきい値を補正する
3.フロイド-スタインバーグ・ディザリングを適用してみる
■均等量子化法(uniform quantization method)
最も簡単な減色方法は、R,G,B それぞれを等分割するという考え方です。
RGB各256色の情報を「赤と緑は7分割、青は5分割」してブロック化します。
R: {0, 43, 85, 128, 170, 213, 255}
G: {0, 43, 85, 128, 170, 213, 255}
B: {0, 63, 127, 191, 255}
この赤7色×緑7色×青5色の組み合わせで、245色のパレットを作成します。
【補足】
赤7色×緑6色×青6色の組み合わせで、252色のパレットを作成することも可能です。
また、赤・緑・青、各{0x00,0x33,0x66,0x99,0xCC,0xFF}の組み合わせで作る216色はセーフカラーと呼ばれています。
元画像の各RGB値を一番近いパレット値に置き換えていきます。
例えば、RGB値
(190,239,132)
↓
(170,255,127)
に置き換えます。
このようにして、画像データを置き換えたものが左の画像です。
下瞼がベタな色合いになってしまっています。
そこで、しきい値に用いる値に、256色を7分割して、各々分割されたブロックの中央値を赤と緑のしきい値に、5分割した中央値を青のしきい値にすると、
下記のようになります。
R: {18, 54, 90, 126, 162, 198, 234}
G: {18, 54, 90, 126, 162, 198, 234}
B: {24, 76, 128, 180, 232}
先程と同様に、元画像の各RGB値を一番近いパレット値に置き換えていきます。
しきい値によって、かなり画像が改善されているのがわかります。
■K平均法(Kmeans)
次に、しきい値を最適化する方法を考えてみます。
K平均法は「しきい値をもとに各ピクセルのRGB値をブロックに分割して、そのブロック毎のRGB値の平均色をしきい値に置き換える」という事をしきい値の変化がなくなるまで繰り返します。
各ブロック毎に含まれる元画像のRGB値を、ブロック毎に足し込んで出現数で割った色の平均値をしきい値に設定しなおします。
R:{18,54, 90,126,162,198,234}
G:{18,54, 90,126,162,198,234}
B:{24,76,128,180,232}
↓Kmeans適用
R:{15,57, 92,127,162,200,233}
G:{17,57, 91,126,164,197,232}
B:{28,75,129,181,228}
この新たなしきい値を用いて、再描画したものが左の画像です。
新たに設定されたしきい値をもとに、再度しきい値を計算しなおします。
R:{15,57, 92,127,162,200,233}
G:{17,57, 91,126,164,197,232}
B:{28,75,129,181,228}
↓再計算
R:{15,58, 94,127,162,201,233}
G:{18,58, 92,127,165,196,230}
B:{29,76,129,180,226}
本来は、しきい値に変化がなくなるまで繰り返すようですが、あまり変化がないので2回で終わりにしました。
■誤差拡散法
画像の色数や階調数を減らす際に擬似的に中間色を表現するディザリング手法の一つで、ある点を減色する際に元の色との誤差を近傍の点の色情報に上乗せする方式。
●フロイド-スタインバーグ・ディザリング
フロイド-スタインバーグ・ディザリング(Floyd–Steinberg dithering)は画像用ディザリングアルゴリズムであり、1976年、ロバート・フロイドと Louis Steinberg が発表した。画像操作関係のソフトウェアで広く用いられており、例えば最大256色までしか使えないGIF形式への変換の際に使われています。
各ピクセルの量子化誤差をそれに隣接するピクセル群に拡散させることでディザリングを実現するアルゴリズムです。
隣接ピクセルへの誤差の分配は次のようになります。

星印 (*) が現在見ているピクセルを表しています。
このアルゴリズムでは、画像を左から右、上から下にスキャンし、ピクセルの値を1つずつ量子化していきます。
毎回の量子化誤差は隣接するピクセル群に分配されますが、既に量子化が済んだピクセルの値は変更しません。
これにより、あるピクセルの値が量子化によって切り下げられたら、次のピクセルにその誤差が反映されて切り上げられることになり、全体として量子化誤差がゼロに近づくことになります。
Ref.フロイド-スタインバーグ・ディザリング(ウィキペディア)
実際の処理はこんな感じです。Pixels[]が元画像、RGBs[]がK平均法を施した後の画像です。
for (y=0; y < img->Height; y++) {
for (x=0; x < img->Width; x++) {
pos = img->Width * y + x;
quantErrorR = img->Pixels[pos].R - img->RGBs[pos].R;
quantErrorG = img->Pixels[pos].G - img->RGBs[pos].G;
quantErrorB = img->Pixels[pos].B - img->RGBs[pos].B;
if ((x+1)Width) {
pos = img->Width * y + (x + 1);
tmpRGBs[pos].R = tmpRGBs[pos].R + quantErrorR * 7/16;
tmpRGBs[pos].G = tmpRGBs[pos].G + quantErrorG * 7/16;
tmpRGBs[pos].B = tmpRGBs[pos].B + quantErrorB * 7/16;
}
.........
このようがカラー減色画像にフロイド-スタインバーグ・ディザリングを施しても、誤差拡散をしきい値間隔により吸収されてしまい、変化はありませんでした。
■256色(245色)BMP画像ファイルの生成
最後に減色した画像をBMP画像ファイルとして保存します。
最初に元画像のBMPヘッダー情報を構造体に保存しておきます。
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;
ヘッダー情報の中で、変更が発生するのが赤字の部分です。
| BitsPerPixel | 24 → 8 |
| ColorsUsed | 0 → 245 ※(R)7×(G)7×(B)5 |
| ImageSize | Width×Height×3 → Width×Height |
| DataOffset | sizeof(BMP_FORMAT) → sizeof(BMP_FORMAT)+245×4 |
| FileSize | DataOffset + ImageSize |
なお、値をセットする際には、リトルエンディアンで格納します。
ヘッダー情報に続いて、カラーテーブル(パレット情報)を書き出します。
typedef struct {
uint8_t B;
uint8_t G;
uint8_t R;
uint8_t Reserved;
} PALETTE_INFO;
下記のようなRGBの組み合わせを使った場合
R:{15,58, 94,127,162,201,233}
G:{18,58, 92,127,165,196,230}
B:{29,76,129,180,226}
B,G,R,Reservedの順に、245色の組み合わせを書き出していきます。
(29,18,15,0),(76,18,15,0),.....
この書き出した順番がパレット番号、0,1,2...に対応します。
カラーテーブルに続いて画像データを登録していきます。
24ビット画像データのときは、B,G,Rの値を書き出しますが、8ビット画像の場合は対応するパレット番号を書き出します。
24bitと8bit BMP画像サイズを解像度 240x320 の場合で比較した表です。
| BMP | HEADER | ColorTable | ImageData | サイズ |
| 24bit | 54 | 0 | 230,400 | 230,454 |
| 8bit | 54 | 980 | 76,800 | 77,834 |
■ソースコード
#include <Arduino.h>
#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);
// THE BMP FILE FORMAT
typedef struct {
uint8_t Signature[2]; // 'BM'
uint8_t FileSize[4];
uint8_t reserved1[4]; // unused (=0)
uint8_t DataOffset[4];
uint8_t Size[4]; // Size of InfoHeader =40
uint8_t Width[4];
uint8_t Height[4];
uint8_t Planes[2]; // always 1
uint8_t BitsPerPixel[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;
typedef struct {
uint8_t R;
uint8_t G;
uint8_t B;
} PIXEL_INFO;
typedef struct {
uint8_t B;
uint8_t G;
uint8_t R;
uint8_t Reserved;
} PALETTE_INFO;
typedef struct {
uint32_t FileSize;
uint32_t DataOffset;
uint32_t Width;
uint32_t Height;
uint16_t BitsPerPixel;
uint32_t ImageSize;
uint32_t ColorsUsed;
BMP_FORMAT *Bmp;
PIXEL_INFO *Pixels;
PIXEL_INFO *RGBs;
} IMAGE_INFO;
uint16_t swap16(uint8_t *pt) {
uint16_t ret;
ret = *pt;
ret |= (*(pt+1)<<8)&0xff00;
return ret;
}
void short2byte(uint8_t *pt, uint16_t val) {
*pt = (uint8_t)val &0x00ff;
*(pt+1) = (uint8_t)((val>>8)&0x00ff);
}
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 long2byte(uint8_t *pt, uint32_t val) {
*pt = (uint8_t)val &0x000000ff;
*(pt+1) = (uint8_t)((val>> 8)&0x000000ff);
*(pt+2) = (uint8_t)((val>>16)&0x000000ff);
*(pt+3) = (uint8_t)((val>>24)&0x000000ff);
}
boolean BmpFile2Psram(IMAGE_INFO *img, const char *fname) {
File fileSD;
PIXEL_INFO *imgpt;
PALETTE_INFO *palettes;
uint32_t x,y;
uint8_t nBuf[16], paletteNo;
SDSPI.begin(SPI_sck, SPI_miso, SPI_mosi, -1);
pinMode(SPI_sd_ss, OUTPUT);
if (!SD.begin(SPI_sd_ss, SDSPI)) return false;
fileSD = SD.open(fname, FILE_READ);
if (!fileSD) return false;
fileSD.read((uint8_t*)img->Bmp,sizeof(BMP_FORMAT));
if (swap32(img->Bmp->Compression)!=0) return false;
img->FileSize = swap32(img->Bmp->FileSize);
img->DataOffset = swap32(img->Bmp->DataOffset);
img->Width = swap32(img->Bmp->Width);
img->Height = swap32(img->Bmp->Height);
img->BitsPerPixel = swap32(img->Bmp->BitsPerPixel);
img->ImageSize = swap32(img->Bmp->ImageSize);
img->ColorsUsed = swap32(img->Bmp->ColorsUsed);
img->Pixels = (PIXEL_INFO *)malloc(img->Width * img->Height * sizeof(PIXEL_INFO));
img->RGBs = (PIXEL_INFO *)malloc(img->Width * img->Height * sizeof(PIXEL_INFO));
if (img->Pixels) {
imgpt = img->Pixels;
if (img->BitsPerPixel==24) {
uint8_t mod = (img->Width % 4) * 3; // RGB情報は画像横一列が4byteの倍数でなければならないための補正
for(y=0; y < img->Height; y++){
for(x=0; x < img->Width; x++){
fileSD.read(nBuf,3);
imgpt->B = nBuf[0];
imgpt->G = nBuf[1];
imgpt->R = nBuf[2];
imgpt++;
}
if (mod>0) fileSD.read(nBuf,mod);
}
}
if (img->BitsPerPixel==8) {
uint8_t mod = img->Width % 4;
uint16_t paletteSize = img->ColorsUsed * sizeof(PALETTE_INFO);
palettes = (PALETTE_INFO *)malloc(paletteSize);
fileSD.read((uint8_t*)palettes, paletteSize);
for(y=0; y < img->Height; y++){
for(x=0; x < img->Width; x++){
fileSD.read(&paletteNo,1);
imgpt->B = palettes[paletteNo].B;
imgpt->G = palettes[paletteNo].G;
imgpt->R = palettes[paletteNo].R;
imgpt++;
}
if (mod>0) fileSD.read(nBuf,mod);
}
free(palettes);
}
memcpy(img->RGBs,img->Pixels,(img->Width * img->Height * sizeof(PIXEL_INFO)));
}
fileSD.close();
SD.end();
SDSPI.end();
return true;
}
uint8_t uqmR[7] = {18,54,90,126,162,198,234};
uint8_t uqmG[7] = {18,54,90,126,162,198,234};
uint8_t uqmB[5] = {24,76,128,180,232};
uint32_t uqmRsum[7] = {0,0,0,0,0,0,0};
uint32_t uqmGsum[7] = {0,0,0,0,0,0,0};
uint32_t uqmBsum[5] = {0,0,0,0,0};
uint32_t uqmRcnt[7] = {0,0,0,0,0,0,0};
uint32_t uqmGcnt[7] = {0,0,0,0,0,0,0};
uint32_t uqmBcnt[5] = {0,0,0,0,0};
uint8_t devide7R(uint8_t color) {
if (color <= (uqmR[0]+uqmR[1])/2 ) { uqmRsum[0]+=color; uqmRcnt[0]+=1; return uqmR[0]; }
else if(color <= (uqmR[1]+uqmR[2])/2 ) { uqmRsum[1]+=color; uqmRcnt[1]+=1; return uqmR[1]; }
else if(color <= (uqmR[2]+uqmR[3])/2 ) { uqmRsum[2]+=color; uqmRcnt[2]+=1; return uqmR[2]; }
else if(color <= (uqmR[3]+uqmR[4])/2 ) { uqmRsum[3]+=color; uqmRcnt[3]+=1; return uqmR[3]; }
else if(color <= (uqmR[4]+uqmR[5])/2 ) { uqmRsum[4]+=color; uqmRcnt[4]+=1; return uqmR[4]; }
else if(color <= (uqmR[5]+uqmR[6])/2 ) { uqmRsum[5]+=color; uqmRcnt[5]+=1; return uqmR[5]; }
else { uqmRsum[6]+=color; uqmRcnt[6]+=1; return uqmR[6]; };
}
uint8_t devide7G(uint8_t color) {
if (color <= (uqmG[0]+uqmG[1])/2 ) { uqmGsum[0]+=color; uqmGcnt[0]+=1; return uqmG[0]; }
else if(color <= (uqmG[1]+uqmG[2])/2 ) { uqmGsum[1]+=color; uqmGcnt[1]+=1; return uqmG[1]; }
else if(color <= (uqmG[2]+uqmG[3])/2 ) { uqmGsum[2]+=color; uqmGcnt[2]+=1; return uqmG[2]; }
else if(color <= (uqmG[3]+uqmG[4])/2 ) { uqmGsum[3]+=color; uqmGcnt[3]+=1; return uqmG[3]; }
else if(color <= (uqmG[4]+uqmG[5])/2 ) { uqmGsum[4]+=color; uqmGcnt[4]+=1; return uqmG[4]; }
else if(color <= (uqmG[5]+uqmG[6])/2 ) { uqmGsum[5]+=color; uqmGcnt[5]+=1; return uqmG[5]; }
else { uqmGsum[6]+=color; uqmGcnt[6]+=1; return uqmG[6]; };
}
uint8_t devide5B(uint8_t color) {
if (color <= (uqmB[0]+uqmB[1])/2 ) { uqmBsum[0]+=color; uqmBcnt[0]+=1; return uqmB[0]; }
else if(color <= (uqmB[1]+uqmB[2])/2 ) { uqmBsum[1]+=color; uqmBcnt[1]+=1; return uqmB[1]; }
else if(color <= (uqmB[2]+uqmB[3])/2 ) { uqmBsum[2]+=color; uqmBcnt[2]+=1; return uqmB[2]; }
else if(color <= (uqmB[3]+uqmB[4])/2 ) { uqmBsum[3]+=color; uqmBcnt[3]+=1; return uqmB[3]; }
else { uqmBsum[4]+=color; uqmBcnt[4]+=1; return uqmB[4]; };
}
void updateBlock() {
for (uint8_t i=0; i<7; i++) {
uqmR[i] = uqmRsum[i] / uqmRcnt[i];
uqmG[i] = uqmGsum[i] / uqmGcnt[i];
uqmRsum[i] = uqmRcnt[i] = 0;
uqmGsum[i] = uqmGcnt[i] = 0;
if (i<5) {
uqmB[i] = uqmBsum[i] / uqmBcnt[i];
uqmBsum[i] = uqmBcnt[i] = 0;
}
}
}
void uniformQuantize(IMAGE_INFO *img) {
uint32_t x,y;
PIXEL_INFO *imgpt;
memcpy(img->RGBs,img->Pixels,(img->Width * img->Height * sizeof(PIXEL_INFO)));
imgpt = img->RGBs;
for(y=0; y < img->Height; y++){
for(x=0; x < img->Width; x++){
imgpt->R = devide7R(imgpt->R);
imgpt->G = devide7G(imgpt->G);
imgpt->B = devide5B(imgpt->B);
imgpt++;
}
}
}
void draw565(IMAGE_INFO *img) {
int x,y;
uint16_t color;
PIXEL_INFO *imgpt = img->RGBs;
for(y=0; y < img->Height; y++){
for(x=img->Width-1; x>=0; x--) {
tft.drawPixel(x, y, tft.color565(imgpt->R,imgpt->G,imgpt->B));
imgpt++;
}
}
}
void bmp256toSd(IMAGE_INFO *img, const char *fileName) {
PIXEL_INFO *imgpt;
int32_t x,y;
SDSPI.begin(SPI_sck, SPI_miso, SPI_mosi, -1);
pinMode(SPI_sd_ss, OUTPUT);
if (!SD.begin(SPI_sd_ss, SDSPI)) return;
if (SD.exists(fileName)) SD.remove(fileName);
File fileSD = SD.open(fileName, "wb");
if (!fileSD) return;
img->ColorsUsed = 7 * 7 * 5;
img->ImageSize = img->Width * img->Height;
uint16_t paletteSize = 7 * 7 * 5 * sizeof(PALETTE_INFO);
img->DataOffset = sizeof(BMP_FORMAT) + paletteSize;
img->FileSize = img->DataOffset + img->ImageSize;
short2byte(img->Bmp->BitsPerPixel,img->BitsPerPixel);
long2byte(img->Bmp->ColorsUsed, img->ColorsUsed);
long2byte(img->Bmp->ImageSize, img->ImageSize);
long2byte(img->Bmp->DataOffset, img->DataOffset);
long2byte(img->Bmp->FileSize, img->FileSize);
fileSD.write((uint8_t*)img->Bmp, sizeof(BMP_FORMAT));
uint8_t reserved = 0x00;
uint8_t iR, iG, iB, paletteNo;
for (iR=0; iR < 7; ++iR) {
for (iG=0; iG < 7; ++iG) {
for (uint8_t iB=0; iB < 5; ++iB) {
fileSD.write(&uqmB[iB], 1);
fileSD.write(&uqmG[iG], 1);
fileSD.write(&uqmR[iR], 1);
fileSD.write(&reserved,1);
}
}
}
uint8_t mod = (img->Width % 4) * 3;
imgpt = img->RGBs;
for(y=0; y<img->Height; y++){
for(x=0; x<img->Width; x++){
for (iR=0; iR<7; iR++) if (imgpt->R==uqmR[iR]) break;
for (iG=0; iG<7; iG++) if (imgpt->G==uqmG[iG]) break;
for (iB=0; iB<5; iB++) if (imgpt->B==uqmB[iB]) break;
paletteNo = 35 * iR + 5 * iG + iB;
fileSD.write(&paletteNo, 1);
imgpt++;
}
if (mod>0) fileSD.write(0x00,mod);
}
fileSD.close();
SD.end();
SDSPI.end();
}
void setup() {
char sampleFile[] = "/nostalgic_eye.bmp";
IMAGE_INFO *img = (IMAGE_INFO *)malloc(sizeof(IMAGE_INFO));
img->Bmp = (BMP_FORMAT *)malloc(sizeof(BMP_FORMAT));
if(!BmpFile2Psram(img,sampleFile)) return;
if (true) {
ledcSetup(0,12800,8); // channel,周波数,解像度(8bit=256)
ledcAttachPin(SPI_tft_bl,0); // ピンをチャンネルに接続
ledcWrite(0,64);
tft.begin();
tft.setRotation(2);
tft.fillScreen(ILI9341_BLACK);
}
draw565(img);
uniformQuantize(img);
tft.fillScreen(ILI9341_BLACK);
draw565(img);
updateBlock();
uniformQuantize(img);
tft.fillScreen(ILI9341_BLACK);
draw565(img);
updateBlock();
uniformQuantize(img);
tft.fillScreen(ILI9341_BLACK);
draw565(img);
bmp256toSd(img,"/uqm_kmeans.bmp");
free(img->Bmp);
free(img->Pixels);
free(img->RGBs);
free(img);
}
void loop() {}
■参考文献
・24bit → 8bit 減色
・減色アルゴリズム[量子化/メディアンカット/k平均法]
・画像のデータはどの様に保存されているの?
|
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)
|