One subtlety: when the kernel slides over a pixel near the image boundary, some of its cells fall off the edge of the input. There's no value there to multiply by, so you have to invent one. The common choices: pad with zeros (which darkens edges), repeat the boundary pixel, or reflect the image. Each produces slightly different results in the first few pixels of the output. Most BOM convolutional filter engines expose this as a border-handling parameter; the default usually works, but it's worth knowing why your output looks slightly off in the corners.
The morphological operationsLoading... face the same boundary handling with their structuring elementLoading... — same underlying action, same edge subtlety, applied to binary masks instead of grayscale intensities.