Commit 1213b32b authored by Oleg Dzhimiev's avatar Oleg Dzhimiev

initial + splitting composite images

parent 58bb6b94
__author__ = "Oleg Dzhimiev"
__copyright__ = "Copyright (C) 2018 Elphel Inc."
__license__ = "GPL-3.0+"
__email__ = "oleg@elphel.com"
'''
Example usage:
test.py:
import elphel_exif
info = elphel_exif.Exif("test.jpeg")
print(info)
print(info.MakerNote)
print(info.exif.toString())
'''
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
# Might be needed by some applications like:
# panoramic of Eyesis4Pi, composite images
def elphel_add_to_PIL_TAGS():
TAGS[0x0129] = 'Page'
TAGS[0xC62F] = 'Camera Serial Number'
# Open file and Parse
def elphel_get_exif(f):
ret = {}
i = Image.open(f)
info = i._getexif()
elphel_add_to_PIL_TAGS()
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if decoded == 'GPSInfo':
gret = {}
for gtag,gval in value.items():
gdecoded = GPSTAGS.get(gtag,gtag)
gret[gdecoded] = gval
value = gret
if decoded == 'MakerNote':
value = elphel_MakerNote_parse(value)
ret[decoded] = value
return ret
# Init with default values
def elphel_MakerNote_defaults():
ret = {}
# defaults
ret['COLOR_MODE'] = 0
return ret
# Based on JP46_Reader_camera.java's jp46Reorder()
def elphel_MakerNote_parse(MakerNote):
ret = {}
ret = elphel_MakerNote_defaults()
ret['GAIN_R' ] = MakerNote[0]/65536.0
ret['GAIN_G'] = MakerNote[1]/65536.0
ret['GAIN_GB'] = MakerNote[2]/65536.0
ret['GAIN_B'] = MakerNote[3]/65536.0
ret['BLACK_R'] = (MakerNote[4]>>24)/256.0
ret['BLACK_G'] = (MakerNote[5]>>24)/256.0
ret['BLACK_GB'] = (MakerNote[6]>>24)/256.0
ret['BLACK_B'] = (MakerNote[7]>>24)/256.0
ret['GAMMA_R'] = ((MakerNote[4]>>16)&0xff)/100.0
ret['GAMMA_G'] = ((MakerNote[5]>>16)&0xff)/100.0
ret['GAMMA_GB'] = ((MakerNote[6]>>16)&0xff)/100.0
ret['GAMMA_B'] = ((MakerNote[7]>>16)&0xff)/100.0
ret['GAMMA_SCALE_R'] = MakerNote[4]&0xffff
ret['GAMMA_SCALE_G'] = MakerNote[5]&0xffff
ret['GAMMA_SCALE_GB'] = MakerNote[6]&0xffff
ret['GAMMA_SCALE_B'] = MakerNote[7]&0xffff
if len(MakerNote)>=14:
if (MakerNote[10]&0xc0000000)!=0:
composite = 1
else:
composite = 0
ret['COMPOSITE'] = composite
if composite:
ret['HEIGHT1'] = MakerNote[11]&0xffff
ret['BLANK1'] = (MakerNote[11]>>16)&0xffff
ret['HEIGHT2'] = MakerNote[12]&0xffff
ret['BLANK2'] = (MakerNote[12]>>16)&0xffff
ret['HEIGHT3'] = (MakerNote[9]>>16)-ret['HEIGHT1']-ret['BLANK1']-ret['HEIGHT2']-ret['BLANK2']
ret['FLIPH1'] = (MakerNote[10]>>24)&0x1
ret['FLIPV1'] = (MakerNote[10]>>25)&0x1
ret['FLIPH2'] = (MakerNote[10]>>26)&0x1
ret['FLIPV2'] = (MakerNote[10]>>27)&0x1
ret['FLIPH3'] = (MakerNote[10]>>28)&0x1
ret['FLIPV3'] = (MakerNote[10]>>29)&0x1
ret['PORTRAIT'] = (MakerNote[13]>>7)&0x1
ret['YTABLEFORC'] = (MakerNote[13]>>15)&0x1
ret['QUALITY'] = MakerNote[13]&0x7f
ret['CQUALITY'] = (MakerNote[13]>>8)&0x7f
if ret['CQUALITY']==0:
ret['CQUALITY'] = ret['QUALITY']
ret['CORING_INDEX_Y'] = (MakerNote[13]>>16)&0x7f
ret['CORING_INDEX_C'] = (MakerNote[13]>>24)&0x7f
if ret['CORING_INDEX_C']==0:
ret['CORING_INDEX_C'] = ret['CORING_INDEX_Y']
if len(MakerNote)>=12:
ret['WOI_LEFT'] = MakerNote[8]&0xffff
ret['WOI_WIDTH'] = MakerNote[8]>>16
ret['WOI_TOP'] = MakerNote[9]&0xffff
ret['WOI_HEIGHT'] = MakerNote[9]>>16
ret['FLIPH'] = MakerNote[10]&0x1
ret['FLIPV'] = (MakerNote[10]>>1)&0x1
ret['BAYER_MODE'] = (MakerNote[10]>>2)&0x3
ret['COLOR_MODE'] = (MakerNote[10]>>4)&0x0f
ret['DCM_HOR'] = (MakerNote[10]>>8)&0x0f
ret['DCM_VERT'] = (MakerNote[10]>>12)&0x0f
ret['BIN_HOR'] = (MakerNote[10]>>16)&0x0f
ret['BIN_VERT'] = (MakerNote[10]>>20)&0x0f
# HW specific temperature readings
if len(MakerNote)>=16:
ret['TEMP1'] = (MakerNote[14]>> 0)&0xffff
ret['TEMP2'] = (MakerNote[14]>>16)&0xffff
ret['TEMP3'] = (MakerNote[14]>> 0)&0xffff
ret['TEMP4'] = (MakerNote[14]>>16)&0xffff
return ret
# Class
class Exif:
def __init__(self,f):
self.f = f
self.data = elphel_get_exif(f)
self.MakerNote = self.data['MakerNote']
#self.color_mode = self.data['MakerNote']['COLOR_MODE']
def toString(self):
res = ''
for k in self.data:
if k!='MakerNote':
res += str(k)+": "+str(self.data[k])+"\n"
res += "MakerNote: \n"
for k in self.data['MakerNote']:
res += " "+str(k)+": "+str(self.data['MakerNote'][k])+"\n"
return res[:-1]
#!/usr/bin/env python3
import jp4
#import cv2
import numpy as np
import sys
from PIL import Image, ImageOps
try:
filename = sys.argv[1]
except IndexError:
filename = "test.jp4"
img = jp4.JP4(filename)
# print exif
#print(img.exif.toString())
# deblock if JP4 (auto read from exif) skip if other
img.deblock()
img.demosaic_bilinear()
img.saturation()
I = img.image[...,[2,1,0]].astype(np.uint8)
res = Image.fromarray(I)
#res.save("result.png")
# also works
#res.save("result.jpeg")
# for Eyesis4pi
exif = img.exif
mn = exif.MakerNote
#print(exif.MakerNote)
# common
# WOI_WIDTH
# FLIPH
# FLIPV
# PORTRAIT
# COMPOSITE
# individual
# HEIGHTx, 1..3
# FLIPHx, 1..3
# FLIPVx, 1..3
# BLANKx, only 1 and 2
if mn['COMPOSITE']:
W = mn['WOI_WIDTH']
H = mn['WOI_HEIGHT']
FH = mn['FLIPH']
FV = mn['FLIPV']
#if FV:
# I =
v_offset = 0
for i in range(1,4):
try:
height = mn['HEIGHT'+str(i)]
#fliph = mn['FLIPH'+str(i)]
flipv = mn['FLIPV'+str(i)]
except KeyError:
# break for '_9'
break
try:
blank = mn['BLANK'+str(i)]
except KeyError:
blank = 0
crop = res.crop((0,v_offset,W,v_offset+height))
if flipv:
crop = ImageOps.flip(crop)
# if eyesis4pi, 1&2 - rotate 90CW, 3 - 90CCW
if i<3:
crop = crop.rotate(-90,expand=1)
else:
crop = crop.rotate(90,expand=1)
crop.save("result_"+str(i)+".jpeg")
v_offset += height + blank
__copyright__ = "Copyright 2018, Elphel, Inc."
__license__ = "GPL-3.0+"
__maintainer__ = "Oleg Dzhimiev"
__email__ = "oleg@elphel.com"
import numpy as np
#import imageio
import elphel_exif
from PIL import Image, ImageOps
'''
Python image manipulation libs:
Lib Reads image as Exif Image manipulations Comment
Pillow(PIL) Image + + likes np.uint8 arrays
imageio numpy.array - - almost useless
opencv numpy.array - +
Recommendations:
'''
# clear from code
def get_exif(filename):
exif = elphel_exif.Exif(filename)
# exif.f - filename
# exif.data - parsed (as dict) exif with parsed MakeNote
# exif.MakerNote - parsed as dict
return exif
# clear from code
def naur(a):
b0 = a[0]
b0 = np.reshape(b0,(1,b0.shape[0]))
b = a[:-1]
b = np.concatenate((b0,b),axis=0)
return b
# clear from code
def nabr(a):
b0 = a[-1]
b0 = np.reshape(b0,(1,b0.shape[0]))
b = a[1:]
b = np.concatenate((b,b0),axis=0)
return b
# clear from code
def nalc(a):
b0 = a[::,0]
b0 = np.reshape(b0,(b0.shape[0],1))
b = a[::,:-1]
b = np.concatenate((b0,b),axis=1)
return b
# clear from code
def narc(a):
b0 = a[::,-1]
b0 = np.reshape(b0,(b0.shape[0],1))
b = a[::,1:]
b = np.concatenate((b,b0),axis=1)
return b
class JP4:
def __init__(self,filename="test.jp4"):
# open image using PIL
#i = Image.open(filename)
#self.px = i.load()
#self.px = scipy.misc.imread(filename, flatten=False, mode='RGB')
#self.h, self.w, self.chn = self.px.shape
self.exif = get_exif(filename)
# not JP4 - no deblock
self.need_deblocking = True if self.exif.MakerNote['COLOR_MODE']==5 else False
self.need_demosaicing = True if self.need_deblocking else False
#print(self.exif.color_mode)
#print(self.exif.MakerNote['GAIN_R'])
#print(self.exif.MakerNote['GAIN_G'])
#print(self.exif.MakerNote['GAIN_B'])
#print(self.exif.MakerNote['GAIN_GB'])
if self.exif.MakerNote['BAYER_MODE']==0:
self.bayer = [["Gr","R"],["B","Gb"]]
elif self.exif.MakerNote['BAYER_MODE']==1:
self.bayer = [["R","Gr"],["Gb","B"]]
elif self.exif.MakerNote['BAYER_MODE']==2:
self.bayer = [["B","Gb"],["Gr","R"]]
elif self.exif.MakerNote['BAYER_MODE']==3:
self.bayer = [["Gb","B"],["R","Gr"]]
# open image
# TODO: do not open twice (the 1st opening was when reading exif)
im = Image.open(filename)
# TODO: handle orientation
# imageio - takes care about orientation
# PIL - does not, which is fine - just need to change bayer mosaic
# rotated 90 CW
if self.exif.data['Orientation']==6:
# keep bayer
pass
# horizontal mirror then rotated 270 CW
elif self.exif.data['Orientation']==5:
# change bayer mosaic
if self.bayer==[["Gr","R"],["B","Gb"]]:
self.bayer = [["B","Gb"],["Gr","R"]]
#im = ImageOps.mirror(im)
#im = im.rotate(270)
# TODO: change bayer due to flips here
# 'L' is 8 bit mode
#self.image = scipy.misc.imread(filename, flatten=False, mode='L')
#im_arr = imageio.imread(filename)
#im = im.rotate(90)
#im = ImageOps.mirror(im)
#im_arr = im.getdata()
im_arr = np.fromstring(im.tobytes(), dtype=np.uint8)
im_arr = im_arr.reshape((im.size[1], im.size[0]))
print(im_arr.shape)
self.image = im_arr
self.h, self.w = self.image.shape
# in JP4 format the 16x16 block is arranged in 8x32,
# where color channels are grouped in 8x8 (8x8Gr, 8x8R, 8x8B, 8x8Gb)
# the 1st line of 8x32 blocks is the left half of the image
# the 2nd line of 8x32 blocks is the right half
def deblock(self):
if not self.need_deblocking:
return 0
# tmp copy
I = np.copy(self.image)
W = self.w
H = self.h
bayer = self.bayer
# 16x16 block
block = np.zeros((16,16))
# 16xW
stripe = np.zeros((16,W))
I = np.reshape(I,(H>>4,16,W))
for i in range(I.shape[0]):
# stripe 16xW
stripe = np.copy(I[i])
for j in range(0,W,16):
if j<W/2:
k = 0
l = 2*j
else:
k = 8
l = int(2*(j-W/2))
# gr r b gb
block[0::2,0::2] = stripe[k:k+8,l+ 0:l+ 0+8]
block[0::2,1::2] = stripe[k:k+8,l+ 8:l+ 8+8]
block[1::2,0::2] = stripe[k:k+8,l+16:l+16+8]
block[1::2,1::2] = stripe[k:k+8,l+24:l+24+8]
I[i,0:16,j:j+16] = block
I = np.reshape(I,(H,W))
self.image = I
# gamma encode, gamma correct
# incorrect, do not use
def apply_gamma(self):
print("apply gamma")
#I = np.copy(self.image.astype(np.int32))
I = np.copy(self.image)
gain_r = self.exif.MakerNote['GAIN_R']
gain_gr = self.exif.MakerNote['GAIN_G']
gain_b = self.exif.MakerNote['GAIN_B']
gain_gb = self.exif.MakerNote['GAIN_GB']
I[0::2,0::2] = I[0::2,0::2]**(1/gain_gr)
I[0::2,1::2] = I[0::2,1::2]**(1/gain_r)
I[1::2,0::2] = I[1::2,0::2]**(1/gain_b)
I[1::2,1::2] = I[1::2,1::2]**(1/gain_gb)
self.image = I
# unapply gamma
# incorrect, do not use
def linearize(self):
print("unapply gamma")
I = np.copy(self.image.astype(np.float))
gain_r = self.exif.MakerNote['GAIN_R']
gain_gr = self.exif.MakerNote['GAIN_G']
gain_b = self.exif.MakerNote['GAIN_B']
gain_gb = self.exif.MakerNote['GAIN_GB']
I[0::2,0::2] = I[0::2,0::2]**gain_gr
I[0::2,1::2] = I[0::2,1::2]**gain_r
I[1::2,0::2] = I[1::2,0::2]**gain_b
I[1::2,1::2] = I[1::2,1::2]**gain_gb
# OpenCV uses BGR format
self.image = I
# simple but incorrect, with 2x downsampling
# from here: https://groups.google.com/forum/#!topic/pydc1394/KycTwjyBDV0
def demosaic_simple_downsample(self):
if not self.need_demosaicing:
return 0
# no need in clipping
I = np.copy(self.image)
R = I[0::2,1::2]
G = I[1::2,1::2]/2 + I[0::2,0::2]/2
B = I[1::2,0::2]
# OpenCV uses BGR format
I = np.dstack([B,G,R])
self.image = I
def demosaic_bilinear_slow_and_true(self):
if not self.need_demosaicing:
return 0
bayer = self.bayer
I = np.copy(self.image.astype(np.float))
R = np.copy(I)
G = np.copy(I)
B = np.copy(I)
h,w = I.shape
for j in range(h):
for i in range(w):
jt = j+1 if j==0 else j-1
jb = j-1 if j==h-1 else j+1
il = i+1 if i==0 else i-1
ir = i-1 if i==w-1 else i+1
px_0 = I[j,i]
px_t, px_b, px_l, px_r = I[jt,i], I[jb,i], I[j,il], I[j,ir]
px_tl,px_tr,px_bl,px_br = I[jt,il],I[jt,ir],I[jb,il],I[jb,ir]
if (bayer[j%2][i%2]=="Gr"):
R[j,i] = (px_l + px_r)/2
G[j,i] = (4*px_0 + px_tl + px_tr + px_bl + px_br)/8
B[j,i] = (px_t + px_b)/2
elif (bayer[j%2][i%2]=="R"):
R[j,i] = px_0
G[j,i] = (px_t + px_b + px_l + px_r)/4
B[j,i] = (px_tl + px_tr + px_bl + px_br)/4
elif (bayer[j%2][i%2]=="B"):
R[j,i] = (px_tl + px_tr + px_bl + px_br)/4
G[j,i] = (px_t + px_b + px_l + px_r)/4
B[j,i] = px_0
elif (bayer[j%2][i%2]=="Gb"):
R[j,i] = (px_t + px_b)/2
G[j,i] = (4*px_0 + px_tl + px_tr + px_bl + px_br)/8
B[j,i] = (px_l + px_r)/2
# OpenCV uses BGR format
I = np.dstack([B,G,R])
self.image = I
return I
def demosaic_bilinear(self):
if not self.need_demosaicing:
return 0
bayer = self.bayer
# int16 is enough, int32 - native and faster? need to test
I = np.copy(self.image.astype(np.int32))
R = np.copy(I)
G = np.copy(I)
B = np.copy(I)
# this depends on bayer
p00 = I[0::2,0::2]
p01 = I[0::2,1::2]
p10 = I[1::2,0::2]
p11 = I[1::2,1::2]
if bayer==[["Gr","R"],["B","Gb"]]:
R[0::2,0::2] = (p01 + nalc(p01))/2
G[0::2,0::2] = (4*p00 + p11 + naur(p11) + nalc(p11 + naur(p11)))/8
B[0::2,0::2] = (p10 + naur(p10))/2
R[0::2,1::2] = p01
G[0::2,1::2] = (p00 + narc(p00) + p11 + naur(p11))/4
B[0::2,1::2] = (p10 + naur(p10) + narc(p10 + naur(p10)))/4
R[1::2,0::2] = (p01 + nabr(p01) + nalc(p01 + nabr(p01)))/4
G[1::2,0::2] = (p11 + nalc(p11) + p00 + nabr(p00))/4
B[1::2,0::2] = p10
R[1::2,1::2] = (p01 + nabr(p01))/2
G[1::2,1::2] = (4*p11 + p00 + nabr(p00) + narc(p00 + nabr(p00)))/8
B[1::2,1::2] = (p10 + narc(p10))/2
elif bayer==[["R","Gr"],["Gb","B"]]:
R[0::2,0::2] = p00
G[0::2,0::2] = (p01 + nalc(p01) + p10 + naur(p10))/4
B[0::2,0::2] = (p11 + naur(p11) + nalc(p11 + naur(p11)))/4
R[0::2,1::2] = (p00 + narc(p00))/2
G[0::2,1::2] = (4*p01 + p10 + naur(p10) + narc(p10 + naur(p10)))/8
B[0::2,1::2] = (p11 + naur(p11))/2
R[1::2,0::2] = (p00 + nabr(p00))/2
G[1::2,0::2] = (4*p10 + p01 + nabr(p01) + nalc(p01 + nabr(p01)))/8
B[1::2,0::2] = (p11 + nalc(p11))/2
R[1::2,1::2] = (p00 + nabr(p00) + narc(p00 + nabr(p00)))/4
G[1::2,1::2] = (p01 + nabr(p01) + p10 + narc(p10))/4
B[1::2,1::2] = p11
elif bayer==[["B","Gb"],["Gr","R"]]:
R[0::2,0::2] = (p11 + naur(p11) + nalc(p11 + naur(p11)))/4
G[0::2,0::2] = (p10 + naur(p10) + p01 + nalc(p01))/4
B[0::2,0::2] = p00
R[0::2,1::2] = (p11 + naur(p11))/2
G[0::2,1::2] = (4*p01 + p10 + naur(p10) + narc(p10 + naur(p10)))/8
B[0::2,1::2] = (p00 + narc(p00))/2
R[1::2,0::2] = (p11 + nalc(p11))/2
G[1::2,0::2] = (4*p10 + p01 + nabr(p01) + nalc(p01 + nabr(p01)))/8
B[1::2,0::2] = (p00 + nabr(p00))/2
R[1::2,1::2] = p11
G[1::2,1::2] = (p10 + narc(p10) + p01 + nabr(p01))/4
B[1::2,1::2] = (p00 + nabr(p00) + narc(p00 + nabr(p00)))/4
# OpenCV uses BGR format
I = np.dstack([B,G,R])
self.image = I
return I
def saturation(self):
I = self.image.astype(np.int32)
# use '2' for now
s = 2
r = I[:,:,2]
g = I[:,:,1]
b = I[:,:,0]
Y = 0.299*r+0.5870*g+ 0.144*b
Cb = 128+s*(-0.1687*r-0.3313*g+ 0.500*b)
Cr = 128+s*( 0.5*r-0.4187*g-0.0813*b)
Cb[Cb<0]=0
Cb[Cb>255]=255
Cr[Cr<0]=0
Cr[Cr>255]=255
r = Y + 1.402*(Cr-128)
g = Y - 0.34414*(Cb-128)-0.71414*(Cr-128)
b = Y + 1.772*(Cb-128)
r[r<0]=0
r[r>255]=255
g[g<0]=0
g[g>255]=255
b[b<0]=0
b[b>255]=255
# OpenCV uses BGR format
I = np.dstack([b,g,r])
self.image = I
return I
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment