"""画像認識と物体検出(/series7)のトップのカード(3:2・1800×1200)を作る(2026-10-30)。 Log03 で撮った赤い箱の写真を4列に並べ、画素 → 白黒 → しきい値 → 輪郭 と処理が進む順に見せる。 背景の青い箱の角と紙の継ぎ目は、赤い箱と影だけを残して背景をなめらかな面に置き換えて消している。 元の写真: KIOXIA の vision/captured/Log03/IMG_7911.JPG(第1引数で渡す) 出力: 第2引数の PNG → public/site/series7-image-recognition-steps.webp """ import sys import cv2, numpy as np from PIL import Image, ImageOps, ImageDraw, ImageFont src=np.array(ImageOps.exif_transpose(Image.open(sys.argv[1])).convert('RGB')) cx,cy=2195,1019 cw,chh=885,2353 x0=cx-cw//2; y0=max(0,cy-int(chh*0.40)) crop=src[y0:y0+chh,x0:x0+cw] img=cv2.resize(crop,(450,1200),interpolation=cv2.INTER_AREA) # 赤い箱(と影)だけ残し、背景はなめらかな面に置き換える(青い箱の角・紙の継ぎ目を消す) _h=cv2.cvtColor(img,cv2.COLOR_RGB2HSV) _r=(((_h[...,0]<8)|(_h[...,0]>170))&(_h[...,1]>110)&(_h[...,2]>50)).astype(np.uint8) _n,_l,_s,_=cv2.connectedComponentsWithStats(_r); _r=(_l==1+np.argmax(_s[1:,4])).astype(np.uint8) _r=cv2.morphologyEx(_r,cv2.MORPH_CLOSE,np.ones((15,15),np.uint8)) _dark=(cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)<150).astype(np.uint8) _kill=cv2.dilate(np.maximum(_r,_dark),np.ones((25,25),np.uint8)) _bg=cv2.inpaint(img,_kill,15,cv2.INPAINT_TELEA) _bg=cv2.GaussianBlur(_bg,(0,0),40) _keep=cv2.dilate(_r,np.ones((61,61),np.uint8)) _keep[:, :]=_keep*0+_keep # 箱の近く(影を含む) _yy,_xx=np.nonzero(_r); _ymax=_yy.max() _keep[_ymax+40:,:]=0 _a=cv2.GaussianBlur(_keep.astype(np.float32),(0,0),12)[...,None] img=(img*_a+_bg*(1-_a)).astype(np.uint8) hsv=cv2.cvtColor(img,cv2.COLOR_RGB2HSV) red=(((hsv[...,0]<8)|(hsv[...,0]>170))&(hsv[...,1]>110)&(hsv[...,2]>50)).astype(np.uint8) red=cv2.morphologyEx(red,cv2.MORPH_OPEN,np.ones((5,5),np.uint8)); red=cv2.morphologyEx(red,cv2.MORPH_CLOSE,np.ones((11,11),np.uint8)) n,lab,st,_=cv2.connectedComponentsWithStats(red); k=1+np.argmax(st[1:,4]); red=(lab==k).astype(np.uint8) gray=cv2.cvtColor(img,cv2.COLOR_RGB2GRAY); g3=np.stack([gray]*3,-1).astype(float) BK=(17,17,17);WH=(241,240,236);RD=(224,38,44) t1=g3*(1-red[...,None])+img*red[...,None] t2=g3 t3=np.where(red[...,None]>0,np.array(WH),np.array(BK)).astype(float) t4=np.full_like(img,WH) cnts,_=cv2.findContours(red,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE); c=max(cnts,key=cv2.contourArea) cv2.drawContours(t4,[c],-1,RD,4,lineType=cv2.LINE_AA) x,y,w,h=cv2.boundingRect(c) def dash(im,p0,p1,col,th=2,dl=12): p0=np.array(p0,float);p1=np.array(p1,float);L=np.linalg.norm(p1-p0);n=max(2,int(L//dl)) for i in range(0,n,2): a=p0+(p1-p0)*i/n;b=p0+(p1-p0)*min(i+1,n)/n cv2.line(im,tuple(a.astype(int)),tuple(b.astype(int)),col,th,cv2.LINE_AA) p=16 for a,b in [((x-p,y-p),(x+w+p,y-p)),((x+w+p,y-p),(x+w+p,y+h+p)),((x+w+p,y+h+p),(x-p,y+h+p)),((x-p,y+h+p),(x-p,y-p))]: dash(t4,a,b,BK) M=cv2.moments(c);mx,my=int(M['m10']/M['m00']),int(M['m01']/M['m00']) cv2.line(t4,(mx-14,my),(mx+14,my),BK,2);cv2.line(t4,(mx,my-14),(mx,my+14),BK,2) out=np.concatenate([t1,t2,t3,t4.astype(float)],1) im=Image.fromarray(out.clip(0,255).astype(np.uint8)); d=ImageDraw.Draw(im) f=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',34) fs=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',28) labs=['PIXELS','GRAY','THRESHOLD','CONTOUR'] for i,l in enumerate(labs): col=WH if i==2 else BK d.text((i*450+36,1060),f'0{i+1}',font=fs,fill=col) d.text((i*450+36,1104),l,font=f,fill=col) for e in [450,900,1350]: d.line((e,0,e,1200),fill=BK,width=2) im.save(sys.argv[2])