3

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?

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, this has helped me to understand working with dpi/pixels/inches in matplotlib! However, how would you do this with an image for example? If you wanted to open and display it in a subplot with its original size, then get the center 50x50 box like you did and display it in another subplot?
In the above solution ar is a (grayscale) image. So I'm not sure if I understand the issue here. In case you have a 3-channel color image, you 'd need to add another dimension in the indexing for the cut, cut = ar[50:100, 200:250, :]. Is that what you mean?
The issue regards the sizing of the image, and trying to get its equivalent size in the plot. I've edited my original post, giving an example of what I need. Ty
So I did provide a solution in my answer. If you don't use this solution, but some arbitrary subplot grid instead, you will of course not get the desired outcome. What more can I do to help you?
0

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:])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.