PIL(pillow)のデータ構造ってどうなっている?

読み込んだデータは、どうなっている?

画像読み込み

from PIL import Image
im = Image.open('sample20x15.bmp')
im

f:id:chiyoh:20190503233653p:plain

print("画像モード = ",im.mode)
print("画像サイズ(x,y) = ",im.size)
print("pixel格納形式 = ",im.getbands())
print("pixel(0,0) = ",im.getpixel((0,0)))
print("pixel(0,0)の形式 = ",type(im.getpixel((0,0))))
print("各チャンネルの形式 = ",type(im.getpixel((0,0))[0]),type(im.getpixel((0,0))[1]),type(im.getpixel((0,0))[2]))
画像モード =  RGB
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('R', 'G', 'B')
pixel(0,0) =  (200, 36, 179)
pixel(0,0)の形式 =  <class 'tuple'>
各チャンネルの形式 =  <class 'int'> <class 'int'> <class 'int'>

なるほど、なるほど。なら書き込みは

im.putpixel((0,0),(255,255,255))
print("[1] pixel(0,0) = ",im.getpixel((0,0)))
im.putpixel((0,0),(256,256,256))
print("[2] pixel(0,0) = ",im.getpixel((0,0)))
im.putpixel((0,0),(1000,-1000,0))
print("[3] pixel(0,0) = ",im.getpixel((0,0)))
[1] pixel(0,0) =  (255, 255, 255)
[2] pixel(0,0) =  (255, 255, 255)
[3] pixel(0,0) =  (255, 0, 0)

データ形式がintとなっていたので意地悪して入力 各チャンネルR,G,Bは0~255までのuint8の範囲でそれ以外はmin,maxにまとまるという感じか

im.putpixel((20,15),(215,215,215))
print("[4] pixel(-1,0) = ",im.getpixel((20,15)))
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-4-b38936316c41> in <module>
----> 1 im.putpixel((20,15),(215,215,215))
      2 print("[4] pixel(-1,0) = ",im.getpixel((20,15)))


h:\Anaconda3\lib\site-packages\PIL\Image.py in putpixel(self, xy, value)
   1681             # RGB or RGBA value for a P image
   1682             value = self.palette.getcolor(value)
-> 1683         return self.im.putpixel(xy, value)
   1684 
   1685     def remap_palette(self, dest_map, source_palette=None):


IndexError: image index out of range

画像サイズが20x15なので(0~19,0~14)が範囲なのは分かる。よって(20,15)はErrorか。rangeエラーなのね

im.putpixel((-1,0),(111,111,111))
print("[5] pixel(-1,0) = ",im.getpixel((-1,0)))
[5] pixel(-1,0) =  (111, 111, 111)

(-1,0)ってどこ?

print("[1] pixel(0,0) = ",im.getpixel((0,0)))
print("[2] pixel(19,0) = ",im.getpixel((19,0)))
print("[3] pixel(0,14) = ",im.getpixel((0,14)))
print("[4] pixel(19,14) = ",im.getpixel((19,14)))
[1] pixel(0,0) =  (255, 0, 0)
[2] pixel(19,0) =  (111, 111, 111)
[3] pixel(0,14) =  (147, 42, 197)
[4] pixel(19,14) =  (49, 53, 230)

なるほど、Pythonの配列みたいにxy[-1][0]がxy[19][0]という感じか

格納形式は?

print(type(im.tobytes()))
print(im.tobytes())
<class 'bytes'>
b"\xff\x00\x00\xc5%\xb5\xc0%\xb6\xbc%\xb7\xb8&\xb9\xb3&\xbb\xad'\xbc\xa9(\xbe\xa4(\xbf\x9e)\xc2\x99)\xc4\x94*\xc5\x8e+\xc7\x89+\xc9\x84,\xcb~,\xccx-\xcfs.\xd0n.\xd2ooo\xc5$\xb4\xc1$\xb6\xbd%\xb7\xb8&\xb9\xb3'\xba\xae'\xbb\xa9(\xbe\xa4(\xbf\x9f(\xc1\x9a)\xc3\x94*\xc5\x90*\xc7\x8a+\xc8\x85+\xca\x7f,\xcdz-\xcet-\xd0n.\xd2i/\xd4c/\xd6\xc2$\xb5\xbe%\xb6\xb9&\xb8\xb5&\xba\xaf'\xbc\xaa(\xbd\xa6(\xbf\xa1(\xc1\x9c)\xc2\x96*\xc4\x90+\xc6\x8b+\xc8\x86+\xca\x80,\xcc{-\xceu-\xd0p.\xd1j.\xd4e/\xd5_0\xd7\xbe%\xb6\xba%\xb8\xb5&\xba\xb1'\xbb\xac'\xbc\xa7'\xbf\xa2)\xc0\x9c)\xc2\x97*\xc4\x91*\xc6\x8c*\xc8\x86+\xca\x81,\xcc|,\xcdv-\xcfq.\xd2k.\xd3f/\xd5a0\xd7\\0\xd8\xbb%\xb8\xb6&\xba\xb2'\xbb\xac'\xbd\xa8'\xbe\xa3(\xc0\x9d(\xc2\x98)\xc3\x92*\xc5\x8e+\xc7\x87+\xc9\x83,\xcb}-\xcdw.\xcfr-\xd0l.\xd3h/\xd5a0\xd6]1\xd8W1\xda\xb7&\xb9\xb3'\xbb\xad&\xbc\xa9'\xbe\xa4(\xc0\x9e(\xc1\x99*\xc3\x93*\xc5\x8f*\xc7\x89+\xc9\x83,\xcb~,\xccy-\xcfs.\xd0n/\xd2h.\xd4b/\xd6^0\xd7X0\xd9S1\xdb\xb4&\xba\xaf'\xbc\xaa'\xbe\xa4(\xbf\xa0)\xc2\x9a)\xc3\x95*\xc5\x8f+\xc6\x8a+\xc9\x85,\xca\x7f,\xccz-\xcet-\xd0o.\xd2j/\xd3d/\xd5_0\xd7Y0\xdaT1\xdbO2\xdd\xb0'\xbb\xab'\xbd\xa5'\xbf\xa0)\xc0\x9c)\xc2\x96)\xc4\x90*\xc6\x8c*\xc8\x85+\xca\x80,\xcc{-\xceu-\xcfp.\xd1k/\xd3e0\xd5_0\xd7[0\xd9U1\xdaP1\xddK2\xde\xab'\xbd\xa6(\xbf\xa2(\xc0\x9d)\xc2\x97)\xc4\x92*\xc6\x8c+\xc8\x87+\xc9\x82,\xcc|,\xcdw-\xd0q.\xd1l/\xd3f/\xd5a0\xd7\\1\xd9W1\xdaQ2\xdcL2\xdeG3\xdf\xa8(\xbe\xa3)\xc0\x9e)\xc2\x99)\xc4\x92*\xc6\x8d+\xc8\x88+\xc9\x82,\xcb},\xcdx-\xcfr-\xd0m.\xd3g/\xd4b/\xd6]0\xd8X1\xd9R1\xdcM2\xddH2\xdfC3\xe1\xa4(\xbf\x9e(\xc1\x99*\xc3\x94*\xc6\x8f+\xc6\x89+\xc9\x84,\xca\x7f,\xcdy-\xces.\xd0n.\xd2h/\xd4c/\xd6^0\xd8X0\xd9S2\xdbN2\xddI2\xdfD3\xe0?3\xe3\x9f(\xc1\x9a)\xc3\x95*\xc5\x90+\xc7\x8b*\xc9\x85+\xca\x7f,\xccz-\xceu.\xd0o.\xd2j.\xd4d/\xd6_0\xd7Z0\xd9T1\xdbO2\xdcJ2\xdfE2\xe0A3\xe2<4\xe3\x9c)\xc2\x96*\xc4\x91*\xc6\x8c+\xc8\x86+\xca\x81,\xcc{-\xceu-\xcfq.\xd2j/\xd3f/\xd5`0\xd7Z1\xd8U1\xdaP2\xdcK2\xdeF3\xe0B3\xe1=4\xe384\xe5\x97)\xc4\x92*\xc6\x8d+\xc7\x87,\xca\x81,\xcb|-\xcdw-\xcfq.\xd1k/\xd3f/\xd5a/\xd7\\0\xd8V1\xdbR1\xdcL2\xdeG2\xdfB3\xe1>4\xe394\xe455\xe5\x93*\xc5\x8e+\xc7\x89,\xc9\x82,\xca},\xcdx-\xcer.\xd0m.\xd2h/\xd4b/\xd6]0\xd8X0\xdaR1\xdcN1\xddI2\xdfC3\xe1?3\xe3;4\xe465\xe615\xe6"

分からんので、cls

im = Image.new('RGB',(20,15),color=(0,0,0))
print(im.tobytes().hex())
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

ピクセルにでーたを書き込む

im.putpixel((0,0),(1,2,3)) # 左上(0,0)
im.putpixel((19,14),(253,254,255)) # 右下(19,14)
im.putpixel((1,0),(4,5,6)) # (0,1) x=1
im.putpixel((0,1),(7,8,9)) # (1,0) y=1
print(im.tobytes().hex())
010203040506000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070809000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdfeff

なるほどR(0,0),G(0,0),B(0,0),R(1,0),G(1,0),B(1,0),.......R(0,1),G(0,1),B(0,1), ............. R(19,14),G(19,14),B(19,14) の順なのね

print('データのサイズ = ',len(im.tobytes()))
データのサイズ =  900

R,G,B=3 20x15 で3x20x15=900か
ならば

def getBytes2pixel(im,xy):
    x,y = xy
    w,h = im.size
    dat = im.tobytes()
    print(xy," = ",dat[3*(w*y+x)],dat[3*(w*y+x)+1],dat[3*(w*y+x)+2])

getBytes2pixel(im,(0,0))
getBytes2pixel(im,(19,14))
getBytes2pixel(im,(1,0))
getBytes2pixel(im,(0,1))
(0, 0)  =  1 2 3
(19, 14)  =  253 254 255
(1, 0)  =  4 5 6
(0, 1)  =  7 8 9

まとめると

スクリーンXY座標(左上原点)で(0,0)(1,0)(2,0)(3,0)...(19,0)(0,1)(1,1)(2,1).....(19,13)(19,14) の順に並んでいて、アクセスはaddress = 3*(w*y+1)R=address[0], G=address[1], B=address[2] な感じなのね

パレットカラーだと

p_im = im.quantize() # 256色インデックスカラー化
print("画像モード = ",p_im.mode)
print("画像サイズ(x,y) = ",p_im.size)
print("pixel格納形式 = ",p_im.getbands())
print("データサイス = ",len(p_im.tobytes()))
print(p_im.tobytes().hex())
画像モード =  P
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('P',)
データサイス =  300
030204040404040404040404040404040404040401040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040400

P*h*wなので1*20*15=300ピクセルの並びは同じ

PIL(pillow)の使い方(TIFF読み書き)

TIFFでマルチページ保存する

いろいろとエラーにあってうまくいってなかったが解決した。そもそも、サンプルのTIFFPhotoshopで作りマルチページにするという 簡単な確認を行うだけであったがいろいろとエラーにぶつかっていた

TiffImagePluginについて

PILは、画像フォーマット毎にプラグイン形式をとっていてTIFF関係はTiffImagePlugin.pyが担当している 読み込みは単純にImage.open(ファイル名)で画像オブジェクトを作ってくれる。あとは他と一緒。 書き込みに関しては単体ならImage.save(ファイル名)でよい。圧縮形式を使いたかったら‘compression‘を追加する
マルチページの引数は下記の通りになる

Image.save("out.tif", compression='tiff_lzw',save_all=True, append_images=image_list[1:])

  • "out.tif" 出力するファイル名
  • compression='tiff_lzw' 圧縮形式
  • save_all=True 画像自身のマルチページも含めて全部保存
  • append_images=image_list[1:] 追加画像 追加なの初めの画像はインスタンス化した画像リストは2番目から指定してみた

当たり前だが、格納はインスタンスの画像、追加リストの頭から順番に格納される

エラーになった原因は、一部のTAG情報が未対応のため 主に、Photoshopのほうで独自に追加したTAGが原因だった。TAG読み込みは、問題ないが書き込みの際にエラーを出す 対応してないTAGを消していくことで何とか保存できた。

number tag name
34377 PhotoshopInfo
34665 ExifIFD
700 XMP

from PIL import Image, TiffImagePlugin

im0 = Image.open("f0.tif")
im1 = Image.open("f1.tif")
im2 = Image.open("f2.tif")
im3 = Image.open("f3.tif")

image_list = [im0,im1,im2,im3]

for obj in image_list :
    del obj.tag_v2[34377]
    del obj.tag_v2[34665]
    del obj.tag_v2[700]
im0.save("out.tif", compression='tiff_lzw',save_all=True, append_images=image_list[1:])

4ページの画像がつくられたことがわかる

im_mp = Image.open('out.tif')
for i in range(im_mp.n_frames):
    im_mp.seek(i)
    print(im_mp.tell()+1,"/",im_mp.n_frames,"pages",im_mp.tag_v2[306])
1 / 4 pages 2019:04:30 11:03:29
2 / 4 pages 2019:04:30 11:01:13
3 / 4 pages 2019:04:30 11:02:13
4 / 4 pages 2019:04:30 11:02:47

インスタンス画像4ページに新たに4ページ追加して合計8ページが作られた

im_mp.save("out2.tif", compression='tiff_lzw',save_all=True, append_images=image_list)
im_mp2 = Image.open('out2.tif')
for i in range(im_mp2.n_frames):
    im_mp2.seek(i)
    print(im_mp2.tell()+1,"/",im_mp2.n_frames,"pages",im_mp2.tag_v2[306])
1 / 8 pages 2019:04:30 11:03:29
2 / 8 pages 2019:04:30 11:01:13
3 / 8 pages 2019:04:30 11:02:13
4 / 8 pages 2019:04:30 11:02:47
5 / 8 pages 2019:04:30 11:03:29
6 / 8 pages 2019:04:30 11:01:13
7 / 8 pages 2019:04:30 11:02:13
8 / 8 pages 2019:04:30 11:02:47

対応している圧縮形式

圧縮形式を使うときはlibtiff が必要

print("認識している libtiff version = ",TiffImagePlugin._libtiff_version())
print("定義されている圧縮形式")
for k in TiffImagePlugin.COMPRESSION_INFO.keys():
    print(k,TiffImagePlugin.COMPRESSION_INFO[k])
認識している libtiff version =  4.0.10
定義されている圧縮形式
1 raw
2 tiff_ccitt
3 group3
4 group4
5 tiff_lzw
6 tiff_jpeg
7 jpeg
8 tiff_adobe_deflate
32771 tiff_raw_16
32773 packbits
32809 tiff_thunderscan
32946 tiff_deflate
34676 tiff_sgilog
34677 tiff_sgilog24

使用していたPhotoshopが吐き出したTAGデータ

from PIL import TiffTags
im = Image.open("f0.tif")
im_tag_v2 = im.tag_v2
for k in im_tag_v2.keys():
    print(k,TiffTags.TAGS_V2[k][1])
256 ImageWidth
257 ImageLength
258 BitsPerSample
259 Compression
262 PhotometricInterpretation
34377 PhotoshopInfo
273 StripOffsets
274 Orientation
277 SamplesPerPixel
278 RowsPerStrip
279 StripByteCounts
282 XResolution
283 YResolution
284 PlanarConfiguration
296 ResolutionUnit
34665 ExifIFD
305 Software
306 DateTime
34675 ICCProfile
700 XMP
317 Predictor
254 NewSubfileType

分かってしまえば簡単だが一から調べるのは時間がかかった。

PIL(pillow)のインデックスカラーのあれこれ

PIL(pillow)のインデックスカラーのあれこれ

サンプルのJpegデータを読み込む

from PIL import Image, ImageDraw, ImageFont
imo = Image.open('jpeg/IMG_2952x.jpg')
print("mode = ",imo.mode)
print("size = ",imo.size)
mode =  RGB
size =  (640, 480)

jpeg画像フルカラーからパレットカラー(256色)に減色

im256 = imo.quantize() #減色 index color変換
print("mode = ",im256.mode)
print("size = ",im256.size)
im256.palette.save('palette.dat')
im256
mode =  P
size =  (640, 480)

Markdown記述だが、4スペースCODE識別の時は、後方のイメージが変換できなかったが ```記述に変更したら、画像が読み込めるようになった。単純にBUGかな?


f:id:chiyoh:20190503004617p:plain


もちろん、256色モードがあるTIFFとGIFに変換もできる

im256.save('im256.tif')
im256.save('im256.gif')

TIFFデータに変換したデータを読み込み、各データを見ていく PILライブラリのカラーパレットとTIFFのTAGに入っているパレットformatが違うので注意

from PIL import Image, ImageDraw, ImageFont
im256_tiff = Image.open('im256.tif')
im256_tiff
print("mode = ",im256_tiff.mode) # P
print("size = ",im256_tiff.size) # P
print("getbands = ",im256_tiff.getbands()) # ('P',)
print("getpixel(0,0) = ",im256_tiff.getpixel((0,0))) # 2 index 値
#print("getpalette = ",im256_tiff.getpalette()) # 2 index 値
palette = im256_tiff.getpalette()
i=0
print("colorpalette[0]= (",palette[i*3+0],palette[i*3+1],palette[i*3+2],")")
i=2
print("colorpalette[2]= (",palette[i*3+0],palette[i*3+1],palette[i*3+2],")")
i=32
print("colorpalette[32]= (",palette[i*3+0],palette[i*3+1],palette[i*3+2],")")
i=255
print("colorpalette[255]= (",palette[i*3+0],palette[i*3+1],palette[i*3+2],")")


print("TAG data:")
print("ImageWidth = " , im256_tiff.tag_v2[256]) # 8
print("ImageLength = " , im256_tiff.tag_v2[257]) # 8
print("BitsPerSample = " , im256_tiff.tag_v2[258]) # 8
# color map は、2 ** BitsPerSampleなので 256、RGB=3なので3*256データ
# データ長はshortで16bit 0~65535、(0,0,0)~(65535,65535,65535)
# しかし現在のRGBカラーは~(255,255,255)までで256で割らないと同じ値にならない
# また、R0~R256,G0~G255,B0~B255の順に並んでいるのでgetpellteとは違うフォーマット
print(type(im256_tiff.tag_v2[320][0]),len(im256_tiff.tag_v2[320])) # int
tag_palette = im256_tiff.tag_v2[320]
i=0
print("colorpalette[0]= (",tag_palette[0+i]/256,tag_palette[256+i]/256,tag_palette[512+i]/256,")")
i=32
print("colorpalette[32]= (",tag_palette[0+i]/256,tag_palette[256+i]/256,tag_palette[512+i]/256,")")
i=255
print("colorpalette[255]= (",tag_palette[0+i]/256,tag_palette[256+i]/256,tag_palette[512+i]/256,")")
mode =  P
size =  (640, 480)
getbands =  ('P',)
getpixel(0,0) =  2
colorpalette[0]= ( 253 253 253 )
colorpalette[2]= ( 249 249 250 )
colorpalette[32]= ( 228 230 228 )
colorpalette[255]= ( 4 4 7 )
TAG data:
ImageWidth =  640
ImageLength =  480
BitsPerSample =  (8,)
<class 'int'> 768
colorpalette[0]= ( 253.0 253.0 253.0 )
colorpalette[32]= ( 228.0 230.0 228.0 )
colorpalette[255]= ( 4.0 4.0 7.0 )

実際のパレットマップはこんな感じ

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

def draw_pcolor_map(pcm):

    row,col,c_dx,c_dy,s_dx,y_osf = 32,8,16,16,16*8,24

    im = Image.new("RGB", (col*(c_dx+s_dx), c_dy*row+y_osf),color=(255,255,255))
    font_ttf = "C:/Windows/Fonts/msgothic.ttc" #MS ゴシック 他のOSの場合は適当にfont ファイルに変える
    draw = ImageDraw.Draw(im)

    draw.font = ImageFont.truetype(font_ttf, 24) # font size
    draw.text([0,0], "Index color map",(0,0,0))

    draw.font = ImageFont.truetype(font_ttf, 14) # font size

    for x in range(0,col):
        for num in range(0,row):
            # color box
            ix = row*x+num
            pcolor = (pcm[ix*3],pcm[ix*3+1],pcm[ix*3+2])
            draw.rectangle([(x*(c_dx+s_dx)+1,num*c_dy+1+y_osf),
                            (x*(c_dx+s_dx)+c_dx-1,(num+1)*c_dy-1-1+y_osf)],
                           outline=(0,0,0),
                           fill=pcolor)
            # number
            str_color = '{0:>3}:({1:>3},{2:>3},{3:>3})'.format(ix,pcolor[0],pcolor[1],pcolor[2]) 
            #print(str_color) # debug
            draw.text([x*(c_dx+s_dx)+c_dx+4,num*c_dy+y_osf], str_color,(0,0,0))

    return im

if __name__ == '__main__' :
    imo = Image.open('jpeg/IMG_2952x.jpg')
    im256 = imo.quantize() #減色 index color変換
    pcm = im256.getpalette()
    im = draw_pcolor_map(pcm)
    im.show()
pcm = im256.getpalette()
im = draw_pcolor_map(pcm)
im

f:id:chiyoh:20190503004624p:plain

Jpeg画像をEXIFの位置に合わせて保存する

PIL(pillow)ライブラリでjpegの表示向きとデータ方向を合わせる

前置き

携帯電話等で撮影した電子データを表示するとき、向きがあってなくて困ることがあります。専用のビューアソフトを使えば向きを合わせてくれることもあります。しかし、対応してない場合撮影した写真が縦長なのか横長なのか判別してくれません。そもそも、撮影機器自身はイメージセンサの並びで決まった方向にしか出力しません。試しに、携帯電話(カメラ)を縦にして撮影、横にして撮影、寝転がって天井にを横になって撮影、縦に撮影すればビューアソフトが最近はやりのAIを使って画像識別を行って縦横判定してないことがわかります(最新機器は知りませんが)。たいていの場合手振れ防止等でも使用しているジャイロセンサを使って機器が横に向いているのか縦に向いているのかを判断してその情報をEXIFメタデータとしてjpegファイル等に書き出しているにすぎないのです(だったら、水平角データを記録してくれれば連続撮影の時にマッチングしやすいのにね)。

内容

pythonのPILライブラリを使ってjpegEXIFデータを読み取り、jpeg画像の方向を合わせ保存する

コマンド1: exifデータに合わせて画像を変換する

python jpeg_conv.py input.jpg output.jpg

コマンド2: 画像をcommandに合わせて変換する

python jpeg_conv.py input.jpg output.jpg [1-8]

コマンド番号

  1. オリジナル
  2. 左右反転
  3. 180度回転
  4. 上下反転
  5. SCREEN座標(左上原点)で縦横入れ替え
  6. 90度回転
  7. XY座標でXY入れ替え
  8. 270度回転
# -*- coding: utf-8 -*-
"""
Created on Thu May  2 15:55:03 2019
@author: cyiyoh
"""
import sys
from PIL import Image
def exif_orientation(im,param, op=False):
    """
    im : イメージオブジェクト
    param : orientationのパラメータ
    """
    orient = {1:None,  # 360度回転(オリジナルなまま)
              2:Image.FLIP_LEFT_RIGHT,  # 左右反転、X軸反転
              3:Image.ROTATE_180, # 180度回転、X軸反転+Y軸反転
              4:Image.FLIP_TOP_BOTTOM, # 上下反転、Y軸反転
              5:Image.TRANSPOSE, # SCREEN座標で縦横入れ替え
              6:Image.ROTATE_90, # 90度回転
              7:Image.TRANSVERSE, # XY座標でXY入れ替え 45度(Y=X ラインで対称)
              8:Image.ROTATE_270 } # 270度回転
    # 状態を元に戻す
    conv = {1:1, # 何もしない
            2:2, # 左右反転
            3:3, # 180度回転
            4:4, # 上下反転
            5:5, # SCREEN座標で縦横入れ替え
            6:8, # 90°進んでいるので -90°進めて戻す
            7:7, # XY座標でXY入れ替え
            8:6 } # -90°進んでいるので 90°進めて戻す

    if param == 1 :
        return im.copy()

    elif op == True :
        return im.transpose(orient[param])

    else:
        return im.transpose(orient[conv[param]])

if __name__ == '__main__' :
    """
    python jpeg_conv input.jpg output.jpg
    exifデータは方向変換したときに抜け落ちるがそれはそれで問題なし
    """
    print(len(sys.argv))
    print(sys.argv)
    if len(sys.argv) ==1 :
        print("Convert using exif")
        print("   uage:python jpeg_conv.py input.jpg output.jpg")
        print("Convert using command")
        print("   uage:python jpeg_conv.py input.jpg output.jpg command")
        print("command")
        print(''' 1 :None 360度回転(オリジナルなまま)
 2 :Image.FLIP_LEFT_RIGHT 左右反転、X軸反転
 3 :Image.ROTATE_180 180度回転、X軸反転+Y軸反転
 4 :Image.FLIP_TOP_BOTTOM 上下反転、Y軸反転
 5 :Image.TRANSPOSE SCREEN座標で縦横入れ替え
 6 :Image.ROTATE_90 90度回転
 7 :Image.TRANSVERSE XY座標でXY入れ替え 45度(Y=X ラインで対称)
 8 :Image.ROTATE_270 270度回転''')
    else:
        im = Image.open(sys.argv[1])
        if len(sys.argv) > 3 :
            im_out = exif_orientation(im,int(sys.argv[3]),op=True)
        else:
            im_out = exif_orientation(im,im._getexif()[274])
        im_out.save(sys.argv[2])

簡単にできるんだね

(はてなブログ用)Markdown についてかき集めてみた

メモ Markdownの書き方

目次



デザインとMarkdownの関係

 Markdownではないが、はてなブログでは、いろいろなデザインから好きなものを選べる。HTMLやら、CSSやらでいろいろと定義されている。Markdown記法で書かれたマークをHTMLのTAGに変換しているに過ぎない。よって、デザインのほうでヘッダのH1,H2などをどのように表記するかが書かれているのでMarkdownでいい感じに編集できたとしても、デザインを変えてしまうと見た目が大幅に変わってしまうので注意が必要である

TEXT(編集のテキストボックス)の改行

 改行の役割1を考えて。改行(Enter)キーを押すと。編集画面が見やすい程度。表示では、文と文のつなぎ目でスペースと同じ役割に置き換わる。  

1234567890↲
abcdefg↲
ABCDEFG↲
あいうえお↲

1234567890 abcdefg ABCDEFG あいうえお


うーーん2

スペース(スペースを1個以上続ける)

 文字と文字の間のスペースの事、これも改行と同じで日本人としてはスペースによって前の行と位置合わせにしてしまうところがある。文章整形プロセッサとしては、位置合わせという機能があり左詰め、右詰め、中央合わせ、等間隔など。改行と同じでMarkdownでもスペースが連続できても1つのスペースとしてしょりを行う。しかし、悪賢い大抵の人は「 」全角スペースで代用してしまうだろう。

あ~~~ い~~~ う~~~ え~~~ お~~~
あ い う え お
あ  い  う  え  お
あ            い            う            え            お

あ~~~ い~~~ う~~~ え~~~ お~~~
あ い う え お
あ い う え お
あ い う え お


段落(空白行を追加すると段落になる)

段落…文章の区切り

 段落、改行を2回以上押す(連打!)。改行を多く打っても結果は変わらない。空白行が出来ればよい。スペースや、" "(全角スーペース)のみで構成された行は見た目おなじだが段落にはならない文と文の間のスペースとして足される。仕様としては、スペース、タブだけの行ということになっているみたい

1234567890 abcdefg ABCDEFG↲
1234567890 abcdefg ABCDEFG↲
↲
1234567890 abcdefg ABCDEFG↲

1234567890 abcdefg ABCDEFG 1234567890 abcdefg ABCDEFG

1234567890 abcdefg ABCDEFG


1234567890↲
↲  1改行
abcdefg↲
↲
↲  2改行
ABCDEFG↲
↲
↲
↲  3改行
あいうえお↲

1234567890

abcdefg

ABCDEFG

あいうえお


強制改行(改行前にスペース2つ)

 改行に関しては、先に書いた通り。文章の終わりに改行を入れなくても必要なところには改行が入る仕組みになっている。しかし、台本のセリフのように短文で改行したい場合もある。  文の終わりに半角スペース2文字入れることで強制改行できる。

あいうえお  ↲  (半角スペース2つ+Enter)
かきくけこ  ↲  (半角スペース2つ+Enter)
さしすせそ  ↲  (半角スペース2つ+Enter)

あいうえお
かきくけこ
さしすせそ


ヘッダ(H1~H6) (章、レベルなど) (#~######まで)

 HTMLの<H1>~<h6>に相当。#とスペースをいれることで識別。また、- =などを1個以上いれることでみためヘッダぽいのでヘッダとして認識する仕様になっている

# H1
## H2
### H3
#### H4
##### H5
###### H6

ヘッダー1
=========

ヘッダー2
--------

H1

H2

H3

H4

H5
H6

ヘッダー1

ヘッダー2


引用(ある意味インデント) (>を1つ以上付ける)

 電子メールの>スタイルの引用が使える。メールのリプライがかさなるとインデントやマークが付くイメージ。段落の終わりが来るまでが有効範囲

aaaaaaaaaaaa  
> aaaaaaaaaaaa  
aaaaaaaaaaaa  
aaaaaaaaaaaa  
>> bbbbbbbbbbbb    
bbbbbbbbbbbb  
bbbbbbbbbbbb  
bbbbbbbbbbbb  
>>> cccccccccccccc   
>>>> AAAAA
>>>>>bbbbbbbbbbbb  
>>>>>>bbbbbbbbbbbb  
>>>>>>>bbbbbbbbbbbb  
>>>> ああああ
>>>  ああああ
>> ああああ
> ああああ
> ああああ
ああああ

aaaaaaaaaaaa

aaaaaaaaaaaa
aaaaaaaaaaaa
aaaaaaaaaaaa

bbbbbbbbbbbb
bbbbbbbbbbbb
bbbbbbbbbbbb
bbbbbbbbbbbb

cccccccccccccc

AAAAA

bbbbbbbbbbbb

bbbbbbbbbbbb

bbbbbbbbbbbb
ああああ ああああ ああああ ああああ ああああ ああああ


線(--- *** ___など1行で)

 区切り線を入れる。合計3つ以上

---
- - -
***
___




コード1(等幅フォント) (‘(バッククォートで囲む。))

 バッククォート、「’」クォート「‘」バッククォート。一般的な、PCのキーボードで7+シフト@+シフトの違いがあるので気を付ける必要あり。

たとえば、 `range(1,10,3)` は1,4,7のイタレーションが発生する

たとえば、 range(1,10,3) は1,4,7のイタレーションが発生する


コード2(コードを埋め込む) (‘‘‘(バッククォート3つで囲む。))

 ```の後ろに言語名を指定することもできる。

 ```python
 import numpy as np
 for i in range(1,10,2):
     print(i)
 ```

import numpy as np
for i in range(1,10,2):
    print(i)

help.hatenablog.com

コード3(その他) (「&」「<」「>」やスペース)

 バッククォート内では、「&」「<」「>」を使っていてもHTMLのコードの一部と認識られずそのまま表示できる。また、先頭からスペース4つやタブ1のインデントされたコードを差し込むことができる

&:例 —
<>:例


&:例 &mdash; バッククオートで囲んでいるので&表現のHTML表記もキャンセルされる
<>:例 <blank> バッククオートで囲んでいるので<>表現のHTML表記もキャンセルされる


import numpy as np
a = np.array(range(12)).reshape(3,4)
print(a)
    [[ 0  1  2  3]      (先頭にスペース4つ)
     [ 4  5  6  7]      (先頭にスペース4つ)
     [ 8  9 10 11]]   (先頭にスペース4つ)

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

箇条書き(リスト) (「*」「+」「-」+スペースのマークを付ける)

 箇条書き、「*」「+」「-」とスペースで区切ることで箇条書きになる改行し、同じインデントでマークを付けることで連続した箇条書きとして認識する。またスペースを2つ(はてなブログ仕様?)以上付けてインデントすることでレベル分けしたマークが付く。この辺はWord等と同じ仕様。また、数値+ピリオド(.)1. 2. 3.などにすると数値の箇条書きになる(はてなブログはならない?)
 Markdownの仕様では、スペース4つかタブを頭に追加することでレベルを下げた箇条書きになることになっている。

- aaaaa
  - bbbb
    - ccccc
    - ccccc
  - AAAA
  - BBBB
- あああ

  • aaaaa
    • bbbb
      • ccccc
      • ccccc
    • AAAA
    • BBBB
  • あああ

* aaaaa
  * bbbb
    * ccccc
       * dddd

+ aaa
+ bbb
+ ccc

1. aaa
2. bbb
3. ccc

  • aaaaa

    • bbbb
      • ccccc
        • dddd
  • AAA

  • BBB
  • CCC

  • あああ

  • いいい
  • ううう

謎仕様

テーブル(| -を使って表現する)

 表を作れる。1列目はヘッダー行になるのでBOLD仕様。ヘッダー行の次の行で 左寄せ:---、中央寄せ:---:、右寄せ---:、等ができる

number | height\[cm\] | weight\[kg\]
---: | :---: | :---
13 | 154.7 | 45.6
14 | 157.4 | 49.0
15 | 157.2 | 51.6
16 | 157.7 | 50.2
17 | 159.1 | 51.6

number height[cm] weight[kg]
13 154.7 45.6
14 157.4 49.0
15 157.2 51.6
16 157.7 50.2
17 159.1 51.6

リンク([ ]()で囲む)

 リンクを付ける。[リンクされる文字](リンク先 "タイトル")という感じで書く。参照(引用)形式もできる。リンクする文字列を[]で囲み次の[]で引用記号を書く。引用記号は、数値でも文字でもOK。引用は、リンク文字の文章外の段落外に引用記号を[]で囲み:(コロン)で区切って[リンク先] "タイトル"で表現できる。引用形式をとることで文章内に複数リンクを埋め込んでも編集テキストがリンクのURLで埋まり文章の修正や添削、内容の確認の邪魔になりにくい

[chyoh'BLOG](https://chiyoh.hatenablog.com/)
[chyoh'BLOG](https://chiyoh.hatenablog.com/ "タイトル")

Link: chyoh'BLOG
Link(タイトル付き): chyoh'BLOG


参照形式

ウキペディアのMarkdownのページ…[wikipedia][1]   
Markdownの仕様のページ...[Markdown][2]  


[1]:[https://ja.wikipedia.org/wiki/Markdown] "Markdown" 
[2]:[https://daringfireball.net/projects/markdown/] "John Gruberさんのところ"

ウキペディアのMarkdownのページ…wikipedia
Markdownの仕様のページ...Markdown


リンク(自動リンク) (リアルタイムプレビューではリンクは貼られない)

 書かれている文章から、自動的にリンクを生成する方式。Markdownではなく、wiki的な はてなの仕様

http://www.hatena.ne.jp/
https://www.hatena.ne.jp/login
mailto:info@example.com

http://www.hatena.ne.jp/
https://www.hatena.ne.jp/login
info@example.com


hatenadiary.g.hatena.ne.jp

staff.hatenablog.com

画像(![ ]()で囲む。リンクと違って頭に!が付いている)

 画像を埋め込む、基本はリンク表現と同じ。頭に!を付けることで画像として認識する。はてなブログ仕様で使用できない?

![写真](https://drive.google.com/file/d/1wLNvoAP-3mBLdC2kP-VwzYzUFMyVCgzl/preview) "タイトル"

写真 "タイトル"


画像2(埋め込み) (fotolife記法 はてな仕様)

 HatenaPotolifeにアップされた画像を埋め込むための記法。Markdown記法の場合、エントリー画面に画像をドロップして、HatenaPotolifeにアップする。アップし終わった画像を選択し、挿入ボタンを押すことでfotolife記法のアドレスが張り付けられる。

[f:id:chiyoh:20190503004617p:plain]

f:id:chiyoh:20190503004617p:plain


staff.hatenablog.com

help.hatenablog.com

help.hatenablog.com

f.hatena.ne.jp

目次([:contents])

 [:contents]を入れることでヘッダーを一覧にした目次を生成する。(Markdownの仕様ではない)

[:contents]


脚注([^数字]で囲む。[^識別]:脚注)

 脚注の内容は、エントリーの最後にまとめて表示される。記述の方法は、リンクの方法と似ている。(数字以外でも発生する。識別に数値以外を使うこともできるが結局は、エントリーの頭から順に数値が割り振られその時の番号が表示される。ただし、脚注とリンクする脚注内容は、識別文字として同じも出なくてはだめ。ユニークでないと識別しない)

0123456789[^1]、abcdefgh[^2]、ABCDEFGH[^3]、あいうえお[^4]

[^1]: 数値  
[^2]: 小文字アルファベット  
[^3]: 大文字アルファベット  
[^4]: 平仮名  

01234567893、abcdefgh4、ABCDEFGH5、あいうえお6


装飾

強調(「*」「_」で囲むことで強調文字を表す)

 文字を強調する、これも、英語圏の文化italic typebold typeに変化させて強調。*``_を3つ重ねることでイタリック+ボールドになる。

import *matplotlib*  
import _numpy_ as _np_  
import **matplotlib.pyplot** as __plt__  
import ***numpy*** as ___np___   
import ****math****  
import ____os____  

1つ *イタリック* _イタリック_  
2つ **ボールド** __ボールド__   
3つ ***ボールド+イタリック*** ___ボールド+イタリック___

import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
import math
import os

1つ イタリック イタリック
2つ ボールド ボールド
3つ ボールド+イタリック ボールド+イタリック


打消し(~~で囲む)

 文字の打消し、~2個以上で囲むことにより棒線で取り消し表現ができる

~aaaaaaa~  
~~aaaaaaa~~  
~~~aaaaaaa~~~  

~aaaaaaa~
aaaaaaa
aaaaaaa


エスケープ(文字の頭に\(バックスラッシュ)を入れる)

 \を入れることでいろいろ回避できる。Markdownも含めていろいろなルールがあり英数字記号の組み合わせにより、予想外のマーク適応が発生した時などに\をいれることで回避できる。

__aaaaaaa__  (__2つなのでBOLD)
\_\_aaaaaaa\_\_  (\_\_にしているのでアンダーバーの記号として画面表示される)

aaaaaaa
__aaaaaaa__


上付き、下付き(^,_文字と文字の間にいれる)

 ^上付きになる。 _した付きになる。_は、ベーシックなシンタックス_をイタリックに変えるルールがあるのでデコードがどうなるか癖を見極めて使う必要がありそう。日本語^日本語が使えない(全角英数)など。^_で微妙に動きが違う

z=x^2+y^2  
上付き^脚注1   
abcd^1234    
ABC^DEF    
上付き^1   
上付き^abc def   

z=x_2+y_2  
上付き_脚注1   
abcd_1234    
ABC_DEF    
上付き_1   
上付き_abc def   

z=x2+y2
上付き^脚注1
abcd1234
ABCDEF
上付き1
上付きabc def

z=x_2+y_2
上付き脚注1
abcd_1234
ABC_DEF
上付き
1
上付き_abc def


TEX([tex:,]で囲む) リアルタイムプレビューは使えない

 [tex:~~]と書くと使える。ただ、非常に使いにくい。
[,],^,_は、Markdownで使用しているのでTEXの中ではエスケープしてあげる必要がある。\\[,\\],\^,\_に変換

2次方程式[tex:ax\^2+bx+c=0] こんな感じ  
解の公式 [tex:\frac{-b\pm \sqrt{b\^2-4ac}}{2a}] こんな感じ

2次方程式ax^2+bx+c=0 こんな感じ
解の公式 \frac{-b\pm \sqrt{b^2-4ac}}{2a} こんな感じ


HTML(色指定) (タグ埋め込み)

<span style="color: bule">青色</span>
<span style="color: red">赤色</span>
<span style="color: yellow">黄色</span>
<span style="background-color: #00FFFF">青色</span>
<span style="background-color: #FF00FF">赤色</span>
<span style="background-color: #FFFF00">黄色</span>

青色 赤色 黄色 青色 赤色 黄色


■■■■

箇条書き(リスト)2 未実装

 タスクリスト


  • [x] aaaaa
  • [x] aaaaa
  • [ ] aaaaa

絵文字(:コロンで囲む) 未実装

 ブラウザー仕様?とりあえず、表示できていない


:smile:
:ghost:


未実装

=AAA=  
==AAA==  
===AAA===  

=AAA=
==AAA==
===AAA===


数式($,$$で囲む) 未実装

 $文字$や、$$$$は行を跨いで。ルールはTEXに準拠?

2次方程式 $ax2+bx+c=0$ こんな感じ
解の公式 $\frac{-b\pm \sqrt{b2-4ac}}{2a}$ こんな感じ

$$

ax2+bx+c=0

\frac{-b\pm \sqrt{b2-4ac}}{2a}

$$

メモ

 下記の4つは、リアルタイムプレビュー非対応


脚注↓この辺に表示されているはず

便利機能?

chiyoh.hatenablog.com


  1. 欧米のワープロ(文書作成プロセス)と日本語が相容れない点だね。日本語を扱う人は文字を絵と認識している文字を書く(ペンで書く、文字を打ち込む)時点が最終状態と認識している点が大いに問題。その辺を指摘する機会を逃してしまった世代がWordで作っている文章がえらいことになっている。いまだにWordの機能を使いきれない人が多い。Wordってチョー使いにくいと言っている。思想が違うからね)。見た目から安易に改行(Enter)キーを押してはいけない

  2. そもそも、電子データのいいところは、出力先を自由に変更できる点でこの辺は、カラム数の呪縛はDOSからWindowsに移った時に解放されたはずであるが解放されなかった。あ、プリンターの呪縛もあったか。これも、レーザープリンタが普及に従って無くなったはずなのに。要は、出力が先が固定になっており、使う側がそれに縛られていた時代であった。時代は進み、出力先を自由に選べる自体が来たが文章を作る側が最終出力先を決めて作っているために不幸が今も続いている。出力先が、A4縦であったり、A3横であったり、ガラケ携帯メール、スマホ、PCの画面いろいろと多岐にわたる。よって、最近は改行キーは改行の意味を果たしてない。もしくは編集画面内だけ有効になっている。Markdownにおいては改行は、1スペースと入れ替わるみたいだ。

  3. 数値

  4. 小文字アルファベット

  5. 大文字アルファベット

  6. 平仮名