dtam
dtam

Reputation: 251

matplotlib opencv image subplot

Within a global matplotlib figure, I want to have a few subplots of different sizes. For example, one subplot should be the exact size of the frame captured from opencv2 (webcam), and the other should be a smaller image obtained from that frame.

I'm having two different issues, both regarding to sizing:

  1. I'm aware I can indicate the figSize of the plt.figure, but how can I set a different subplot size for each subplot (fig.add_subplot)?
  2. The frame from opencv is with pixels, how can I make a subplot show the exact same image (in size), especially since matplotlib uses inches for dimensions?

Getting frame:

import cv2
import matplotlib.pyplot as plt

cap = cv2.VideoCapture(0)
ret, frame = cap.read()

Building figure and subplots:

fig = plt.figure()
img = fig.add_subplot(121)
img2 = fig.add_subplot(122)

Then putting frame into subplot

img.imshow(frame) #this should be the original size of captured frame
#take out a square of the frame, and plot with box dimensions
#img2.imshow(box)

Cheers!

------ EDIT ------

Although I'll be using a webcam for the images, the core of my problem is the following: 1. Open image with opencv 2. Plot the image into a subplot, having same dimensions as the opencv read image

The code:

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('flower.jpg')
cv2.imshow('img',img)

fig = plt.figure()
video_plot = plt.subplot2grid((10, 10), (0, 0)) #Here I need to specify the same size as original
video = video_plot.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

cv2.waitKey(0)
cv2.destroyAllWindows()

plt.show()

Original 256x256 picture, read opened with opencv

enter image description here

Smaller image, if I leave out colspan and rowspan (=1) plt.subplot2grid((10, 10), (0, 0))

enter image description here

Bigger image, if I max out the colspan and rowspan: plt.subplot2grid((10, 10), (0, 0), colspan=10, rowspan=10)

enter image description here

So to sum up, how can I plot the same image size?

Upvotes: 3

Views: 8429

Answers (2)

Wolfgang Fahl
Wolfgang Fahl

Reputation: 15586

The solution of @ImportanceOfBeingErnes worked for me but i had a hard time understanding it. The issue is with the difference in the width/height extraction from the array for images of opencv and matplotlib. I prefer to use readable variable names to hide this complexity.

So this helper function is the core of getting an apropriate axis:

def addImage(fig,image,x,y):    
    dpi=fig.dpi
    fw,fh=fig.get_size_inches()[:2]
    ih,iw=image.shape[:2]
    ax = fig.add_axes([x/dpi/fw,y/dpi/fh,iw/dpi/fw,ih/dpi/fh])
    return ax  

Result: ChessBoard Example

Code: see also https://github.com/WolfgangFahl/play-chess-with-a-webcam/blob/master/examples/matplotlib/image_show.py

#!/usr/bin/python
# -*- encoding: utf-8 -*-
# part of https://github.com/WolfgangFahl/play-chess-with-a-webcam
# see https://stackoverflow.com/questions/43372792/matplotlib-opencv-image-subplot
import matplotlib.pyplot as plt
import sys
import cv2

def main(argv):
    # get the square chessbord file as an example
    default_file = '../../testMedia/chessBoard011.jpg'
    filename = argv[0] if len(argv) > 0 else default_file
    image = cv2.imread(filename)
    cv2.cvtColor(ima,cv2.COLOR_BGR2RGB)

    # resize it to fit at the default 100 dpi
    w=320
    h=320
    x=50
    y=50
    image = cv2.resize(image,(w,h))
    # 6 x 4 inch figure
    figsize=(6, 4)
    fig = plt.figure(figsize=figsize)

    # add the image at position x=50, y=50 (in image coordinates)
    ax=addImage(fig,image,x,y)
    im = ax.imshow(image)

    subboard2x2=image[w//4:w//2,h//4:h//2,:]
    yLegendOffset=100
    ax2=addImage(fig,subboard2x2,w+yLegendOffset,h//2)
    im2=ax2.imshow(subboard2x2) 

    ax.axis("off")

    plt.show()

def addImage(fig,image,x,y):    
    dpi=fig.dpi
    fw,fh=fig.get_size_inches()[:2]
    ih,iw=image.shape[:2]
    ax = fig.add_axes([x/dpi/fw,y/dpi/fh,iw/dpi/fw,ih/dpi/fh])
    return ax  

if __name__ == "__main__":
    main(sys.argv[1:])

Upvotes: 0

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339560

The figure size is indeed specified in inches. The dots per inch (dpi) are specified as well in matplotlib.

Knowing the figure size in inches and the dpi allows you to position an axes in figure coordinates (from 0 to 1), by dividing the pixels by the dpi and the figure size. E.g. in order to place an axes 50 pixels away from the lower left corner you'd place the axes at

fig.add_axes([50./dpi/figsize[0], 50./dpi/figsize[1], ...])

The width and height are in analogy determined by the number of pixels in each direction divided by dpi and figure size in inches.

Complete example:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)

ar = np.random.poisson(lam=10, size=(250,300))
cut = ar[50:100, 200:250]; print cut.shape
dpi=100. # dots per inch
figsize=(5, 3.5)
fig = plt.figure(figsize=figsize)

ax = fig.add_axes([50./dpi/figsize[0],50./dpi/figsize[1],
                    ar.shape[1]/dpi/figsize[0],ar.shape[0]/dpi/figsize[1]])
im = ax.imshow(ar)

ax2 = fig.add_axes([100./dpi/figsize[0]+ar.shape[1]/dpi/figsize[0],50./dpi/figsize[1],
                    cut.shape[1]/dpi/figsize[0],cut.shape[0]/dpi/figsize[1]])
im2 = ax2.imshow(cut)

ax.axis("off")

plt.show()

enter image description here

Upvotes: 1

Related Questions