|
|
@ -0,0 +1,178 @@ |
|
|
|
Let's jump right into the fun and start making pixel art with Open CV. |
|
|
|
Before you read this article, consider checkout out these articles: |
|
|
|
|
|
|
|
- [Shallow Dive into Open CV](https://jrtechs.net/open-source/shallow-dive-into-open-cv) |
|
|
|
- [Image Clustering with K-means](https://jrtechs.net/data-science/image-clustering-with-k-means) |
|
|
|
|
|
|
|
|
|
|
|
Like most CV projects, we need to start by importing some libraries and loading an image. |
|
|
|
|
|
|
|
```python |
|
|
|
# Open cv library |
|
|
|
import cv2 |
|
|
|
|
|
|
|
# matplotlib for displaying the images |
|
|
|
from matplotlib import pyplot as plt |
|
|
|
|
|
|
|
img = cv2.imread('dolphin.jpg') |
|
|
|
``` |
|
|
|
|
|
|
|
I like to define scripts to print images nicely in a Jupyter notebook. |
|
|
|
|
|
|
|
```python |
|
|
|
def printI(img): |
|
|
|
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) |
|
|
|
plt.imshow(rgb) |
|
|
|
|
|
|
|
def printI2(i1, i2): |
|
|
|
fig = plt.figure() |
|
|
|
ax1 = fig.add_subplot(1,2,1) |
|
|
|
ax1.imshow(cv2.cvtColor(i1, cv2.COLOR_BGR2RGB)) |
|
|
|
ax2 = fig.add_subplot(1,2,2) |
|
|
|
ax2.imshow(cv2.cvtColor(i2, cv2.COLOR_BGR2RGB)) |
|
|
|
|
|
|
|
printI(img) |
|
|
|
``` |
|
|
|
|
|
|
|
![original image](media/pixel/output_2_0.png) |
|
|
|
|
|
|
|
|
|
|
|
To pixelate an image, we can use the open cv resize function. |
|
|
|
To make the image viewable, after we shrink the picture, we resize it again to be the size of the original image. |
|
|
|
|
|
|
|
```python |
|
|
|
def pixelate(img, w, h): |
|
|
|
height, width = img.shape[:2] |
|
|
|
|
|
|
|
# Resize input to "pixelated" size |
|
|
|
temp = cv2.resize(img, (w, h), interpolation=cv2.INTER_LINEAR) |
|
|
|
|
|
|
|
# Initialize output image |
|
|
|
return cv2.resize(temp, (width, height), interpolation=cv2.INTER_NEAREST) |
|
|
|
|
|
|
|
img16 = pixelate(img, 16, 16) |
|
|
|
|
|
|
|
printI2(img, img16) |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
![pixelated 16x16](media/pixel/output_3_0.png) |
|
|
|
|
|
|
|
We can try a few different shrinkage sizes. |
|
|
|
32x32 seems to work the best. |
|
|
|
|
|
|
|
```python |
|
|
|
img32 = pixelate(img, 32, 32) |
|
|
|
img64 = pixelate(img, 64, 64) |
|
|
|
|
|
|
|
printI2(img32, img64) |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
![pixelated 32x42, 64x64](media/pixel/output_4_0.png) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
img8 = pixelate(img, 8, 8) |
|
|
|
printI(img8) |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
![original image 8x8 pixelated](media/pixel/output_5_0.png) |
|
|
|
|
|
|
|
|
|
|
|
Despite the images being pixelated, they have imperfections that normal pixel art wouldn't have. |
|
|
|
To remove the noise and make it look smoother, we will do k-means clustering on the pixelated images. |
|
|
|
K-means will reduce the number of colors in the image and eliminate any noise. |
|
|
|
Most of the clustering code is from my blog post: [Image Clustering with K-means](https://jrtechs.net/data-science/image-clustering-with-k-means) |
|
|
|
|
|
|
|
```python |
|
|
|
import skimage |
|
|
|
from sklearn.cluster import KMeans |
|
|
|
from numpy import linalg as LA |
|
|
|
import numpy as np |
|
|
|
|
|
|
|
def colorClustering(idx, img, k): |
|
|
|
clusterValues = [] |
|
|
|
for _ in range(0, k): |
|
|
|
clusterValues.append([]) |
|
|
|
|
|
|
|
for r in range(0, idx.shape[0]): |
|
|
|
for c in range(0, idx.shape[1]): |
|
|
|
clusterValues[idx[r][c]].append(img[r][c]) |
|
|
|
|
|
|
|
imgC = np.copy(img) |
|
|
|
|
|
|
|
clusterAverages = [] |
|
|
|
for i in range(0, k): |
|
|
|
clusterAverages.append(np.average(clusterValues[i], axis=0)) |
|
|
|
|
|
|
|
for r in range(0, idx.shape[0]): |
|
|
|
for c in range(0, idx.shape[1]): |
|
|
|
imgC[r][c] = clusterAverages[idx[r][c]] |
|
|
|
|
|
|
|
return imgC |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
def segmentImgClrRGB(img, k): |
|
|
|
|
|
|
|
imgC = np.copy(img) |
|
|
|
|
|
|
|
h = img.shape[0] |
|
|
|
w = img.shape[1] |
|
|
|
|
|
|
|
imgC.shape = (img.shape[0] * img.shape[1], 3) |
|
|
|
|
|
|
|
#5. Run k-means on the vectorized responses X to get a vector of labels (the clusters); |
|
|
|
# |
|
|
|
kmeans = KMeans(n_clusters=k, random_state=0).fit(imgC).labels_ |
|
|
|
|
|
|
|
#6. Reshape the label results of k-means so that it has the same size as the input image |
|
|
|
# Return the label image which we call idx |
|
|
|
kmeans.shape = (h, w) |
|
|
|
|
|
|
|
return kmeans |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
def kMeansImage(image, k): |
|
|
|
idx = segmentImgClrRGB(image, k) |
|
|
|
return colorClustering(idx, image, k) |
|
|
|
|
|
|
|
printI(kMeansImage(img, 5)) |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
![original image with k means of 5](media/pixel/output_9_0.png) |
|
|
|
|
|
|
|
|
|
|
|
Running the k-means algorithm on the 32x32 bit image produces a cool look. |
|
|
|
|
|
|
|
```python |
|
|
|
printI(kMeansImage(img32, 3)) |
|
|
|
``` |
|
|
|
|
|
|
|
![32 bit k-means of 3 clusters](media/pixel/output_10_0.png) |
|
|
|
|
|
|
|
We can compare the original, pixelated, and clustered images side by side. |
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
def printI3(i1, i2, i3): |
|
|
|
fig = plt.figure(figsize=(18,6)) |
|
|
|
ax1 = fig.add_subplot(1,3,1) |
|
|
|
ax1.imshow(cv2.cvtColor(i1, cv2.COLOR_BGR2RGB)) |
|
|
|
ax2 = fig.add_subplot(1,3,2) |
|
|
|
ax2.imshow(cv2.cvtColor(i2, cv2.COLOR_BGR2RGB)) |
|
|
|
ax3 = fig.add_subplot(1,3,3) |
|
|
|
ax3.imshow(cv2.cvtColor(i3, cv2.COLOR_BGR2RGB)) |
|
|
|
plt.savefig('trifecta.png') |
|
|
|
printI3(img, img32, kMeansImage(img32, 3)) |
|
|
|
``` |
|
|
|
|
|
|
|
![original, pixelated, k-means](media/pixel/output_11_0.png) |