Crop image with Python script
Function to crop an image:

def crop_image(orig_img, cropped_min_x, cropped_max_x, cropped_min_y, cropped_max_y):
    '''Crops an image object of type &lt;class 'bpy.types.Image'&gt;.  For example, for a 10x10 image, 
    if you put cropped_min_x = 2 and cropped_max_x = 6,
    you would get back a cropped image with width 4, and 
    pixels ranging from the 2 to 5 in the x-coordinate

    Note: here y increasing as you down the image.  So, 
    if cropped_min_x and cropped_min_y are both zero, 
    you'll get the top-left of the image (as in GIMP).

    Returns: An image of type  &lt;class 'bpy.types.Image'&gt;
    '''

    num_channels=orig_img.channels
    #calculate cropped image size
    cropped_size_x = cropped_max_x - cropped_min_x
    cropped_size_y = cropped_max_y - cropped_min_y
    #original image size
    orig_size_x = orig_img.size[0]
    orig_size_y = orig_img.size[1]

    cropped_img = bpy.data.images.new(name="cropped_img", width=cropped_size_x, height=cropped_size_y)

    print("Exctracting image fragment, this could take a while...")

    #loop through each row of the cropped image grabbing the appropriate pixels from original
    #the reason for the strange limits is because of the 
    #order that Blender puts pixels into a 1-D array.
    current_cropped_row = 0
    for yy in range(orig_size_y - cropped_max_y, orig_size_y - cropped_min_y):
        #the index we start at for copying this row of pixels from the original image
        orig_start_index = (cropped_min_x + yy*orig_size_x) * num_channels
        #and to know where to stop we add the amount of pixels we must copy
        orig_end_index = orig_start_index + (cropped_size_x * num_channels)
        #the index we start at for the cropped image
        cropped_start_index = (current_cropped_row * cropped_size_x) * num_channels 
        cropped_end_index = cropped_start_index + (cropped_size_x * num_channels)

        #copy over pixels 
        cropped_img.pixels[cropped_start_index : cropped_end_index] = 
                                orig_img.pixels[orig_start_index : orig_end_index]

        #move to the next row before restarting loop
        current_cropped_row += 1

    return cropped_img


How this script works

The image class stores the pixel information as a one-dimensional array, and save 4 channels for RGBA (even if you load a .jpg).  For example, on this 2x2 image, I've labelled the order that the pixels appear:



and so if you have an image object named my_image, then my_image.pixels will be a 1-D array: [R value for pixel 0, G value for 0, B value for 0, A value for 0, R value for pixel 1, etc.].

Example usage

#crop image to 100x100 square
cropped_min_x = 300
cropped_max_x = 400
cropped_min_y = 300
cropped_max_y = 400 
input_image_filepath='/home/garrett/Desktop/kjEh.jpg'

orig_img = bpy.data.images.load(input_image_filepath)

cropped_img = crop_image(orig_img, cropped_min_x, cropped_max_x, cropped_min_y, cropped_max_y)

print("Saving new image...")
cropped_img.filepath_raw = "/home/garrett/Desktop/myImage6.png" 
cropped_img.file_format = 'PNG'
cropped_img.save()
print("Finished saving")